diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/zip/FileDataBlock.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/zip/FileDataBlock.java index e94784a561d5..cd0a1da4321c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/zip/FileDataBlock.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/zip/FileDataBlock.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedChannelException; @@ -39,22 +40,22 @@ class FileDataBlock implements CloseableDataBlock { private static final DebugLogger debug = DebugLogger.get(FileDataBlock.class); - static Tracker tracker; + static Tracker tracker = Tracker.NONE; - private final ManagedFileChannel channel; + private final FileAccess fileAccess; private final long offset; private final long size; FileDataBlock(Path path) throws IOException { - this.channel = new ManagedFileChannel(path); + this.fileAccess = new FileAccess(path); this.offset = 0; this.size = Files.size(path); } - FileDataBlock(ManagedFileChannel channel, long offset, long size) { - this.channel = channel; + FileDataBlock(FileAccess fileAccess, long offset, long size) { + this.fileAccess = fileAccess; this.offset = offset; this.size = size; } @@ -79,7 +80,7 @@ public int read(ByteBuffer dst, long pos) throws IOException { originalDestinationLimit = dst.limit(); dst.limit(dst.position() + remaining); } - int result = this.channel.read(dst, this.offset + pos); + int result = this.fileAccess.read(dst, this.offset + pos); if (originalDestinationLimit != -1) { dst.limit(originalDestinationLimit); } @@ -92,7 +93,7 @@ public int read(ByteBuffer dst, long pos) throws IOException { * @throws IOException on I/O error */ void open() throws IOException { - this.channel.open(); + this.fileAccess.open(); } /** @@ -102,7 +103,7 @@ void open() throws IOException { */ @Override public void close() throws IOException { - this.channel.close(); + this.fileAccess.close(); } /** @@ -112,7 +113,7 @@ public void close() throws IOException { * @throws E if the channel is closed */ void ensureOpen(Supplier exceptionSupplier) throws E { - this.channel.ensureOpen(exceptionSupplier); + this.fileAccess.ensureOpen(exceptionSupplier); } /** @@ -145,14 +146,14 @@ FileDataBlock slice(long offset, long size) { if (size < 0 || offset + size > this.size) { throw new IllegalArgumentException("Size must not be negative and must be within bounds"); } - debug.log("Slicing %s at %s with size %s", this.channel, offset, size); - return new FileDataBlock(this.channel, this.offset + offset, size); + debug.log("Slicing %s at %s with size %s", this.fileAccess, offset, size); + return new FileDataBlock(this.fileAccess, this.offset + offset, size); } /** * Manages access to underlying {@link FileChannel}. */ - static class ManagedFileChannel { + static class FileAccess { static final int BUFFER_SIZE = 1024 * 10; @@ -162,6 +163,10 @@ static class ManagedFileChannel { private FileChannel fileChannel; + private boolean fileChannelInterrupted; + + private RandomAccessFile randomAccessFile; + private ByteBuffer buffer; private long bufferPosition = -1; @@ -170,7 +175,7 @@ static class ManagedFileChannel { private final Object lock = new Object(); - ManagedFileChannel(Path path) { + FileAccess(Path path) { if (!Files.isRegularFile(path)) { throw new IllegalArgumentException(path + " must be a regular file"); } @@ -194,34 +199,45 @@ int read(ByteBuffer dst, long position) throws IOException { } 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) { + if (Thread.currentThread().isInterrupted()) { + fillBufferUsingRandomAccessFile(position); + return; + } + try { + if (this.fileChannelInterrupted) { repairFileChannel(); + this.fileChannelInterrupted = false; } - finally { - if (interrupted) { - Thread.currentThread().interrupt(); - } - } + this.buffer.clear(); + this.bufferSize = this.fileChannel.read(this.buffer, position); + this.bufferPosition = position; + } + catch (ClosedByInterruptException ex) { + this.fileChannelInterrupted = true; + fillBufferUsingRandomAccessFile(position); } - throw new ClosedByInterruptException(); } - private void repairFileChannel() throws IOException { - if (tracker != null) { - tracker.closedFileChannel(this.path, this.fileChannel); + private void fillBufferUsingRandomAccessFile(long position) throws IOException { + if (this.randomAccessFile == null) { + this.randomAccessFile = new RandomAccessFile(this.path.toFile(), "r"); + tracker.openedFileChannel(this.path); } - this.fileChannel = FileChannel.open(this.path, StandardOpenOption.READ); - if (tracker != null) { - tracker.openedFileChannel(this.path, this.fileChannel); + byte[] bytes = new byte[BUFFER_SIZE]; + this.randomAccessFile.seek(position); + int len = this.randomAccessFile.read(bytes); + this.buffer.clear(); + if (len > 0) { + this.buffer.put(bytes, 0, len); } + this.bufferSize = len; + this.bufferPosition = position; + } + + private void repairFileChannel() throws IOException { + tracker.closedFileChannel(this.path); + this.fileChannel = FileChannel.open(this.path, StandardOpenOption.READ); + tracker.openedFileChannel(this.path); } void open() throws IOException { @@ -230,9 +246,7 @@ void open() throws IOException { debug.log("Opening '%s'", this.path); this.fileChannel = FileChannel.open(this.path, StandardOpenOption.READ); this.buffer = ByteBuffer.allocateDirect(BUFFER_SIZE); - if (tracker != null) { - tracker.openedFileChannel(this.path, this.fileChannel); - } + tracker.openedFileChannel(this.path); } this.referenceCount++; debug.log("Reference count for '%s' incremented to %s", this.path, this.referenceCount); @@ -251,10 +265,13 @@ void close() throws IOException { this.bufferPosition = -1; this.bufferSize = 0; this.fileChannel.close(); - if (tracker != null) { - tracker.closedFileChannel(this.path, this.fileChannel); - } + tracker.closedFileChannel(this.path); this.fileChannel = null; + if (this.randomAccessFile != null) { + this.randomAccessFile.close(); + tracker.closedFileChannel(this.path); + this.randomAccessFile = null; + } } debug.log("Reference count for '%s' decremented to %s", this.path, this.referenceCount); } @@ -262,7 +279,7 @@ void close() throws IOException { void ensureOpen(Supplier exceptionSupplier) throws E { synchronized (this.lock) { - if (this.referenceCount == 0 || !this.fileChannel.isOpen()) { + if (this.referenceCount == 0) { throw exceptionSupplier.get(); } } @@ -280,9 +297,21 @@ public String toString() { */ interface Tracker { - void openedFileChannel(Path path, FileChannel fileChannel); + Tracker NONE = new Tracker() { + + @Override + public void openedFileChannel(Path path) { + } + + @Override + public void closedFileChannel(Path path) { + } + + }; + + void openedFileChannel(Path path); - void closedFileChannel(Path path, FileChannel fileChannel); + void closedFileChannel(Path path); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java index 40a59c7baae3..8dfc3e564c60 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java @@ -303,7 +303,7 @@ void cleanupFromReleasesResources() throws IOException { Cleanable cleanable = mock(Cleanable.class); given(cleaner.register(any(), action.capture())).willReturn(cleanable); try (NestedJarFile jar = new NestedJarFile(this.file, null, null, false, cleaner)) { - Object channel = Extractors.byName("resources.zipContent.data.channel").apply(jar); + Object channel = Extractors.byName("resources.zipContent.data.fileAccess").apply(jar); assertThat(channel).extracting("referenceCount").isEqualTo(1); action.getValue().run(); assertThat(channel).extracting("referenceCount").isEqualTo(0); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/AssertFileChannelDataBlocksClosedExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/AssertFileChannelDataBlocksClosedExtension.java index bb6b53668187..d302dfcee072 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/AssertFileChannelDataBlocksClosedExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/AssertFileChannelDataBlocksClosedExtension.java @@ -19,7 +19,6 @@ import java.io.Closeable; import java.io.IOException; import java.lang.ref.Cleaner.Cleanable; -import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -52,7 +51,7 @@ public void beforeEach(ExtensionContext context) throws Exception { @Override public void afterEach(ExtensionContext context) throws Exception { tracker.assertAllClosed(); - FileDataBlock.tracker = null; + FileDataBlock.tracker = Tracker.NONE; } private static final class OpenFilesTracker implements Tracker { @@ -64,12 +63,12 @@ private static final class OpenFilesTracker implements Tracker { private final List close = new ArrayList<>(); @Override - public void openedFileChannel(Path path, FileChannel fileChannel) { + public void openedFileChannel(Path path) { this.paths.add(path); } @Override - public void closedFileChannel(Path path, FileChannel fileChannel) { + public void closedFileChannel(Path path) { this.paths.remove(path); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileChannelDataBlockManagedFileChannel.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileChannelDataBlockManagedFileChannel.java index 00c1f480dfc6..293383b9acf2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileChannelDataBlockManagedFileChannel.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileChannelDataBlockManagedFileChannel.java @@ -16,10 +16,10 @@ package org.springframework.boot.loader.zip; -import org.springframework.boot.loader.zip.FileDataBlock.ManagedFileChannel; +import org.springframework.boot.loader.zip.FileDataBlock.FileAccess; /** - * Test access to {@link ManagedFileChannel} details. + * Test access to {@link FileAccess} details. * * @author Phillip Webb */ @@ -28,6 +28,6 @@ public final class FileChannelDataBlockManagedFileChannel { private FileChannelDataBlockManagedFileChannel() { } - public static int BUFFER_SIZE = FileDataBlock.ManagedFileChannel.BUFFER_SIZE; + public static int BUFFER_SIZE = FileDataBlock.FileAccess.BUFFER_SIZE; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileDataBlockTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileDataBlockTests.java index ac27a7c43a5c..09569a461fa4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileDataBlockTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/FileDataBlockTests.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; @@ -55,7 +54,7 @@ void writeTempFile() throws IOException { @AfterEach void resetTracker() { - FileDataBlock.tracker = null; + FileDataBlock.tracker = Tracker.NONE; } @Test @@ -160,25 +159,25 @@ void openAndCloseHandleReferenceCounting() throws IOException { TestTracker tracker = new TestTracker(); FileDataBlock.tracker = tracker; FileDataBlock block = createBlock(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(0); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(0); tracker.assertOpenCloseCounts(0, 0); block.open(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(1); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(1); tracker.assertOpenCloseCounts(1, 0); block.open(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(2); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(2); tracker.assertOpenCloseCounts(1, 0); block.close(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(1); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(1); tracker.assertOpenCloseCounts(1, 0); block.close(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(0); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(0); tracker.assertOpenCloseCounts(1, 1); block.open(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(1); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(1); tracker.assertOpenCloseCounts(2, 1); block.close(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(0); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(0); tracker.assertOpenCloseCounts(2, 2); } @@ -188,31 +187,31 @@ void openAndCloseSliceHandleReferenceCounting() throws IOException { FileDataBlock.tracker = tracker; FileDataBlock block = createBlock(); FileDataBlock slice = block.slice(1, 4); - assertThat(block).extracting("channel.referenceCount").isEqualTo(0); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(0); tracker.assertOpenCloseCounts(0, 0); block.open(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(1); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(1); tracker.assertOpenCloseCounts(1, 0); slice.open(); - assertThat(slice).extracting("channel.referenceCount").isEqualTo(2); + assertThat(slice).extracting("fileAccess.referenceCount").isEqualTo(2); tracker.assertOpenCloseCounts(1, 0); slice.open(); - assertThat(slice).extracting("channel.referenceCount").isEqualTo(3); + assertThat(slice).extracting("fileAccess.referenceCount").isEqualTo(3); tracker.assertOpenCloseCounts(1, 0); slice.close(); - assertThat(slice).extracting("channel.referenceCount").isEqualTo(2); + assertThat(slice).extracting("fileAccess.referenceCount").isEqualTo(2); tracker.assertOpenCloseCounts(1, 0); slice.close(); - assertThat(slice).extracting("channel.referenceCount").isEqualTo(1); + assertThat(slice).extracting("fileAccess.referenceCount").isEqualTo(1); tracker.assertOpenCloseCounts(1, 0); block.close(); - assertThat(block).extracting("channel.referenceCount").isEqualTo(0); + assertThat(block).extracting("fileAccess.referenceCount").isEqualTo(0); tracker.assertOpenCloseCounts(1, 1); slice.open(); - assertThat(slice).extracting("channel.referenceCount").isEqualTo(1); + assertThat(slice).extracting("fileAccess.referenceCount").isEqualTo(1); tracker.assertOpenCloseCounts(2, 1); slice.close(); - assertThat(slice).extracting("channel.referenceCount").isEqualTo(0); + assertThat(slice).extracting("fileAccess.referenceCount").isEqualTo(0); tracker.assertOpenCloseCounts(2, 2); } @@ -233,12 +232,12 @@ static class TestTracker implements Tracker { private int closeCount; @Override - public void openedFileChannel(Path path, FileChannel fileChannel) { + public void openedFileChannel(Path path) { this.openCount++; } @Override - public void closedFileChannel(Path path, FileChannel fileChannel) { + public void closedFileChannel(Path path) { this.closeCount++; }