Skip to content

Commit

Permalink
8281412: MemorySegment::map should live in FileChannel
Browse files Browse the repository at this point in the history
Reviewed-by: mcimadamore
  • Loading branch information
Julia Boes committed Mar 11, 2022
1 parent cad5d16 commit c28366d
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 165 deletions.
60 changes: 5 additions & 55 deletions src/java.base/share/classes/java/lang/foreign/MemorySegment.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,18 @@

package java.lang.foreign;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Array;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Spliterator;
import java.util.stream.Stream;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.HeapMemorySegmentImpl;
import jdk.internal.foreign.MappedMemorySegmentImpl;
import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.SharedUtils;
Expand Down Expand Up @@ -89,8 +86,8 @@
* <h2>Mapping memory segments from files</h2>
*
* It is also possible to obtain a native memory segment backed by a memory-mapped file using the factory method
* {@link MemorySegment#mapFile(Path, long, long, FileChannel.MapMode, MemorySession)}. Such native memory segments are
* called <em>mapped memory segments</em>; mapped memory segments are associated with an underlying file descriptor.
* {@link FileChannel#map(FileChannel.MapMode, long, long, MemorySession)}. Such native memory segments are called
* <em>mapped memory segments</em>; mapped memory segments are associated with an underlying file descriptor.
* <p>
* Contents of mapped memory segments can be {@linkplain #force() persisted} and {@linkplain #load() loaded} to and from the underlying file;
* these capabilities are suitable replacements for some capabilities in the {@link java.nio.MappedByteBuffer} class.
Expand Down Expand Up @@ -384,7 +381,7 @@ default MemorySegment asSlice(long offset) {

/**
* Returns {@code true} if this segment is a mapped segment. A mapped memory segment is
* created using the {@link #mapFile(Path, long, long, FileChannel.MapMode, MemorySession)} factory, or a buffer segment
* created using the {@link FileChannel#map(FileChannel.MapMode, long, long, MemorySession)} factory, or a buffer segment
* derived from a {@link java.nio.MappedByteBuffer} using the {@link #ofByteBuffer(ByteBuffer)} factory.
* @return {@code true} if this segment is a mapped segment.
*/
Expand Down Expand Up @@ -953,53 +950,6 @@ static MemorySegment allocateNative(long bytesSize, long alignmentBytes, MemoryS
return NativeMemorySegmentImpl.makeNativeSegment(bytesSize, alignmentBytes, session);
}

/**
* Creates a new mapped memory segment that models a memory-mapped region of a file from a given path,
* size, offset and memory session.
* <p>
* If the specified mapping mode is {@linkplain FileChannel.MapMode#READ_ONLY READ_ONLY}, the resulting segment
* will be read-only (see {@link #isReadOnly()}).
* <p>
* The content of a mapped memory segment can change at any time, for example
* if the content of the corresponding region of the mapped file is changed by
* this (or another) program. Whether such changes occur, and when they
* occur, is operating-system dependent and therefore unspecified.
* <p>
* All or part of a mapped memory segment may become
* inaccessible at any time, for example if the backing mapped file is truncated. An
* attempt to access an inaccessible region of a mapped memory segment will not
* change the segment's content and will cause an unspecified exception to be
* thrown either at the time of the access or at some later time. It is
* therefore strongly recommended that appropriate precautions be taken to
* avoid the manipulation of a mapped file by this (or another) program, except to read or write
* the file's content.
*
* @implNote When obtaining a mapped segment from a newly created file, the initialization state of the contents of the block
* of mapped memory associated with the returned mapped memory segment is unspecified and should not be relied upon.
*
* @param path the path to the file to memory map.
* @param bytesOffset the offset (expressed in bytes) within the file at which the mapped segment is to start.
* @param bytesSize the size (in bytes) of the mapped memory backing the memory segment.
* @param mapMode a file mapping mode, see {@link FileChannel#map(FileChannel.MapMode, long, long)}; the mapping mode
* might affect the behavior of the returned memory mapped segment (see {@link #force()}).
* @param session the segment memory session.
* @return a new mapped memory segment.
* @throws IllegalArgumentException if {@code bytesOffset < 0}, {@code bytesSize < 0}, or if {@code path} is not associated
* with the default file system.
* @throws IllegalStateException if {@code session} is not {@linkplain MemorySession#isAlive() alive}, or if access occurs from
* a thread other than the thread {@linkplain MemorySession#ownerThread() owning} {@code session}.
* @throws UnsupportedOperationException if an unsupported map mode is specified.
* @throws IOException if the specified path does not point to an existing file, or if some other I/O error occurs.
* @throws SecurityException If a security manager is installed, and it denies an unspecified permission required by the implementation.
* In the case of the default provider, the {@link SecurityManager#checkRead(String)} method is invoked to check
* read access if the file is opened for reading. The {@link SecurityManager#checkWrite(String)} method is invoked to check
* write access if the file is opened for writing.
*/
static MemorySegment mapFile(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode, MemorySession session) throws IOException {
Objects.requireNonNull(session);
return MappedMemorySegmentImpl.makeMappedSegment(path, bytesOffset, bytesSize, mapMode, session);
}

/**
* Performs a bulk copy from source segment to destination segment. More specifically, the bytes at offset
* {@code srcOffset} through {@code srcOffset + bytes - 1} in the source segment are copied into the destination
Expand All @@ -1012,7 +962,7 @@ static MemorySegment mapFile(Path path, long bytesOffset, long bytesSize, FileCh
* <p>
* The result of a bulk copy is unspecified if, in the uncommon case, the source segment and the destination segment
* do not overlap, but refer to overlapping regions of the same backing storage using different addresses.
* For example, this may occur if the same file is {@linkplain MemorySegment#mapFile mapped} to two segments.
* For example, this may occur if the same file is {@linkplain FileChannel#map mapped} to two segments.
* <p>
* Calling this method is equivalent to the following code:
* {@snippet lang=java :
Expand Down Expand Up @@ -1055,7 +1005,7 @@ static void copy(MemorySegment srcSegment, long srcOffset, MemorySegment dstSegm
* <p>
* The result of a bulk copy is unspecified if, in the uncommon case, the source segment and the destination segment
* do not overlap, but refer to overlapping regions of the same backing storage using different addresses.
* For example, this may occur if the same file is {@linkplain MemorySegment#mapFile mapped} to two segments.
* For example, this may occur if the same file is {@linkplain FileChannel#map mapped} to two segments.
* @param srcSegment the source segment.
* @param srcElementLayout the element layout associated with the source segment.
* @param srcOffset the starting offset, in bytes, of the source segment.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
* <ul>
* <li>closing the memory session associated with a {@linkplain MemorySegment#allocateNative(long, long, MemorySession) native memory segment}
* results in <em>freeing</em> the native memory associated with it;</li>
* <li>closing the memory session associated with a {@linkplain MemorySegment#mapFile(Path, long, long, FileChannel.MapMode, MemorySession) mapped memory segment}
* <li>closing the memory session associated with a {@linkplain FileChannel#map(FileChannel.MapMode, long, long, MemorySession) mapped memory segment}
* results in the backing memory-mapped file to be unmapped;</li>
* <li>closing the memory session associated with an {@linkplain CLinker#upcallStub(MethodHandle, FunctionDescriptor, MemorySession) upcall stub}
* results in releasing the stub;</li>
Expand Down
92 changes: 89 additions & 3 deletions src/java.base/share/classes/java/nio/channels/FileChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
package java.nio.channels;

import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.spi.AbstractInterruptibleChannel;
Expand All @@ -38,6 +40,7 @@
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import jdk.internal.javac.PreviewFeature;

/**
* A channel for reading, writing, mapping, and manipulating a file.
Expand Down Expand Up @@ -943,13 +946,13 @@ public String toString() {
*
* @throws NonReadableChannelException
* If the {@code mode} is {@link MapMode#READ_ONLY READ_ONLY} or
* an implementation specific map mode requiring read access
* an implementation specific map mode requiring read access,
* but this channel was not opened for reading
*
* @throws NonWritableChannelException
* If the {@code mode} is {@link MapMode#READ_WRITE READ_WRITE}.
* If the {@code mode} is {@link MapMode#READ_WRITE READ_WRITE},
* {@link MapMode#PRIVATE PRIVATE} or an implementation specific
* map mode requiring write access but this channel was not
* map mode requiring write access, but this channel was not
* opened for both reading and writing
*
* @throws IllegalArgumentException
Expand All @@ -967,6 +970,89 @@ public String toString() {
public abstract MappedByteBuffer map(MapMode mode, long position, long size)
throws IOException;

/**
* Maps a region of this channel's file into a new mapped memory segment,
* with a given offset, size and memory session.
*
* <p> If the specified mapping mode is
* {@linkplain FileChannel.MapMode#READ_ONLY READ_ONLY}, the resulting
* segment will be read-only (see {@link MemorySegment#isReadOnly()}).
*
* <p> The content of a mapped memory segment can change at any time, for
* example if the content of the corresponding region of the mapped file is
* changed by this (or another) program. Whether such changes occur, and
* when they occur, is operating-system dependent and therefore unspecified.
*
* <p> All or part of a mapped memory segment may become inaccessible at any
* time, for example if the backing mapped file is truncated. An attempt to
* access an inaccessible region of a mapped memory segment will not change
* the segment's content and will cause an unspecified exception to be
* thrown either at the time of the access or at some later time. It is
* therefore strongly recommended that appropriate precautions be taken to
* avoid the manipulation of a mapped file by this (or another) program,
* except to read or write the file's content.
*
* @implNote When obtaining a mapped segment from a newly created file
* channel, the initialization state of the contents of the block
* of mapped memory associated with the returned mapped memory
* segment is unspecified and should not be relied upon.
*
* @param mode
* The file mapping mode, see
* {@link FileChannel#map(FileChannel.MapMode, long, long)};
* the mapping mode might affect the behavior of the returned memory
* mapped segment (see {@link MemorySegment#force()}).
*
* @param offset
* The offset (expressed in bytes) within the file at which the
* mapped segment is to start.
*
* @param size
* The size (in bytes) of the mapped memory backing the memory
* segment.
* @param session
* The segment memory session.
*
* @return A new mapped memory segment.
*
* @throws IllegalArgumentException
* If {@code offset < 0}, {@code size < 0} or
* {@code offset + size < 0}.
*
* @throws IllegalStateException
* If the {@code session} is not
* {@linkplain MemorySession#isAlive() alive}, or if access occurs
* from a thread other than the thread
* {@linkplain MemorySession#ownerThread() owning} the
* {@code session}.
*
* @throws NonReadableChannelException
* If the {@code mode} is {@link MapMode#READ_ONLY READ_ONLY} or
* an implementation specific map mode requiring read access,
* but this channel was not opened for reading.
*
* @throws NonWritableChannelException
* If the {@code mode} is {@link MapMode#READ_WRITE READ_WRITE},
* {@link MapMode#PRIVATE PRIVATE} or an implementation specific
* map mode requiring write access, but this channel was not
* opened for both reading and writing.
*
* @throws IOException
* If some other I/O error occurs.
*
* @throws UnsupportedOperationException
* If an unsupported map mode is specified.
*
* @since 19
*/
@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN)
public MemorySegment map(MapMode mode, long offset, long size,
MemorySession session)
throws IOException, UnsupportedOperationException
{
throw new UnsupportedOperationException();
}

// -- Locks --

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,11 @@

package jdk.internal.foreign;

import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import jdk.internal.access.foreign.UnmapperProxy;
import jdk.internal.misc.ExtendedMapMode;
import jdk.internal.misc.ScopedMemoryAccess;
import sun.nio.ch.FileChannelImpl;

/**
* Implementation for a mapped memory segments. A mapped memory segment is a native memory segment, which
Expand All @@ -53,7 +43,7 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {

static ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();

MappedMemorySegmentImpl(long min, UnmapperProxy unmapper, long length, int mask, MemorySession session) {
public MappedMemorySegmentImpl(long min, UnmapperProxy unmapper, long length, int mask, MemorySession session) {
super(min, length, mask, session);
this.unmapper = unmapper;
}
Expand All @@ -71,7 +61,6 @@ MappedMemorySegmentImpl dup(long offset, long size, int mask, MemorySession sess

// mapped segment methods


@Override
public MappedMemorySegmentImpl asSlice(long offset, long newSize) {
return (MappedMemorySegmentImpl)super.asSlice(offset, newSize);
Expand Down Expand Up @@ -104,56 +93,7 @@ public void force() {
SCOPED_MEMORY_ACCESS.force(sessionImpl(), unmapper.fileDescriptor(), min, unmapper.isSync(), 0, length);
}

// factories

public static MemorySegment makeMappedSegment(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode, MemorySession session) throws IOException {
Objects.requireNonNull(path);
Objects.requireNonNull(mapMode);
MemorySessionImpl sessionImpl = MemorySessionImpl.toSessionImpl(session);
sessionImpl.checkValidStateSlow();
if (bytesSize < 0) throw new IllegalArgumentException("Requested bytes size must be >= 0.");
if (bytesOffset < 0) throw new IllegalArgumentException("Requested bytes offset must be >= 0.");
FileSystem fs = path.getFileSystem();
if (fs != FileSystems.getDefault() ||
fs.getClass().getModule() != Object.class.getModule()) {
throw new IllegalArgumentException("Unsupported file system");
}
try (FileChannel channelImpl = FileChannel.open(path, openOptions(mapMode))) {
UnmapperProxy unmapperProxy = ((FileChannelImpl)channelImpl).mapInternal(mapMode, bytesOffset, bytesSize);
int modes = DEFAULT_MODES;
if (mapMode == FileChannel.MapMode.READ_ONLY) {
modes |= READ_ONLY;
}
if (unmapperProxy != null) {
AbstractMemorySegmentImpl segment = new MappedMemorySegmentImpl(unmapperProxy.address(), unmapperProxy, bytesSize,
modes, session);
sessionImpl.addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() {
@Override
public void cleanup() {
unmapperProxy.unmap();
}
});
return segment;
} else {
return new EmptyMappedMemorySegmentImpl(modes, session);
}
}
}

private static OpenOption[] openOptions(FileChannel.MapMode mapMode) {
if (mapMode == FileChannel.MapMode.READ_ONLY ||
mapMode == ExtendedMapMode.READ_ONLY_SYNC) {
return new OpenOption[] { StandardOpenOption.READ };
} else if (mapMode == FileChannel.MapMode.READ_WRITE ||
mapMode == FileChannel.MapMode.PRIVATE ||
mapMode == ExtendedMapMode.READ_WRITE_SYNC) {
return new OpenOption[] { StandardOpenOption.READ, StandardOpenOption.WRITE };
} else {
throw new UnsupportedOperationException("Unsupported map mode: " + mapMode);
}
}

static class EmptyMappedMemorySegmentImpl extends MappedMemorySegmentImpl {
public static class EmptyMappedMemorySegmentImpl extends MappedMemorySegmentImpl {

public EmptyMappedMemorySegmentImpl(int modes, MemorySession session) {
super(0, null, 0, modes, session);
Expand All @@ -178,5 +118,5 @@ public boolean isLoaded() {
public void force() {
// do nothing
}
};
}
}
Loading

0 comments on commit c28366d

Please sign in to comment.