/*
* Copyright 2016 higherfrequencytrading.com
*
* 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 net.openhft.lang.io;
import net.openhft.lang.io.serialization.ObjectSerializer;
import org.jetbrains.annotations.NotNull;
import sun.misc.Unsafe;
import java.io.EOFException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.atomic.AtomicInteger;
/**
* works in conjunction with a MappedFile to map additional memory blocks when required This
* implementation is very similar to {@code NativeBytes}, accept that the memory address resolution
* is left to ChronicleUnsafe, rather than being part of this class.
*/
public class MappedNativeBytes extends AbstractBytes {
static final int BYTES_OFFSET;
static final int CHARS_OFFSET;
static {
try {
@SuppressWarnings("ALL")
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
BYTES_OFFSET = unsafe.arrayBaseOffset(byte[].class);
CHARS_OFFSET = unsafe.arrayBaseOffset(char[].class);
} catch (Exception e) {
throw new AssertionError(e);
}
}
@NotNull
public final ThreadLocal<ChronicleUnsafe> threadLocal = new ThreadLocal<ChronicleUnsafe>();
private final boolean isSingleThreaded;
private final MappedFile mappedFile;
private final ChronicleUnsafe chronicleUnsafe;
protected long start;
protected long position;
protected long limit;
protected long capacity;
public MappedNativeBytes(@NotNull final MappedFile mappedFile, boolean isSingleThreaded) {
this.isSingleThreaded = isSingleThreaded;
this.mappedFile = mappedFile;
this.start = 0;
this.position = start;
this.limit = this.capacity = Long.MAX_VALUE;
this.chronicleUnsafe = (isSingleThreaded) ? new ChronicleUnsafe(mappedFile) : null;
}
public MappedNativeBytes(ObjectSerializer objectSerializer,
long sliceStart,
long capacity,
@NotNull AtomicInteger refCount,
@NotNull MappedFile mappedFile,
boolean singleThreaded) {
this.isSingleThreaded = singleThreaded;
setObjectSerializer(objectSerializer);
this.start = sliceStart;
this.position = 0;
this.capacity = capacity;
this.refCount.set(refCount.get());
this.mappedFile = mappedFile;
this.chronicleUnsafe = (isSingleThreaded) ? new ChronicleUnsafe(mappedFile) : null;
}
@Override
public MappedNativeBytes slice() {
return new MappedNativeBytes(objectSerializer(), position, limit, refCount, mappedFile, isSingleThreaded);
}
@Override
public MappedNativeBytes slice(long offset, long length) {
long sliceStart = position + offset;
assert sliceStart >= start && sliceStart < capacity;
long sliceEnd = sliceStart + length;
assert sliceEnd > sliceStart && sliceEnd <= capacity;
return new MappedNativeBytes(objectSerializer(), sliceStart, sliceEnd, refCount, mappedFile, isSingleThreaded);
}
@Override
public CharSequence subSequence(int start, int end) {
long subStart = position + start;
if (subStart < position || subStart > limit)
throw new IndexOutOfBoundsException();
long subEnd = position + end;
if (subEnd < subStart || subEnd > limit)
throw new IndexOutOfBoundsException();
if (start == end)
return "";
return new MappedNativeBytes(objectSerializer(), subStart, subEnd, refCount, mappedFile, isSingleThreaded);
}
@Override
public MappedNativeBytes bytes() {
return new MappedNativeBytes(objectSerializer(), start, capacity, refCount, mappedFile, isSingleThreaded);
}
@Override
public MappedNativeBytes bytes(long offset, long length) {
long sliceStart = start + offset;
assert sliceStart >= start && sliceStart < capacity;
long sliceEnd = sliceStart + length;
assert sliceEnd > sliceStart && sliceEnd <= capacity;
return new MappedNativeBytes(objectSerializer(), sliceStart, sliceEnd, refCount, mappedFile, isSingleThreaded);
}
@Override
public long address() {
return start;
}
@Override
public Bytes zeroOut() {
clear();
getChronicleUnsafe().setMemory(start, capacity(), (byte) 0);
return this;
}
@Override
public Bytes zeroOut(long start, long end) {
if (start < 0 || end > limit())
throw new IllegalArgumentException("start: " + start + ", end: " + end);
if (start >= end)
return this;
getChronicleUnsafe().setMemory(this.start + start, end - start, (byte) 0);
return this;
}
@Override
public Bytes zeroOut(long start, long end, boolean ifNotZero) {
return ifNotZero ? zeroOutDirty(start, end) : zeroOut(start, end);
}
private Bytes zeroOutDirty(long start, long end) {
if (start < 0 || end > limit())
throw new IllegalArgumentException("start: " + start + ", end: " + end);
if (start >= end)
return this;
// get unaligned leading bytes
ChronicleUnsafe unsafe = getChronicleUnsafe();
while (start < end && (start & 7) != 0) {
byte b = unsafe.getByte(this.start + start);
if (b != 0)
unsafe.putByte(this.start + start, (byte) 0);
start++;
}
// check 64-bit aligned access
while (start < end - 7) {
long l = unsafe.getLong(this.start + start);
if (l != 0)
unsafe.putLong(this.start + start, 0L);
start += 8;
}
// check unaligned tail
while (start < end) {
byte b = unsafe.getByte(this.start + start);
if (b != 0)
unsafe.putByte(this.start + start, (byte) 0);
start++;
}
return this;
}
@Override
public int read(@NotNull byte[] bytes, int off, int len) {
if (len < 0 || off < 0 || off + len > bytes.length)
throw new IllegalArgumentException();
long left = remaining();
if (left <= 0) return -1;
int len2 = (int) Math.min(len, left);
getChronicleUnsafe().copyMemory(null, position, bytes, BYTES_OFFSET + off, len2);
addPosition(len2);
return len2;
}
@Override
public byte readByte() {
byte aByte = getChronicleUnsafe().getByte(position);
addPosition(1);
return aByte;
}
@Override
public byte readByte(long offset) {
return getChronicleUnsafe().getByte(start + offset);
}
@Override
public void readFully(@NotNull byte[] b, int off, int len) {
checkArrayOffs(b.length, off, len);
long left = remaining();
if (left < len)
throw new IllegalStateException(new EOFException());
getChronicleUnsafe().copyMemory(null, position, b, BYTES_OFFSET + off, len);
addPosition(len);
}
@Override
public void readFully(long offset, byte[] bytes, int off, int len) {
checkArrayOffs(bytes.length, off, len);
getChronicleUnsafe().copyMemory(null, start + offset, bytes, BYTES_OFFSET + off, len);
}
@Override
public void readFully(@NotNull char[] data, int off, int len) {
checkArrayOffs(data.length, off, len);
long bytesOff = off * 2L;
long bytesLen = len * 2L;
long left = remaining();
if (left < bytesLen)
throw new IllegalStateException(new EOFException());
getChronicleUnsafe().copyMemory(null, position, data, BYTES_OFFSET + bytesOff, bytesLen);
addPosition(bytesLen);
}
@Override
public short readShort() {
short s = getChronicleUnsafe().getShort(position);
addPosition(2);
return s;
}
@Override
public short readShort(long offset) {
return getChronicleUnsafe().getShort(start + offset);
}
@Override
public char readChar() {
char ch = getChronicleUnsafe().getChar(position);
addPosition(2);
return ch;
}
@Override
public char readChar(long offset) {
return getChronicleUnsafe().getChar(start + offset);
}
@Override
public int readInt() {
int i = getChronicleUnsafe().getInt(position);
addPosition(4);
return i;
}
@Override
public int readInt(long offset) {
return getChronicleUnsafe().getInt(start + offset);
}
@Override
public int readVolatileInt() {
int i = getChronicleUnsafe().getIntVolatile(null, position);
addPosition(4);
return i;
}
@Override
public int readVolatileInt(long offset) {
return getChronicleUnsafe().getIntVolatile(null, start + offset);
}
@Override
public long readLong() {
long l = getChronicleUnsafe().getLong(position);
addPosition(8);
return l;
}
@Override
public long readLong(long offset) {
return getChronicleUnsafe().getLong(start + offset);
}
@Override
public long readVolatileLong() {
long l = getChronicleUnsafe().getLongVolatile(null, position);
addPosition(8);
return l;
}
@Override
public long readVolatileLong(long offset) {
return getChronicleUnsafe().getLongVolatile(null, start + offset);
}
@Override
public float readFloat() {
float f = getChronicleUnsafe().getFloat(position);
addPosition(4);
return f;
}
@Override
public float readFloat(long offset) {
return getChronicleUnsafe().getFloat(start + offset);
}
@Override
public double readDouble() {
double d = getChronicleUnsafe().getDouble(position);
addPosition(8);
return d;
}
@Override
public double readDouble(long offset) {
return getChronicleUnsafe().getDouble(start + offset);
}
@Override
public void write(int b) {
getChronicleUnsafe().putByte(position, (byte) b);
incrementPositionAddr(1);
}
@Override
public void writeByte(long offset, int b) {
offsetChecks(offset, 1L);
getChronicleUnsafe().putByte(start + offset, (byte) b);
}
@Override
public void write(long offset, @NotNull byte[] bytes) {
if (offset < 0 || offset + bytes.length > capacity())
throw new IllegalArgumentException();
getChronicleUnsafe().copyMemory(bytes, BYTES_OFFSET, null, start + offset, bytes.length);
addPosition(bytes.length);
}
@Override
public void write(byte[] bytes, int off, int len) {
if (off < 0 || off + len > bytes.length || len > remaining())
throw new IllegalArgumentException();
getChronicleUnsafe().copyMemory(bytes, BYTES_OFFSET + off, null, position, len);
addPosition(len);
}
@Override
public void write(long offset, byte[] bytes, int off, int len) {
if (offset < 0 || off + len > bytes.length || offset + len > capacity())
throw new IllegalArgumentException();
getChronicleUnsafe().copyMemory(bytes, BYTES_OFFSET + off, null, start + offset, len);
}
@Override
public void writeShort(int v) {
positionChecks(position + 2L);
getChronicleUnsafe().putShort(position, (short) v);
position += 2L;
}
private long incrementPositionAddr(long value) {
positionAddr(positionAddr() + value);
return positionAddr();
}
@Override
public void writeShort(long offset, int v) {
offsetChecks(offset, 2L);
getChronicleUnsafe().putShort(start + offset, (short) v);
}
@Override
public void writeChar(int v) {
positionChecks(position + 2L);
getChronicleUnsafe().putChar(position, (char) v);
position += 2L;
}
void addPosition(long delta) {
positionAddr(positionAddr() + delta);
}
@Override
public void writeChar(long offset, int v) {
offsetChecks(offset, 2L);
getChronicleUnsafe().putChar(start + offset, (char) v);
}
@Override
public void writeInt(int v) {
positionChecks(position + 4L);
getChronicleUnsafe().putInt(position, v);
position += 4L;
}
@Override
public void writeInt(long offset, int v) {
offsetChecks(offset, 4L);
getChronicleUnsafe().putInt(start + offset, v);
}
@Override
public void writeOrderedInt(int v) {
positionChecks(position + 4L);
getChronicleUnsafe().putOrderedInt(null, position, v);
position += 4L;
}
@Override
public void writeOrderedInt(long offset, int v) {
offsetChecks(offset, 4L);
getChronicleUnsafe().putOrderedInt(null, start + offset, v);
}
@Override
public boolean compareAndSwapInt(long offset, int expected, int x) {
offsetChecks(offset, 4L);
return getChronicleUnsafe().compareAndSwapInt(null, start + offset, expected, x);
}
@Override
public void writeLong(long v) {
positionChecks(position + 8L);
getChronicleUnsafe().putLong(position, v);
position += 8L;
}
@Override
public void writeLong(long offset, long v) {
offsetChecks(offset, 8L);
getChronicleUnsafe().putLong(start + offset, v);
}
@Override
public void writeOrderedLong(long v) {
positionChecks(position + 8L);
getChronicleUnsafe().putOrderedLong(null, position, v);
position += 8L;
}
@Override
public void writeOrderedLong(long offset, long v) {
offsetChecks(offset, 8L);
getChronicleUnsafe().putOrderedLong(null, start + offset, v);
}
@Override
public boolean compareAndSwapLong(long offset, long expected, long x) {
offsetChecks(offset, 8L);
return getChronicleUnsafe().compareAndSwapLong(null, start + offset, expected, x);
}
@Override
public void writeFloat(float v) {
positionChecks(position + 4L);
getChronicleUnsafe().putFloat(position, v);
position += 4L;
}
@Override
public void writeFloat(long offset, float v) {
offsetChecks(offset, 4L);
getChronicleUnsafe().putFloat(start + offset, v);
}
@Override
public void writeDouble(double v) {
positionChecks(position + 8L);
getChronicleUnsafe().putDouble(position, v);
position += 8L;
}
@Override
public void writeDouble(long offset, double v) {
offsetChecks(offset, 8L);
getChronicleUnsafe().putDouble(start + offset, v);
}
@Override
public void readObject(Object object, int start, int end) {
int len = end - start;
if (position + len >= limit)
throw new IndexOutOfBoundsException("Length out of bounds len: " + len);
ChronicleUnsafe unsafe = getChronicleUnsafe();
for (; len >= 8; len -= 8) {
unsafe.putLong(object, (long) start, unsafe.getLong(position));
incrementPositionAddr(8L);
start += 8;
}
for (; len > 0; len--) {
unsafe.putByte(object, (long) start, unsafe.getByte(position));
incrementPositionAddr(1L);
start++;
}
}
@Override
public void writeObject(Object object, int start, int end) {
int len = end - start;
ChronicleUnsafe unsafe = getChronicleUnsafe();
for (; len >= 8; len -= 8) {
positionChecks(position + 8L);
unsafe.putLong(position, unsafe.getLong(object, (long) start));
position += 8;
start += 8;
}
for (; len > 0; len--) {
positionChecks(position + 1L);
unsafe.putByte(position, unsafe.getByte(object, (long) start));
position++;
start++;
}
}
@Override
public boolean compare(long offset, RandomDataInput input, long inputOffset, long len) {
if (offset < 0 || inputOffset < 0 || len < 0)
throw new IndexOutOfBoundsException();
if (offset + len < 0 || offset + len > capacity() || inputOffset + len < 0 ||
inputOffset + len > input.capacity()) {
return false;
}
long i = 0L;
ChronicleUnsafe unsafe = getChronicleUnsafe();
for (; i < len - 7L; i += 8L) {
if (unsafe.getLong(start + offset + i) != input.readLong(inputOffset + i))
return false;
}
if (i < len - 3L) {
if (unsafe.getInt(start + offset + i) != input.readInt(inputOffset + i))
return false;
i += 4L;
}
if (i < len - 1L) {
if (unsafe.getChar(start + offset + i) != input.readChar(inputOffset + i))
return false;
i += 2L;
}
if (i < len) {
if (unsafe.getByte(start + offset + i) != input.readByte(inputOffset + i))
return false;
}
return true;
}
@Override
public long position() {
return (position - start);
}
@Override
public MappedNativeBytes position(long position) {
if (position < 0 || position > limit())
throw new IndexOutOfBoundsException("position: " + position + " limit: " + limit());
positionAddr(start + position);
return this;
}
/**
* Change the position acknowleging there is no thread safety assumptions. Best effort setting
* is fine. *
*
* @param position to set if we can.
* @return this
*/
public MappedNativeBytes lazyPosition(long position) {
if (position < 0 || position > limit())
throw new IndexOutOfBoundsException("position: " + position + " limit: " + limit());
// assume we don't need to no check thread safety.
positionAddr(start + position);
return this;
}
@Override
public void write(RandomDataInput bytes, long position, long length) {
if (length > remaining())
throw new IllegalArgumentException("Attempt to write " + length + " bytes with " + remaining() + " remaining");
if (bytes instanceof MappedNativeBytes) {
getChronicleUnsafe().copyMemory(((MappedNativeBytes) bytes).start + position, this.position, length);
skip(length);
} else {
super.write(bytes, position, length);
}
}
@Override
public long capacity() {
return (capacity - start);
}
@Override
public long remaining() {
return (limit - position);
}
@Override
public long limit() {
return (limit - start);
}
@Override
public MappedNativeBytes limit(long limit) {
if (limit < 0 || limit > capacity()) {
throw new IllegalArgumentException("limit: " + limit + " capacity: " + capacity());
}
this.limit = start + limit;
return this;
}
@NotNull
@Override
public ByteOrder byteOrder() {
return ByteOrder.nativeOrder();
}
@Override
public void checkEndOfBuffer() throws IndexOutOfBoundsException {
if (position() > limit()) {
throw new IndexOutOfBoundsException(
"position is beyond the end of the buffer " + position() + " > " + limit());
}
}
public long startAddr() {
return start;
}
long capacityAddr() {
return capacity;
}
@Override
protected void cleanup() {
// TODO nothing to do.
}
@Override
public Bytes load() {
ChronicleUnsafe unsafe = getChronicleUnsafe();
int pageSize = unsafe.pageSize();
for (long addr = start; addr < capacity; addr += pageSize)
unsafe.getByte(addr);
return this;
}
public void alignPositionAddr(int powerOf2) {
long value = (position + powerOf2 - 1) & ~(powerOf2 - 1);
positionAddr(value);
}
public void positionAddr(long positionAddr) {
positionChecks(positionAddr);
this.position = positionAddr;
}
void positionChecks(long positionAddr) {
assert actualPositionChecks(positionAddr);
}
boolean actualPositionChecks(long positionAddr) {
if (positionAddr < start)
throw new IndexOutOfBoundsException("position before the start by " + (start - positionAddr) + " bytes");
if (positionAddr > limit)
throw new IndexOutOfBoundsException("position after the limit by " + (positionAddr - limit) + " bytes");
return true;
}
void offsetChecks(long offset, long len) {
assert actualOffsetChecks(offset, len);
}
boolean actualOffsetChecks(long offset, long len) {
if (offset < 0L || offset + len > capacity())
throw new IndexOutOfBoundsException("offset out of bounds: " + offset + ", len: " +
len + ", capacity: " + capacity());
return true;
}
public long positionAddr() {
return position;
}
@Override
public ByteBuffer sliceAsByteBuffer(ByteBuffer toReuse) {
return sliceAsByteBuffer(toReuse, null);
}
protected ByteBuffer sliceAsByteBuffer(ByteBuffer toReuse, Object att) {
return ByteBufferReuse.INSTANCE.reuse(position, (int) remaining(), att, toReuse);
}
// todo : we should move this lookup further up the stack, so that it can be done, just ONCE, for example once by a single threaded appender
// todo : hence the constructor should be give then instance of chronicleUnsafe to use
@NotNull
public ChronicleUnsafe getChronicleUnsafe() {
if (isSingleThreaded)
return chronicleUnsafe;
ChronicleUnsafe chronicleUnsafe = threadLocal.get();
if (chronicleUnsafe == null) {
chronicleUnsafe = new ChronicleUnsafe(mappedFile);
threadLocal.set(chronicleUnsafe);
}
return chronicleUnsafe;
}
}