mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Retry read on ClosedByInterruptException
In gh-38154, we started handling ClosedByInterruptException. The FileChannel was repaired by recreating it and then the exception was rethrown. This allowed other threads to use the channel that had been read by an interrupted thread while allowing that interruption to continue. This approach has proven to be insufficient as there are scenarios where the read needs to succeed on the interrupted thread. This commit updates the handling of ClosedByInterruptException so that this is the case. The FileChannel is recreated as before but the thread's interrupted flag is now cleared before retrying the read. The flag is then reinstated so that any subsequent actions that should fail due to the interruption will do so. We could clear and reinstate the interrupted flag before the first read, rather than catching ClosedByInterruptException. This approach was rejected as it will have an impact on the performance of the happy path where the thread hasn't been interrupted. Fixes gh-38611
This commit is contained in:
parent
9eae176fce
commit
e6970243ee
@ -179,15 +179,7 @@ class FileChannelDataBlock implements CloseableDataBlock {
|
||||
int read(ByteBuffer dst, long position) throws IOException {
|
||||
synchronized (this.lock) {
|
||||
if (position < this.bufferPosition || position >= this.bufferPosition + this.bufferSize) {
|
||||
this.buffer.clear();
|
||||
try {
|
||||
this.bufferSize = this.fileChannel.read(this.buffer, position);
|
||||
}
|
||||
catch (ClosedByInterruptException ex) {
|
||||
repairFileChannel();
|
||||
throw ex;
|
||||
}
|
||||
this.bufferPosition = position;
|
||||
fillBuffer(position);
|
||||
}
|
||||
if (this.bufferSize <= 0) {
|
||||
return this.bufferSize;
|
||||
@ -200,6 +192,27 @@ class FileChannelDataBlock implements CloseableDataBlock {
|
||||
}
|
||||
}
|
||||
|
||||
private void fillBuffer(long position) throws IOException {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
boolean interrupted = (i != 0) ? Thread.interrupted() : false;
|
||||
try {
|
||||
this.buffer.clear();
|
||||
this.bufferSize = this.fileChannel.read(this.buffer, position);
|
||||
this.bufferPosition = position;
|
||||
return;
|
||||
}
|
||||
catch (ClosedByInterruptException ex) {
|
||||
repairFileChannel();
|
||||
}
|
||||
finally {
|
||||
if (interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ClosedByInterruptException();
|
||||
}
|
||||
|
||||
private void repairFileChannel() throws IOException {
|
||||
if (tracker != null) {
|
||||
tracker.closedFileChannel(this.path, this.fileChannel);
|
||||
|
@ -19,11 +19,9 @@ package org.springframework.boot.loader.zip;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@ -77,25 +75,16 @@ class FileChannelDataBlockTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void readReadsFileWhenAnotherThreadHasBeenInterrupted() throws IOException, InterruptedException {
|
||||
void readReadsFileWhenThreadHasBeenInterrupted() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
|
||||
AtomicReference<IOException> failure = new AtomicReference<>();
|
||||
Thread thread = new Thread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
try {
|
||||
block.read(ByteBuffer.allocate(CONTENT.length), 0);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
failure.set(ex);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertThat(failure.get()).isInstanceOf(ClosedByInterruptException.class);
|
||||
Thread.currentThread().interrupt();
|
||||
assertThat(block.read(buffer, 0)).isEqualTo(6);
|
||||
assertThat(buffer.array()).containsExactly(CONTENT);
|
||||
}
|
||||
finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user