/* * Copyright 2014-2017 Real Logic Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.agrona.concurrent; import org.agrona.BufferUtil; import org.agrona.DirectBuffer; import org.agrona.IoUtil; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import static java.nio.charset.StandardCharsets.UTF_8; import static org.agrona.BitUtil.*; import static org.agrona.UnsafeAccess.UNSAFE; import static org.agrona.BufferUtil.ARRAY_BASE_OFFSET; import static org.agrona.BufferUtil.NATIVE_BYTE_ORDER; import static org.agrona.BufferUtil.NULL_BYTES; import static org.agrona.concurrent.UnsafeBuffer.*; /** * Supports regular, byte ordered, and atomic (memory ordered) access to an underlying buffer. * * This buffer is resizable and based upon an underlying FileChannel. * The channel is <b>not</b> closed when the buffer is closed. * * Note: The resize method is not threadsafe. Concurrent access should only occur after a successful resize. */ public class MappedResizeableBuffer implements AutoCloseable { private long addressOffset; private long capacity; private FileChannel fileChannel; /** * Attach a view to an off-heap memory region by address. * * @param fileChannel the file to map * @param offset the offset of the file to start the mapping * @param initialLength of the buffer from the given address */ public MappedResizeableBuffer(final FileChannel fileChannel, final long offset, final long initialLength) { this.fileChannel = fileChannel; map(offset, initialLength); } public void close() { unmap(); } public void resize(final long newLength) { if (newLength <= 0) { throw new IllegalArgumentException("Length must be a positive long, but is: " + newLength); } unmap(); map(0, newLength); } /** * Remap the buffer using the existing file based on a new offset and length * * @param offset the offset of the file to start the mapping * @param length of the buffer from the given address */ public void wrap(final long offset, final long length) { if (offset == addressOffset && length == capacity) { return; } wrap(fileChannel, offset, length); } /** * Remap the buffer based on a new file, offset and a length * * @param fileChannel the file to map * @param offset the offset of the file to start the mapping * @param length of the buffer from the given address */ public void wrap(final FileChannel fileChannel, final long offset, final long length) { unmap(); this.fileChannel = fileChannel; map(offset, length); } /** * Address offset in memory at which the mapping begins. * * @return the address offset in memory at which the mapping begins. */ public long addressOffset() { return addressOffset; } /** * {@link FileChannel} that this buffer is mapping over. * * @return the {@link FileChannel} that this buffer is mapping over. */ public FileChannel fileChannel() { return fileChannel; } public void setMemory(final long index, final int length, final byte value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); } UNSAFE.setMemory(null, addressOffset + index, length, value); } public long capacity() { return capacity; } public void checkLimit(final long limit) { if (limit > capacity) { throw new IndexOutOfBoundsException("limit=" + limit + " is beyond capacity=" + capacity); } } public void verifyAlignment() { if (0 != (addressOffset & (ALIGNMENT - 1))) { throw new IllegalStateException( "AtomicBuffer is not correctly aligned: addressOffset=" + addressOffset + " is not divisible by " + ALIGNMENT); } } /////////////////////////////////////////////////////////////////////////// public long getLong(final long index, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } long bits = UNSAFE.getLong(null, addressOffset + index); if (NATIVE_BYTE_ORDER != byteOrder) { bits = Long.reverseBytes(bits); } return bits; } public void putLong(final long index, final long value, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } long bits = value; if (NATIVE_BYTE_ORDER != byteOrder) { bits = Long.reverseBytes(bits); } UNSAFE.putLong(null, addressOffset + index, bits); } public long getLong(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } return UNSAFE.getLong(null, addressOffset + index); } public void putLong(final long index, final long value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } UNSAFE.putLong(null, addressOffset + index, value); } public long getLongVolatile(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } return UNSAFE.getLongVolatile(null, addressOffset + index); } public void putLongVolatile(final long index, final long value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } UNSAFE.putLongVolatile(null, addressOffset + index, value); } public void putLongOrdered(final long index, final long value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } UNSAFE.putOrderedLong(null, addressOffset + index, value); } public long addLongOrdered(final long index, final long increment) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } final long offset = addressOffset + index; final long value = UNSAFE.getLong(null, offset); UNSAFE.putOrderedLong(null, offset, value + increment); return value; } public boolean compareAndSetLong(final long index, final long expectedValue, final long updateValue) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } return UNSAFE.compareAndSwapLong(null, addressOffset + index, expectedValue, updateValue); } public long getAndSetLong(final long index, final long value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } return UNSAFE.getAndSetLong(null, addressOffset + index, value); } public long getAndAddLong(final long index, final long delta) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_LONG); } return UNSAFE.getAndAddLong(null, addressOffset + index, delta); } /////////////////////////////////////////////////////////////////////////// public int getInt(final long index, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } int bits = UNSAFE.getInt(null, addressOffset + index); if (NATIVE_BYTE_ORDER != byteOrder) { bits = Integer.reverseBytes(bits); } return bits; } public void putInt(final long index, final int value, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } int bits = value; if (NATIVE_BYTE_ORDER != byteOrder) { bits = Integer.reverseBytes(bits); } UNSAFE.putInt(null, addressOffset + index, bits); } public int getInt(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } return UNSAFE.getInt(null, addressOffset + index); } public void putInt(final long index, final int value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } UNSAFE.putInt(null, addressOffset + index, value); } public int getIntVolatile(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } return UNSAFE.getIntVolatile(null, addressOffset + index); } public void putIntVolatile(final long index, final int value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } UNSAFE.putIntVolatile(null, addressOffset + index, value); } public void putIntOrdered(final long index, final int value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } UNSAFE.putOrderedInt(null, addressOffset + index, value); } public int addIntOrdered(final long index, final int increment) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } final long offset = addressOffset + index; final int value = UNSAFE.getInt(null, offset); UNSAFE.putOrderedInt(null, offset, value + increment); return value; } public boolean compareAndSetInt(final long index, final int expectedValue, final int updateValue) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } return UNSAFE.compareAndSwapInt(null, addressOffset + index, expectedValue, updateValue); } public int getAndSetInt(final long index, final int value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } return UNSAFE.getAndSetInt(null, addressOffset + index, value); } public int getAndAddInt(final long index, final int delta) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_INT); } return UNSAFE.getAndAddInt(null, addressOffset + index, delta); } /////////////////////////////////////////////////////////////////////////// public double getDouble(final long index, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_DOUBLE); } if (NATIVE_BYTE_ORDER != byteOrder) { final long bits = UNSAFE.getLong(null, addressOffset + index); return Double.longBitsToDouble(Long.reverseBytes(bits)); } else { return UNSAFE.getDouble(null, addressOffset + index); } } public void putDouble(final long index, final double value, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_DOUBLE); } if (NATIVE_BYTE_ORDER != byteOrder) { final long bits = Long.reverseBytes(Double.doubleToRawLongBits(value)); UNSAFE.putLong(null, addressOffset + index, bits); } else { UNSAFE.putDouble(null, addressOffset + index, value); } } public double getDouble(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_DOUBLE); } return UNSAFE.getDouble(null, addressOffset + index); } public void putDouble(final long index, final double value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_DOUBLE); } UNSAFE.putDouble(null, addressOffset + index, value); } /////////////////////////////////////////////////////////////////////////// public float getFloat(final long index, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_FLOAT); } if (NATIVE_BYTE_ORDER != byteOrder) { final int bits = UNSAFE.getInt(null, addressOffset + index); return Float.intBitsToFloat(Integer.reverseBytes(bits)); } else { return UNSAFE.getFloat(null, addressOffset + index); } } public void putFloat(final long index, final float value, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_FLOAT); } if (NATIVE_BYTE_ORDER != byteOrder) { final int bits = Integer.reverseBytes(Float.floatToRawIntBits(value)); UNSAFE.putInt(null, addressOffset + index, bits); } else { UNSAFE.putFloat(null, addressOffset + index, value); } } public float getFloat(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_FLOAT); } return UNSAFE.getFloat(null, addressOffset + index); } public void putFloat(final long index, final float value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_FLOAT); } UNSAFE.putFloat(null, addressOffset + index, value); } /////////////////////////////////////////////////////////////////////////// public short getShort(final long index, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_SHORT); } short bits = UNSAFE.getShort(null, addressOffset + index); if (NATIVE_BYTE_ORDER != byteOrder) { bits = Short.reverseBytes(bits); } return bits; } public void putShort(final long index, final short value, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_SHORT); } short bits = value; if (NATIVE_BYTE_ORDER != byteOrder) { bits = Short.reverseBytes(bits); } UNSAFE.putShort(null, addressOffset + index, bits); } public short getShort(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_SHORT); } return UNSAFE.getShort(null, addressOffset + index); } public void putShort(final long index, final short value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_SHORT); } UNSAFE.putShort(null, addressOffset + index, value); } public short getShortVolatile(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_SHORT); } return UNSAFE.getShortVolatile(null, addressOffset + index); } public void putShortVolatile(final long index, final short value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_SHORT); } UNSAFE.putShortVolatile(null, addressOffset + index, value); } /////////////////////////////////////////////////////////////////////////// public byte getByte(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck(index); } return UNSAFE.getByte(null, addressOffset + index); } public void putByte(final long index, final byte value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck(index); } UNSAFE.putByte(null, addressOffset + index, value); } public byte getByteVolatile(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck(index); } return UNSAFE.getByteVolatile(null, addressOffset + index); } public void putByteVolatile(final long index, final byte value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck(index); } UNSAFE.putByteVolatile(null, addressOffset + index, value); } public void getBytes(final long index, final byte[] dst) { getBytes(index, dst, 0, dst.length); } public void getBytes(final long index, final byte[] dst, final long offset, final int length) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); BufferUtil.boundsCheck(dst, offset, length); } UNSAFE.copyMemory(null, addressOffset + index, dst, ARRAY_BASE_OFFSET + offset, length); } public void getBytes(final long index, final ByteBuffer dstBuffer, final int length) { final int dstOffset = dstBuffer.position(); if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); BufferUtil.boundsCheck(dstBuffer, (long)dstOffset, length); } final byte[] dstnull; final long dstBaseOffset; if (dstBuffer.hasArray()) { dstnull = dstBuffer.array(); dstBaseOffset = ARRAY_BASE_OFFSET + dstBuffer.arrayOffset(); } else { dstnull = null; dstBaseOffset = ((sun.nio.ch.DirectBuffer)dstBuffer).address(); } UNSAFE.copyMemory(null, addressOffset + index, dstnull, dstBaseOffset + dstOffset, length); dstBuffer.position(dstBuffer.position() + length); } public void putBytes(final long index, final byte[] src) { putBytes(index, src, 0, src.length); } public void putBytes(final long index, final byte[] src, final long offset, final int length) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); BufferUtil.boundsCheck(src, offset, length); } UNSAFE.copyMemory(src, ARRAY_BASE_OFFSET + offset, null, addressOffset + index, length); } public void putBytes(final long index, final ByteBuffer srcBuffer, final int length) { final int srcIndex = srcBuffer.position(); if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); BufferUtil.boundsCheck(srcBuffer, (long)srcIndex, length); } putBytes(index, srcBuffer, srcIndex, length); srcBuffer.position(srcIndex + length); } public void putBytes(final long index, final ByteBuffer srcBuffer, final long srcIndex, final int length) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); BufferUtil.boundsCheck(srcBuffer, srcIndex, length); } final byte[] srcnull; final long srcBaseOffset; if (srcBuffer.hasArray()) { srcnull = srcBuffer.array(); srcBaseOffset = ARRAY_BASE_OFFSET + srcBuffer.arrayOffset(); } else { srcnull = null; srcBaseOffset = ((sun.nio.ch.DirectBuffer)srcBuffer).address(); } UNSAFE.copyMemory(srcnull, srcBaseOffset + srcIndex, null, addressOffset + index, length); } public void putBytes(final long index, final DirectBuffer srcBuffer, final int srcIndex, final int length) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, length); srcBuffer.boundsCheck(srcIndex, length); } UNSAFE.copyMemory( null, srcBuffer.addressOffset() + srcIndex, null, addressOffset + index, length); } /////////////////////////////////////////////////////////////////////////// public char getChar(final long index, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_CHAR); } char bits = UNSAFE.getChar(null, addressOffset + index); if (NATIVE_BYTE_ORDER != byteOrder) { bits = (char)Short.reverseBytes((short)bits); } return bits; } public void putChar(final long index, final char value, final ByteOrder byteOrder) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_CHAR); } char bits = value; if (NATIVE_BYTE_ORDER != byteOrder) { bits = (char)Short.reverseBytes((short)bits); } UNSAFE.putChar(null, addressOffset + index, bits); } public char getChar(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_CHAR); } return UNSAFE.getChar(null, addressOffset + index); } public void putChar(final long index, final char value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_CHAR); } UNSAFE.putChar(null, addressOffset + index, value); } public char getCharVolatile(final long index) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_CHAR); } return UNSAFE.getCharVolatile(null, addressOffset + index); } public void putCharVolatile(final long index, final char value) { if (SHOULD_BOUNDS_CHECK) { boundsCheck0(index, SIZE_OF_CHAR); } UNSAFE.putCharVolatile(null, addressOffset + index, value); } /////////////////////////////////////////////////////////////////////////// public String getStringUtf8(final long offset) { final int length = getInt(offset); return getStringUtf8(offset, length); } public String getStringUtf8(final long offset, final ByteOrder byteOrder) { final int length = getInt(offset, byteOrder); return getStringUtf8(offset, length); } public String getStringUtf8(final long offset, final int length) { final byte[] stringInBytes = new byte[length]; getBytes(offset + SIZE_OF_INT, stringInBytes); return new String(stringInBytes, UTF_8); } public int putStringUtf8(final long offset, final String value) { return putStringUtf8(offset, value, Integer.MAX_VALUE); } public int putStringUtf8(final long offset, final String value, final ByteOrder byteOrder) { return putStringUtf8(offset, value, byteOrder, Integer.MAX_VALUE); } public int putStringUtf8(final long offset, final String value, final int maxEncodedSize) { final byte[] bytes = value != null ? value.getBytes(UTF_8) : NULL_BYTES; if (bytes.length > maxEncodedSize) { throw new IllegalArgumentException("Encoded string larger than maximum size: " + maxEncodedSize); } putInt(offset, bytes.length); putBytes(offset + SIZE_OF_INT, bytes); return SIZE_OF_INT + bytes.length; } public int putStringUtf8(final long offset, final String value, final ByteOrder byteOrder, final int maxEncodedSize) { final byte[] bytes = value != null ? value.getBytes(UTF_8) : NULL_BYTES; if (bytes.length > maxEncodedSize) { throw new IllegalArgumentException("Encoded string larger than maximum size: " + maxEncodedSize); } putInt(offset, bytes.length, byteOrder); putBytes(offset + SIZE_OF_INT, bytes); return SIZE_OF_INT + bytes.length; } public String getStringWithoutLengthUtf8(final long offset, final int length) { final byte[] stringInBytes = new byte[length]; getBytes(offset, stringInBytes); return new String(stringInBytes, UTF_8); } public int putStringWithoutLengthUtf8(final long offset, final String value) { final byte[] bytes = value != null ? value.getBytes(UTF_8) : NULL_BYTES; putBytes(offset, bytes); return bytes.length; } /////////////////////////////////////////////////////////////////////////// public void boundsCheck(final long index, final int length) { boundsCheck0(index, length); } private void boundsCheck(final long index) { if (index < 0 || index >= capacity) { throw new IndexOutOfBoundsException("index=" + index + " capacity=" + capacity); } } private void boundsCheck0(final long index, final int length) { final long resultingPosition = index + (long)length; if (index < 0 || resultingPosition > capacity || resultingPosition < index) { throw new IndexOutOfBoundsException( "index=" + index + " length=" + length + " capacity=" + capacity); } } private void map(final long offset, final long length) { capacity = length; addressOffset = IoUtil.map(fileChannel, FileChannel.MapMode.READ_WRITE, offset, length); } private void unmap() { IoUtil.unmap(fileChannel, addressOffset, capacity); } }