package org.infinispan.container.offheap;
import java.io.IOException;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.marshall.WrappedByteArray;
import org.infinispan.commons.marshall.WrappedBytes;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.container.versioning.EntryVersion;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.metadata.EmbeddedMetadata;
import org.infinispan.metadata.Metadata;
import org.infinispan.util.TimeService;
/**
* Factory that can create CacheEntry instances from off-heap memory.
*
* @author wburns
* @since 9.0
*/
public class OffHeapEntryFactoryImpl implements OffHeapEntryFactory {
private static final UnsafeWrapper UNSAFE = UnsafeWrapper.INSTANCE;
private Marshaller marshaller;
private OffHeapMemoryAllocator allocator;
private TimeService timeService;
private InternalEntryFactory internalEntryFactory;
private boolean evictionEnabled;
// If custom than we just store the metadata as is (no other bits should be used)
private static final byte CUSTOM = 1;
// Version can be set with any combination of the following types
private static final byte HAS_VERSION = 2;
// Only one of the following should ever be set
private static final byte IMMORTAL = 1 << 2;
private static final byte MORTAL = 1 << 3;
private static final byte TRANSIENT = 1 << 4;
private static final byte TRANSIENT_MORTAL = 1 << 5;
private static final int BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
/**
* HEADER is composed of hashCode (int), keyLength (int), metadataLength (int), valueLength (int), type (byte)
*/
private static final int HEADER_LENGTH = 4 + 4 + 4 + 4 + 1;
@Inject
public void inject(Marshaller marshaller, OffHeapMemoryAllocator allocator, TimeService timeService,
InternalEntryFactory internalEntryFactory, Configuration configuration) {
this.marshaller = marshaller;
this.allocator = allocator;
this.timeService = timeService;
this.internalEntryFactory = internalEntryFactory;
this.evictionEnabled = configuration.memory().size() > 0;
}
/**
* Create an entry off-heap. The first 8 bytes will always be 0, reserved for a future reference to another entry
* @param key the key to use
* @param value the value to use
* @param metadata the metadata to use
* @return the address of the entry created off heap
*/
@Override
public long create(WrappedBytes key, WrappedBytes value, Metadata metadata) {
byte type;
byte[] metadataBytes;
if (metadata instanceof EmbeddedMetadata) {
EntryVersion version = metadata.version();
byte[] versionBytes;
if (version != null) {
type = HAS_VERSION;
try {
versionBytes = marshaller.objectToByteBuffer(version);
} catch (IOException | InterruptedException e) {
throw new CacheException(e);
}
} else {
type = 0;
versionBytes = new byte[0];
}
long lifespan = metadata.lifespan();
long maxIdle = metadata.maxIdle();
if (lifespan < 0 && maxIdle < 0) {
type |= IMMORTAL;
metadataBytes = versionBytes;
} else if (lifespan > -1 && maxIdle < 0) {
type |= MORTAL;
metadataBytes = new byte[16 + versionBytes.length];
Bits.putLong(metadataBytes, 0, lifespan);
Bits.putLong(metadataBytes, 8, timeService.wallClockTime());
System.arraycopy(metadataBytes, 0, versionBytes, 16, versionBytes.length);
} else if (lifespan < 0 && maxIdle > -1) {
type |= TRANSIENT;
metadataBytes = new byte[16 + versionBytes.length];
Bits.putLong(metadataBytes, 0, maxIdle);
Bits.putLong(metadataBytes, 8, timeService.wallClockTime());
System.arraycopy(metadataBytes, 0, versionBytes, 16, versionBytes.length);
} else {
type |= TRANSIENT_MORTAL;
metadataBytes = new byte[32 + versionBytes.length];
Bits.putLong(metadataBytes, 0, maxIdle);
Bits.putLong(metadataBytes, 8, lifespan);
Bits.putLong(metadataBytes, 16, timeService.wallClockTime());
Bits.putLong(metadataBytes, 24, timeService.wallClockTime());
System.arraycopy(metadataBytes, 0, versionBytes, 16, versionBytes.length);
}
} else {
type = CUSTOM;
try {
metadataBytes = marshaller.objectToByteBuffer(metadata);
} catch (IOException | InterruptedException e) {
throw new CacheException(e);
}
}
int keySize = key.getLength();
int valueSize = value.getLength();
int metadataSize = metadataBytes.length;
long totalSize = 8 + HEADER_LENGTH + keySize + metadataSize + valueSize;
long memoryAddress;
long memoryOffset;
// Eviction requires an additional memory pointer at the beginning that points to
// its linked node
if (evictionEnabled) {
memoryAddress = allocator.allocate(totalSize + 8);
memoryOffset = memoryAddress + 8;
} else {
memoryAddress = allocator.allocate(totalSize);
memoryOffset = memoryAddress;
}
int offset = 0;
byte[] header = new byte[HEADER_LENGTH];
Bits.putInt(header, offset, key.hashCode());
offset += 4;
Bits.putInt(header, offset, key.getLength());
offset += 4;
Bits.putInt(header, offset, metadataBytes.length);
offset += 4;
Bits.putInt(header, offset, value.getLength());
offset += 4;
header[offset++] = type;
// Write the empty linked address pointer first
UNSAFE.putLong(memoryOffset, 0);
memoryOffset += 8;
UNSAFE.copyMemory(header, BYTE_ARRAY_BASE_OFFSET, null, memoryOffset, HEADER_LENGTH);
memoryOffset += HEADER_LENGTH;
UNSAFE.copyMemory(key.getBytes(), key.backArrayOffset() + BYTE_ARRAY_BASE_OFFSET, null, memoryOffset, keySize);
memoryOffset += keySize;
UNSAFE.copyMemory(metadataBytes, BYTE_ARRAY_BASE_OFFSET, null, memoryOffset, metadataSize);
memoryOffset += metadataSize;
UNSAFE.copyMemory(value.getBytes(), value.backArrayOffset() + BYTE_ARRAY_BASE_OFFSET, null, memoryOffset,
valueSize);
return memoryAddress;
}
@Override
public long determineSize(long address) {
int beginningOffset = evictionEnabled ? 16 : 8;
byte[] header = readHeader(beginningOffset + address);
int keyLength = Bits.getInt(header, 4);
int metadataLength = Bits.getInt(header, 8);
int valueLength = Bits.getInt(header, 12);
return beginningOffset + HEADER_LENGTH + keyLength + metadataLength + valueLength;
}
@Override
public long getNextLinkedPointerAddress(long address) {
return UNSAFE.getLong(evictionEnabled ? address + 8 : address);
}
@Override
public void updateNextLinkedPointerAddress(long address, long value) {
UNSAFE.putLong(evictionEnabled ? address + 8 : address, value);
}
@Override
public int getHashCodeForAddress(long address) {
// 8 bytes for eviction if needed (optional)
// 8 bytes for linked pointer
byte[] header = readHeader(evictionEnabled ? address + 16 : address + 8);
return Bits.getInt(header, 0);
}
/**
* Assumes the address doesn't contain the linked pointer at the beginning
* @param address the address to read the entry from
* @return the entry at the memory location
*/
@Override
public InternalCacheEntry<WrappedBytes, WrappedBytes> fromMemory(long address) {
address += (evictionEnabled ? 16 : 8);
byte[] header = readHeader(address);
int offset = 0;
int hashCode = Bits.getInt(header, offset);
offset += 4;
byte[] keyBytes = new byte[Bits.getInt(header, offset)];
offset += 4;
byte[] metadataBytes = new byte[Bits.getInt(header, offset)];
offset += 4;
byte[] valueBytes = new byte[Bits.getInt(header, offset)];
offset += 4;
byte metadataType = header[offset++];
long memoryOffset = address + offset;
UNSAFE.copyMemory(null, memoryOffset, keyBytes, BYTE_ARRAY_BASE_OFFSET, keyBytes.length);
memoryOffset += keyBytes.length;
UNSAFE.copyMemory(null, memoryOffset, metadataBytes, BYTE_ARRAY_BASE_OFFSET, metadataBytes.length);
memoryOffset += metadataBytes.length;
UNSAFE.copyMemory(null, memoryOffset, valueBytes, BYTE_ARRAY_BASE_OFFSET, valueBytes.length);
memoryOffset += valueBytes.length;
Metadata metadata;
// This is a custom metadata
if ((metadataType & 1) == 1) {
try {
metadata = (Metadata) marshaller.objectFromByteBuffer(metadataBytes);
} catch (IOException | ClassNotFoundException e) {
throw new CacheException(e);
}
return internalEntryFactory.create(new WrappedByteArray(keyBytes),
new WrappedByteArray(valueBytes), metadata);
} else {
long lifespan;
long maxIdle;
long created;
long lastUsed;
offset = 0;
boolean hasVersion = (metadataType & 2) == 2;
// Ignore CUSTOM and VERSION to find type
switch (metadataType & 0xFC) {
case IMMORTAL:
lifespan = -1;
maxIdle = -1;
created = -1;
lastUsed = -1;
break;
case MORTAL:
maxIdle = -1;
lifespan = Bits.getLong(metadataBytes, offset++);
created = Bits.getLong(metadataBytes, offset += 8);
lastUsed = -1;
break;
case TRANSIENT:
lifespan = -1;
maxIdle = Bits.getLong(metadataBytes, offset++);
created = -1;
lastUsed = Bits.getLong(metadataBytes, offset += 8);
break;
case TRANSIENT_MORTAL:
lifespan = Bits.getLong(metadataBytes, offset++);
maxIdle = Bits.getLong(metadataBytes, offset += 8);
created = Bits.getLong(metadataBytes, offset += 8);
lastUsed = Bits.getLong(metadataBytes, offset += 8);
break;
default:
throw new IllegalArgumentException("Unsupported type: " + metadataType);
}
if (hasVersion) {
try {
EntryVersion version = (EntryVersion) marshaller.objectFromByteBuffer(metadataBytes, offset,
metadataBytes.length - offset);
return internalEntryFactory.create(new WrappedByteArray(keyBytes, hashCode),
new WrappedByteArray(valueBytes), version, created, lifespan, lastUsed, maxIdle);
} catch (IOException | ClassNotFoundException e) {
throw new CacheException(e);
}
} else {
return internalEntryFactory.create(new WrappedByteArray(keyBytes, hashCode),
new WrappedByteArray(valueBytes), (Metadata) null, created, lifespan, lastUsed, maxIdle);
}
}
}
@Override
public WrappedBytes getKey(long address) {
address += (evictionEnabled ? 16 : 8);
byte[] header = readHeader(address);
int keyLength = Bits.getInt(header, 4);
byte[] keyBytes = new byte[keyLength];
UNSAFE.copyMemory(null, address + HEADER_LENGTH, keyBytes, BYTE_ARRAY_BASE_OFFSET, keyBytes.length);
return new WrappedByteArray(keyBytes);
}
private byte[] readHeader(long address) {
byte[] header = new byte[HEADER_LENGTH];
UNSAFE.copyMemory(null, address, header, BYTE_ARRAY_BASE_OFFSET, header.length);
return header;
}
/**
* Assumes the address points to the entry excluding the pointer reference at the beginning
* @param address the address of an entry to read
* @param wrappedBytes the key to check if it equals
* @return whether the key and address are equal
*/
@Override
public boolean equalsKey(long address, WrappedBytes wrappedBytes) {
address += evictionEnabled ? 16 : 8;
byte[] header = readHeader(address);
int hashCode = wrappedBytes.hashCode();
if (hashCode != Bits.getInt(header, 0)) {
return false;
}
int keyLength = Bits.getInt(header, 4);
byte[] keyBytes = new byte[keyLength];
UNSAFE.copyMemory(null, address + HEADER_LENGTH, keyBytes,
BYTE_ARRAY_BASE_OFFSET, keyLength);
return new WrappedByteArray(keyBytes, hashCode).equalsWrappedBytes(wrappedBytes);
}
}