Skip to content

Commit 4570048

Browse files
authored
Clean BufferedFileChannelInputStream's direct ByteBuffer once(#854)
The buffer is now cleaned only on the first close() call.
1 parent 7a7e8a5 commit 4570048

2 files changed

Lines changed: 31 additions & 15 deletions

File tree

src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public static Builder builder() {
137137

138138
private final ByteBuffer byteBuffer;
139139

140+
private boolean clean;
141+
140142
private final FileChannel fileChannel;
141143

142144
@SuppressWarnings("resource")
@@ -209,26 +211,18 @@ public synchronized int available() throws IOException {
209211
* disposed buffer. However, neither the bytes allocated to direct buffers nor file descriptors opened for memory-mapped buffers put pressure on the garbage
210212
* collector. Waiting for garbage collection may lead to the depletion of off-heap memory or huge numbers of open files. There's unfortunately no standard
211213
* API to manually dispose of these kinds of buffers.
212-
*
213-
* @param buffer the buffer to clean.
214-
*/
215-
private void clean(final ByteBuffer buffer) {
216-
if (buffer.isDirect()) {
217-
cleanDirectBuffer(buffer);
218-
}
219-
}
220-
221-
/**
214+
* <p>
222215
* In Java 8, the type of {@code sun.nio.ch.DirectBuffer.cleaner()} was {@code sun.misc.Cleaner}, and it was possible to access the method
223216
* {@code sun.misc.Cleaner.clean()} to invoke it. The type changed to {@code jdk.internal.ref.Cleaner} in later JDKs, and the {@code clean()} method is not
224217
* accessible even with reflection. However {@code sun.misc.Unsafe} added an {@code invokeCleaner()} method in JDK 9+ and this is still accessible with
225218
* reflection.
226-
*
227-
* @param buffer the buffer to clean. must be a DirectBuffer.
219+
* </p>
220+
* @param buffer the buffer to clean.
228221
*/
229-
private void cleanDirectBuffer(final ByteBuffer buffer) {
230-
if (ByteBufferCleaner.isSupported()) {
222+
private void clean(final ByteBuffer buffer) {
223+
if (!clean && buffer.isDirect() && ByteBufferCleaner.isSupported()) {
231224
ByteBufferCleaner.clean(buffer);
225+
clean = true;
232226
}
233227
}
234228

@@ -241,6 +235,10 @@ public synchronized void close() throws IOException {
241235
}
242236
}
243237

238+
boolean isClean() {
239+
return clean;
240+
}
241+
244242
@Override
245243
public synchronized int read() throws IOException {
246244
if (!refill()) {
@@ -301,6 +299,7 @@ public synchronized long skip(final long n) throws IOException {
301299
return skippedFromBuffer + skipFromFileChannel(toSkipFromFileChannel);
302300
}
303301

302+
304303
private long skipFromFileChannel(final long n) throws IOException {
305304
final long currentFilePosition = fileChannel.position();
306305
final long size = fileChannel.size();

src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
1718
package org.apache.commons.io.input;
1819

20+
import static org.junit.jupiter.api.Assertions.assertFalse;
1921
import static org.junit.jupiter.api.Assertions.assertThrows;
2022
import static org.junit.jupiter.api.Assertions.assertTrue;
2123

@@ -65,12 +67,27 @@ void testBuilderGet() {
6567
assertThrows(IllegalStateException.class, () -> BufferedFileChannelInputStream.builder().get());
6668
}
6769

70+
/**
71+
* Tests that the {@code clean(ByteBuffer)} method only performs buffer cleaning once, even when {@link BufferedFileChannelInputStream#close()} is invoked
72+
* multiple times. The private {@code clean} boolean field is inspected via reflection: it must be {@code false} before any close, {@code true} after the
73+
* first close, and remain {@code true} after a second close, confirming that {@code ByteBufferCleaner.clean()} is never called more than once.
74+
*/
75+
@Test
76+
void testCleanCalledOnlyOnce() throws Exception {
77+
try (BufferedFileChannelInputStream stream = BufferedFileChannelInputStream.builder().setPath(InputPath).get()) {
78+
assertFalse(stream.isClean());
79+
stream.close();
80+
assertTrue(stream.isClean());
81+
stream.close();
82+
assertTrue(stream.isClean());
83+
}
84+
}
85+
6886
@Test
6987
void testReadAfterClose() throws Exception {
7088
for (final InputStream inputStream : inputStreams) {
7189
inputStream.close();
7290
assertThrows(IOException.class, inputStream::read);
7391
}
7492
}
75-
7693
}

0 commit comments

Comments
 (0)