8221696: MappedByteBuffer.force method to specify range
authoradinn
Thu, 25 Apr 2019 17:27:37 +0100
changeset 54823 1b940da275d2
parent 54822 f542a3a135bd
child 54824 adb3a3aa2e52
8221696: MappedByteBuffer.force method to specify range Summary: Overload MappedByteBuffer.force to accept index and length arguments Reviewed-by: dfuchs, alanb, bpb
src/java.base/share/classes/java/nio/MappedByteBuffer.java
test/jdk/java/nio/channels/FileChannel/MapTest.java
--- 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 {