Skip to content
Open
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
77 changes: 77 additions & 0 deletions openpdf-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,83 @@
<unpackBundle>true</unpackBundle>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--
MappedFileReleaseTest only makes sense against the packaged multi-release jar (see the
multi-release-java22 profile below): surefire runs against target/classes, where the JVM ignores
META-INF/versions, so it would exercise the Java 21 code path and fail on Windows without System.gc().
-->
<excludes>
<exclude>**/MappedFileReleaseTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

<profiles>
<!--
Multi-release jar: the classes in src/main/java22 are compiled into META-INF/versions/22 and replace their
Java 21 counterparts when OpenPDF runs on Java 22 or later. They use the Foreign Function & Memory API
(JEP 454, final in Java 22) to unmap memory-mapped files deterministically.
The profile is activated automatically when building with JDK 22 or later. Releases must be built with
JDK 22 or later, otherwise the jar contains the Java 21 classes only.
-->
<profile>
<id>multi-release-java22</id>
<activation>
<jdk>[22,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile-java22</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>22</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java22</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<!--
Surefire tests target/classes, where the JVM ignores META-INF/versions. Failsafe runs against the
packaged jar, so these tests exercise the Java 22 classes.
-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<id>multi-release-java22-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<classesDirectory>${project.build.directory}/${project.build.finalName}.jar</classesDirectory>
<includes>
<include>**/LongMappedByteBufferTest.java</include>
<include>**/MappedFileReleaseTest.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,19 @@ public MappedRandomAccessFile(String filename, String mode)
private void init(FileChannel channel, FileChannel.MapMode mapMode)
throws IOException {


this.channel = channel;
this.mappedByteBuffer = new LongMappedByteBuffer(channel, mapMode);

mappedByteBuffer.load();
try {
this.mappedByteBuffer = new LongMappedByteBuffer(channel, mapMode);
mappedByteBuffer.load();
} catch (IOException | RuntimeException e) {
// don't leak the file handle (and the mapping) if the file cannot be mapped
try {
close();
} catch (IOException suppressed) {
e.addSuppressed(suppressed);
}
throw e;
}
}

/**
Expand Down Expand Up @@ -194,17 +202,26 @@ public long length() {
}

/**
* Cleans the mapped bytebuffer and closes the channel
* Cleans the mapped bytebuffer and closes the channel.
* <p>
* On Java 22 and later, the mapping is released immediately, so the file can be deleted or moved right after this
* call, also on Windows. On Java 21, the mapping is released when the buffer is garbage collected.
*
* @throws IOException on error
* @see java.io.RandomAccessFile#close()
*/
public void close() throws IOException {
mappedByteBuffer = null;
if (channel != null) {
channel.close();
try {
if (mappedByteBuffer != null) {
mappedByteBuffer.close();
}
} finally {
mappedByteBuffer = null;
if (channel != null) {
channel.close();
}
channel = null;
}
channel = null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,20 @@
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;


/**
* A utility class that allows random access to files larger than 2GB by internally
* mapping them into multiple {@link MappedByteBuffer} chunks of up to 2GB each.
* <p>
* This is the Java 21 implementation. On Java 22 and later, the multi-release variant of this class (compiled from
* {@code src/main/java22}) is used instead. It maps the file with the Foreign Function &amp; Memory API, so that
* {@link #close()} releases the mapping immediately.
*
* @since 2.0.4
*/
public class LongMappedByteBuffer {
public class LongMappedByteBuffer implements AutoCloseable {

private static final long CHUNK_SIZE = Integer.MAX_VALUE; // 2 GB
private final MappedByteBuffer[] chunks;
Expand Down Expand Up @@ -233,4 +238,18 @@ public void force() {
chunk.force();
}
}

/**
* Releases the references to the mapped chunks. The buffer must not be used after it has been closed.
* <p>
* Java 21 offers no supported way to unmap a {@link MappedByteBuffer} explicitly, so the mapping itself (and with
* it the lock on the file on Windows) is only released once the chunks have been garbage collected. On Java 22
* and later, the multi-release variant of this class releases the mapping immediately.
*
* @since 3.0.6
*/
@Override
public void close() {
Arrays.fill(chunks, null);
}
}
Loading