Skip to content

Commit 890a3e7

Browse files
committed
Repair file channel when it's closed by interruption
When an interrupted that calls FileChannel.read, the channel is closed and the read fails with a ClosedByInterruptException. The closure of the channel makes it unusable by other threads. To allow other threads to read from the data block, this commit recreates the FileChannel when a read fails on an interrupted thread with a ClosedByInterruptException. The exception is then rethrown to continue the thread's interruption. Closes gh-38154
1 parent 173e654 commit 890a3e7

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/zip/FileChannelDataBlock.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.nio.ByteBuffer;
21+
import java.nio.channels.ClosedByInterruptException;
2122
import java.nio.channels.ClosedChannelException;
2223
import java.nio.channels.FileChannel;
2324
import java.nio.file.Files;
@@ -179,7 +180,13 @@ int read(ByteBuffer dst, long position) throws IOException {
179180
synchronized (this.lock) {
180181
if (position < this.bufferPosition || position >= this.bufferPosition + this.bufferSize) {
181182
this.buffer.clear();
182-
this.bufferSize = this.fileChannel.read(this.buffer, position);
183+
try {
184+
this.bufferSize = this.fileChannel.read(this.buffer, position);
185+
}
186+
catch (ClosedByInterruptException ex) {
187+
repairFileChannel();
188+
throw ex;
189+
}
183190
this.bufferPosition = position;
184191
}
185192
if (this.bufferSize <= 0) {
@@ -193,6 +200,16 @@ int read(ByteBuffer dst, long position) throws IOException {
193200
}
194201
}
195202

203+
private void repairFileChannel() throws IOException {
204+
if (tracker != null) {
205+
tracker.closedFileChannel(this.path, this.fileChannel);
206+
}
207+
this.fileChannel = FileChannel.open(this.path, StandardOpenOption.READ);
208+
if (tracker != null) {
209+
tracker.openedFileChannel(this.path, this.fileChannel);
210+
}
211+
}
212+
196213
void open() throws IOException {
197214
synchronized (this.lock) {
198215
if (this.referenceCount == 0) {
@@ -231,7 +248,7 @@ void close() throws IOException {
231248

232249
<E extends Exception> void ensureOpen(Supplier<E> exceptionSupplier) throws E {
233250
synchronized (this.lock) {
234-
if (this.referenceCount == 0) {
251+
if (this.referenceCount == 0 || !this.fileChannel.isOpen()) {
235252
throw exceptionSupplier.get();
236253
}
237254
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileChannelDataBlockTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.nio.ByteBuffer;
22+
import java.nio.channels.ClosedByInterruptException;
2223
import java.nio.channels.FileChannel;
2324
import java.nio.file.Files;
2425
import java.nio.file.Path;
26+
import java.util.concurrent.atomic.AtomicReference;
2527

2628
import org.junit.jupiter.api.AfterEach;
2729
import org.junit.jupiter.api.BeforeEach;
@@ -74,6 +76,28 @@ void readReadsFile() throws IOException {
7476
}
7577
}
7678

79+
@Test
80+
void readReadsFileWhenAnotherThreadHasBeenInterrupted() throws IOException, InterruptedException {
81+
try (FileChannelDataBlock block = createAndOpenBlock()) {
82+
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
83+
AtomicReference<IOException> failure = new AtomicReference<>();
84+
Thread thread = new Thread(() -> {
85+
Thread.currentThread().interrupt();
86+
try {
87+
block.read(ByteBuffer.allocate(CONTENT.length), 0);
88+
}
89+
catch (IOException ex) {
90+
failure.set(ex);
91+
}
92+
});
93+
thread.start();
94+
thread.join();
95+
assertThat(failure.get()).isInstanceOf(ClosedByInterruptException.class);
96+
assertThat(block.read(buffer, 0)).isEqualTo(6);
97+
assertThat(buffer.array()).containsExactly(CONTENT);
98+
}
99+
}
100+
77101
@Test
78102
void readDoesNotReadPastEndOfFile() throws IOException {
79103
try (FileChannelDataBlock block = createAndOpenBlock()) {

0 commit comments

Comments
 (0)