8221696: MappedByteBuffer.force method to specify range
Summary: Overload MappedByteBuffer.force to accept index and length arguments
Reviewed-by: dfuchs, alanb, bpb
--- a/src/java.base/share/classes/java/nio/MappedByteBuffer.java Mon May 13 17:31:23 2019 +0100
+++ b/src/java.base/share/classes/java/nio/MappedByteBuffer.java Thu Apr 25 17:27:37 2019 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
import java.io.FileDescriptor;
import java.lang.ref.Reference;
+import java.util.Objects;
import jdk.internal.misc.Unsafe;
@@ -91,20 +92,52 @@
this.fd = null;
}
- // Returns the distance (in bytes) of the buffer from the page aligned address
- // of the mapping. Computed each time to avoid storing in every direct buffer.
+ // Returns the distance (in bytes) of the buffer start from the
+ // largest page aligned address of the mapping less than or equal
+ // to the start address.
private long mappingOffset() {
+ return mappingOffset(0);
+ }
+
+ // Returns the distance (in bytes) of the buffer element
+ // identified by index from the largest page aligned address of
+ // the mapping less than or equal to the element address. Computed
+ // each time to avoid storing in every direct buffer.
+ private long mappingOffset(int index) {
int ps = Bits.pageSize();
- long offset = address % ps;
- return (offset >= 0) ? offset : (ps + offset);
+ long indexAddress = address + index;
+ long baseAddress = (indexAddress & ~(ps-1));
+ return indexAddress - baseAddress;
}
+ // Given an offset previously obtained from calling
+ // mappingOffset() returns the largest page aligned address of the
+ // mapping less than or equal to the buffer start address.
private long mappingAddress(long mappingOffset) {
- return address - mappingOffset;
+ return mappingAddress(mappingOffset, 0);
}
+ // Given an offset previously otained from calling
+ // mappingOffset(index) returns the largest page aligned address
+ // of the mapping less than or equal to the address of the buffer
+ // element identified by index.
+ private long mappingAddress(long mappingOffset, long index) {
+ long indexAddress = address + index;
+ return indexAddress - mappingOffset;
+ }
+
+ // given a mappingOffset previously otained from calling
+ // mappingOffset() return that offset added to the buffer
+ // capacity.
private long mappingLength(long mappingOffset) {
- return (long)capacity() + mappingOffset;
+ return mappingLength(mappingOffset, (long)capacity());
+ }
+
+ // given a mappingOffset previously otained from calling
+ // mappingOffset(index) return that offset added to the supplied
+ // length.
+ private long mappingLength(long mappingOffset, long length) {
+ return length + mappingOffset;
}
/**
@@ -212,6 +245,58 @@
return this;
}
+ /**
+ * Forces any changes made to a region of this buffer's content to
+ * be written to the storage device containing the mapped
+ * file. The region starts at the given {@code index} in this
+ * buffer and is {@code length} bytes.
+ *
+ * <p> If the file mapped into this buffer resides on a local
+ * storage device then when this method returns it is guaranteed
+ * that all changes made to the selected region buffer since it
+ * was created, or since this method was last invoked, will have
+ * been written to that device. The force operation is free to
+ * write bytes that lie outside the specified region, for example
+ * to ensure that data blocks of some device-specific granularity
+ * are transferred in their entirety.
+ *
+ * <p> If the file does not reside on a local device then no such
+ * guarantee is made.
+ *
+ * <p> If this buffer was not mapped in read/write mode ({@link
+ * java.nio.channels.FileChannel.MapMode#READ_WRITE}) then
+ * invoking this method has no effect. </p>
+ *
+ * @param index
+ * The index of the first byte in the buffer region that is
+ * to be written back to storage; must be non-negative
+ * and less than limit()
+ *
+ * @param length
+ * The length of the region in bytes; must be non-negative
+ * and no larger than limit() - index
+ *
+ * @throws IndexOutOfBoundsException
+ * if the preconditions on the index and length do not
+ * hold.
+ *
+ * @return This buffer
+ *
+ * @since 13
+ */
+ public final MappedByteBuffer force(int index, int length) {
+ if (fd == null) {
+ return this;
+ }
+ if ((address != 0) && (limit() != 0)) {
+ // check inputs
+ Objects.checkFromIndexSize(index, length, limit());
+ long offset = mappingOffset(index);
+ force0(fd, mappingAddress(offset, index), mappingLength(offset, length));
+ }
+ return this;
+ }
+
private native boolean isLoaded0(long address, long length, int pageCount);
private native void load0(long address, long length);
private native void force0(FileDescriptor fd, long address, long length);
--- a/test/jdk/java/nio/channels/FileChannel/MapTest.java Mon May 13 17:31:23 2019 +0100
+++ b/test/jdk/java/nio/channels/FileChannel/MapTest.java Thu Apr 25 17:27:37 2019 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -67,6 +67,8 @@
out.println("Write: OK");
testHighOffset();
out.println("High offset: OK");
+ testForce();
+ out.println("Force: OK");
testExceptions();
out.println("Exceptions: OK");
} finally {
@@ -189,6 +191,53 @@
}
/**
+ * Maps blah file, writes some data and forcing writeback of
+ * the data exercising various valid and invalid writeback ranges.
+ */
+ private static void testForce() throws Exception {
+ for (int x=0; x<100; x++) {
+ try (RandomAccessFile raf = new RandomAccessFile(blah, "rw")) {
+ FileChannel fc = raf.getChannel();
+ final int BLOCK_SIZE = 64;
+ final int BLOCK_COUNT = (4096 * 2)/ BLOCK_SIZE;
+ int offset = 0;
+ MappedByteBuffer b = fc.map(MapMode.READ_WRITE,
+ 0, BLOCK_SIZE * (BLOCK_COUNT + 1));
+
+ for (int blocks = 0; blocks < BLOCK_COUNT; blocks++) {
+ for (int i = 0; i < BLOCK_SIZE; i++) {
+ b.put(offset + i, (byte)('0' + i));
+ }
+ b.force(offset, BLOCK_SIZE);
+ offset += BLOCK_SIZE;
+ }
+
+ Exception exc = null;
+ try {
+ // start and end are out of range
+ b.force(offset + BLOCK_SIZE, BLOCK_SIZE);
+ } catch (IndexOutOfBoundsException e) {
+ exc = e;
+ }
+ if (exc == null) {
+ throw new RuntimeException("expected Exception for force beyond buffer extent");
+ }
+
+ exc = null;
+ try {
+ // start is in range but end is out of range
+ b.force(offset, 2 * BLOCK_SIZE);
+ } catch (IndexOutOfBoundsException e) {
+ exc = e;
+ }
+ if (exc == null) {
+ throw new RuntimeException("expected Exception for force beyond write limit");
+ }
+ }
+ }
+ }
+
+ /**
* Test exceptions specified by map method
*/
private static void testExceptions() throws Exception {