Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

package org.apache.commons.io.input;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.List;

import org.apache.commons.io.Buffers;

Expand Down Expand Up @@ -91,14 +93,19 @@ static void clean(final ByteBuffer buffer) {
try {
if (buffer.isDirect()) {
Buffers.clearWritable(buffer);
INSTANCE.clean(buffer);
if (INSTANCE != null) {
INSTANCE.clean(buffer);
}
}
} catch (final Exception e) {
throw new IllegalStateException("Failed to clean direct buffer.", e);
}
}

private static Cleaner getCleaner() {
static Cleaner getCleaner() {
if (unsafeMemoryAccessDeprecated()) {
return null;
}
try {
return new Java8Cleaner();
} catch (final Exception e) {
Expand All @@ -110,6 +117,17 @@ private static Cleaner getCleaner() {
}
}

private static boolean unsafeMemoryAccessDeprecated() {
final int version;
try {
version = Integer.parseInt(System.getProperty("java.specification.version"));
} catch (final RuntimeException e) {
return false;
}
// see https://openjdk.org/jeps/471
return version >= 23;
}

/**
* Tests if were able to load a suitable cleaner for the current JVM. Attempting to call {@code ByteBufferCleaner#clean(ByteBuffer)} when this method
* returns false will result in an exception.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@

package org.apache.commons.io.input;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;

import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -74,12 +78,13 @@ void testBuilderGet() {
*/
@Test
void testCleanCalledOnlyOnce() throws Exception {
final boolean expectedCleanOnClose = !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_23);
try (BufferedFileChannelInputStream stream = BufferedFileChannelInputStream.builder().setPath(InputPath).get()) {
assertFalse(stream.isClean());
stream.close();
assertTrue(stream.isClean());
assertEquals(stream.isClean(), expectedCleanOnClose);
stream.close();
assertTrue(stream.isClean());
assertEquals(stream.isClean(), expectedCleanOnClose);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@
*/
package org.apache.commons.io.input;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.condition.JRE.JAVA_23;

import java.nio.ByteBuffer;

import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;

/**
* Tests {@code ByteBufferCleaner}.
Expand All @@ -30,24 +36,55 @@ class ByteBufferCleanerTest {

@Test
void testCleanEmpty() {
final ByteBuffer buffer = ByteBuffer.allocateDirect(10);
final ByteBuffer buffer = ByteBuffer.allocateDirect(8);
// There is no way verify that the buffer has been cleaned up, we are just verifying that
// clean() doesn't blow up
ByteBufferCleaner.clean(buffer);
verifyCleared(buffer);
}

@Test
void testCleanFull() {
final ByteBuffer buffer = ByteBuffer.allocateDirect(10);
buffer.put(RandomUtils.insecure().randomBytes(10), 0, 10);
final ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.putLong(Long.MAX_VALUE);
verifyUncleared(buffer);
// There is no way verify that the buffer has been cleaned up, we are just verifying that
// clean() doesn't blow up
ByteBufferCleaner.clean(buffer);
verifyCleared(buffer);
}

@Test
void testCleanNonDirectBuffer() {
assertDoesNotThrow(() -> ByteBufferCleaner.clean(ByteBuffer.allocate(10)));
}

@Test
@EnabledForJreRange(max = JAVA_23)
void testCleanNullBuffer() {
assertThrows(IllegalStateException.class, () -> ByteBufferCleaner.clean(null));
}

@Test
@EnabledForJreRange(max = JAVA_23)
void testSupported() {
assertTrue(ByteBufferCleaner.isSupported(), "ByteBufferCleaner does not work on this platform, please investigate and fix");
}

@Test
@EnabledForJreRange(min = JAVA_23)
void testUnsupportedByDefaultOnJava23() {
assertNull(ByteBufferCleaner.getCleaner());
assertFalse(ByteBufferCleaner.isSupported(), "ByteBufferCleaner does not work on this platform, please investigate and fix");
}

private void verifyUncleared(final ByteBuffer buffer) {
buffer.flip();
assertEquals(Long.MAX_VALUE, buffer.getLong());
buffer.flip();
}

private void verifyCleared(final ByteBuffer buffer) {
assertEquals(0, buffer.getLong());
}
}
Loading