org.infinispan.container.offheap.OffHeapDataContainer Maven / Gradle / Ivy
package org.infinispan.container.offheap;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.marshall.WrappedByteArray;
import org.infinispan.commons.marshall.WrappedBytes;
import org.infinispan.container.DataContainer;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.eviction.PassivationManager;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Stop;
import org.infinispan.filter.KeyFilter;
import org.infinispan.filter.KeyValueFilter;
import org.infinispan.metadata.Metadata;
import org.infinispan.util.TimeService;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import sun.misc.Unsafe;
/**
* Data Container implementation that stores entries in native memory (off-heap).
* @author wburns
* @since 9.0
*/
public class OffHeapDataContainer implements DataContainer {
protected final Log log = LogFactory.getLog(getClass());
protected final boolean trace = log.isTraceEnabled();
protected static final UnsafeWrapper UNSAFE = UnsafeWrapper.INSTANCE;
protected final AtomicLong size = new AtomicLong();
protected final int lockCount;
protected final int memoryAddressCount;
protected final StripedLock locks;
protected final MemoryAddressHash memoryLookup;
protected OffHeapMemoryAllocator allocator;
protected OffHeapEntryFactory offHeapEntryFactory;
protected InternalEntryFactory internalEntryFactory;
protected TimeService timeService;
protected PassivationManager passivator;
// Variable to make sure memory locations aren't read after being deallocated
// This variable should always be read first after acquiring either the read or write lock
private boolean dellocated = false;
// Max would be 1:1 ratio with memory addresses - must be a crazy machine to have that many processors
private final static int MAX_LOCK_COUNT = 1 << 30;
static int nextPowerOfTwo(int target) {
int n = target - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : n >= MAX_LOCK_COUNT ? MAX_LOCK_COUNT : n + 1;
}
public OffHeapDataContainer(int desiredSize) {
lockCount = nextPowerOfTwo(Runtime.getRuntime().availableProcessors()) << 1;
int memoryAddresses = desiredSize >= MAX_LOCK_COUNT ? MAX_LOCK_COUNT : lockCount;
while (memoryAddresses < desiredSize) {
memoryAddresses <<= 1;
}
memoryAddressCount = memoryAddresses;
memoryLookup = new MemoryAddressHash(memoryAddressCount);
// Unfortunately desired size directly correlates to lock size
locks = new StripedLock(lockCount);
}
@Inject
public void inject(PassivationManager passivator, OffHeapEntryFactory offHeapEntryFactory,
OffHeapMemoryAllocator allocator, TimeService timeService, InternalEntryFactory internalEntryFactory) {
this.passivator = passivator;
this.internalEntryFactory = internalEntryFactory;
this.allocator = allocator;
this.offHeapEntryFactory = offHeapEntryFactory;
this.timeService = timeService;
}
/**
* Clears the memory lookups and cache data.
*/
@Stop(priority = Integer.MAX_VALUE)
public void deallocate() {
locks.lockAll();
try {
if (size.get() != 0) {
log.warn("Container was not cleared before deallocating memory lookup tables! Memory leak " +
"will have occurred!");
}
clear();
memoryLookup.deallocate();
dellocated = true;
} finally {
locks.unlockAll();
}
}
static WrappedByteArray toWrapper(Object obj) {
if (obj instanceof WrappedByteArray) {
return (WrappedByteArray) obj;
}
throw new IllegalArgumentException("Require WrappedByteArray: got " + obj.getClass());
}
protected void checkDeallocation() {
if (dellocated) {
throw new IllegalStateException("Container was already shut down!");
}
}
@Override
public InternalCacheEntry get(Object k) {
Lock lock = locks.getLock(k).readLock();
lock.lock();
try {
checkDeallocation();
long address = memoryLookup.getMemoryAddress(k);
if (address == 0) {
return null;
}
return performGet(address, k);
} finally {
lock.unlock();
}
}
protected InternalCacheEntry performGet(long address, Object k) {
WrappedBytes wrappedKey = toWrapper(k);
while (address != 0) {
long nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
InternalCacheEntry ice = offHeapEntryFactory.fromMemory(address);
if (wrappedKey.equalsWrappedBytes(ice.getKey())) {
entryRetrieved(address);
return ice;
} else {
address = nextAddress;
}
}
return null;
}
@Override
public InternalCacheEntry peek(Object k) {
return get(k);
}
@Override
public void put(WrappedBytes key, WrappedBytes value, Metadata metadata) {
Lock lock = locks.getLock(key).writeLock();
lock.lock();
try {
checkDeallocation();
long newAddress = offHeapEntryFactory.create(key, value, metadata);
performPut(newAddress, key);
} finally {
lock.unlock();
}
}
/**
* Performs the actual put operation putting the new address into the memory lookups. The write lock for the given
* key must be held before calling this method.
* @param newAddress the address of the new entry
* @param key the key of the entry
*/
protected void performPut(long newAddress, WrappedBytes key) {
long address = memoryLookup.getMemoryAddress(key);
boolean shouldCreate = false;
// Have to start new linked node list
if (address == 0) {
memoryLookup.putMemoryAddress(key, newAddress);
entryCreated(newAddress);
size.incrementAndGet();
} else {
// Whether the key was found or not - short circuit equality checks
boolean foundKey = false;
// Holds the previous linked list address
long prevAddress = 0;
// Keep looping until we get the tail end - we always append the put to the end
while (address != 0) {
long nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
if (!foundKey) {
if (offHeapEntryFactory.equalsKey(address, key)) {
entryReplaced(newAddress, address);
allocator.deallocate(address);
foundKey = true;
// If this is true it means this was the first node in the linked list
if (prevAddress == 0) {
if (nextAddress == 0) {
// This branch is the case where our key is the only one in the linked list
shouldCreate = true;
} else {
// This branch is the case where our key is the first with another after
memoryLookup.putMemoryAddress(key, nextAddress);
}
} else {
// This branch means our node was not the first, so we have to update the address before ours
// to the one we previously referenced
UNSAFE.putLong(prevAddress, nextAddress);
// We purposely don't update prevAddress, because we have to keep it as the current pointer
// since we removed ours
address = nextAddress;
continue;
}
}
}
prevAddress = address;
address = nextAddress;
}
// If we didn't find the key previous, it means we are a new entry
if (!foundKey) {
entryCreated(newAddress);
size.incrementAndGet();
}
if (shouldCreate) {
memoryLookup.putMemoryAddress(key, newAddress);
} else {
// Now prevAddress should be the last link so we fix our link
offHeapEntryFactory.updateNextLinkedPointerAddress(prevAddress, newAddress);
}
}
}
/**
* Invoked when an entry is about to be created. The new address is fully addressable,
* The write lock will already be acquired for the given * segment the key mapped to.
* @param newAddress the address just created that will be the new entry
*/
protected void entryCreated(long newAddress) {
}
/**
* Invoked when an entry is about to be replaced with a new one. The old and new address are both addressable,
* however oldAddress may be freed after this method returns. The write lock will already be acquired for the given
* segment the key mapped to.
* @param newAddress the address just created that will be the new entry
* @param oldAddress the old address for this entry that will be soon removed
*/
protected void entryReplaced(long newAddress, long oldAddress) {
}
/**
* Invoked when an entry is about to be removed. You can read values from this but after this method is completed
* this memory address may be freed. The write lock will already be acquired for the given segment the key mapped to.
* @param removedAddress the address about to be removed
*/
protected void entryRemoved(long removedAddress) {
}
@Override
public boolean containsKey(Object k) {
Lock lock = locks.getLock(k).readLock();
lock.lock();
try {
checkDeallocation();
long address = memoryLookup.getMemoryAddress(k);
if (address == 0) {
return false;
}
WrappedByteArray wba = toWrapper(k);
while (address != 0) {
long nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
if (offHeapEntryFactory.equalsKey(address, wba)) {
return true;
}
address = nextAddress;
}
return false;
} finally {
lock.unlock();
}
}
/**
* Invoked when an entry is successfully retrieved. The read lock will already
* be acquired for the given segment the key mapped to.
* @param entryAddress
*/
protected void entryRetrieved(long entryAddress) {
}
@Override
public InternalCacheEntry remove(Object key) {
Lock lock = locks.getLock(key).writeLock();
lock.lock();
try {
checkDeallocation();
long address = memoryLookup.getMemoryAddress(key);
if (address == 0) {
return null;
}
return performRemove(address, key);
} finally {
lock.unlock();
}
}
/**
* Performs the actual remove operation removing the new address from the memory lookups. The write lock for the given
* key must be held before calling this method.
* @param address the address of the entry to remove
* @param key the key of the entry
*/
protected InternalCacheEntry performRemove(long address, Object key) {
WrappedByteArray wba = toWrapper(key);
long prevAddress = 0;
while (address != 0) {
long nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
InternalCacheEntry ice = offHeapEntryFactory.fromMemory(address);
if (ice.getKey().equals(wba)) {
entryRemoved(address);
// Free the node
allocator.deallocate(address);
if (prevAddress != 0) {
UNSAFE.putLong(prevAddress, nextAddress);
} else {
memoryLookup.putMemoryAddress(key, nextAddress);
}
size.decrementAndGet();
return ice;
}
prevAddress = address;
address = nextAddress;
}
return null;
}
@Override
public int size() {
long time = timeService.time();
long count = entryStream().filter(e -> !e.isExpired(time)).count();
if (count > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) count;
}
@Override
public int sizeIncludingExpired() {
long currentSize = size.get();
if (currentSize > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) currentSize;
}
@Override
public void clear() {
locks.lockAll();
try {
checkDeallocation();
performClear();
} finally {
locks.unlockAll();
}
}
protected void performClear() {
if (trace) {
log.trace("Clearing off heap data");
}
memoryLookup.toStreamRemoved().forEach(address -> {
while (address != 0) {
long nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
allocator.deallocate(address);
address = nextAddress;
}
});
size.set(0);
if (trace) {
log.trace("Cleared off heap data");
}
}
class ValueCollection extends AbstractCollection {
@Override
public Iterator iterator() {
return stream().iterator();
}
@Override
public void forEach(Consumer super WrappedBytes> action) {
stream().forEach(action);
}
@Override
public Spliterator spliterator() {
return stream().spliterator();
}
@Override
public Stream stream() {
return entryStream().map(Map.Entry::getValue);
}
@Override
public Stream parallelStream() {
return stream().parallel();
}
@Override
public int size() {
return OffHeapDataContainer.this.size();
}
}
class KeySet extends ValueCollection implements Set {
@Override
public Stream stream() {
return entryStream().map(Map.Entry::getKey);
}
@Override
public boolean contains(Object o) {
return containsKey(o);
}
}
class EntrySet extends AbstractSet> {
@Override
public Iterator> iterator() {
return stream().iterator();
}
@Override
public int size() {
return OffHeapDataContainer.this.size();
}
@Override
public void forEach(Consumer super InternalCacheEntry> action) {
stream().forEach(action);
}
@Override
public Spliterator> spliterator() {
return stream().spliterator();
}
@Override
public Stream> stream() {
return entryStream();
}
@Override
public Stream> parallelStream() {
return stream().parallel();
}
}
@Override
public Set keySet() {
return new KeySet();
}
@Override
public Collection values() {
return new ValueCollection();
}
@Override
public Set> entrySet() {
return new EntrySet();
}
@Override
public void evict(WrappedBytes key) {
Lock lock = locks.getLock(key).writeLock();
lock.lock();
try {
checkDeallocation();
// TODO: this could be more efficient
passivator.passivate(get(key));
remove(key);
} finally {
lock.unlock();
}
}
@Override
public InternalCacheEntry compute(WrappedBytes key,
ComputeAction action) {
Lock lock = locks.getLock(key).writeLock();
lock.lock();
try {
checkDeallocation();
InternalCacheEntry prev = get(key);
InternalCacheEntry result = action.compute(key, prev, internalEntryFactory);
if (result != null) {
long newAddress = offHeapEntryFactory.create(key, result.getValue(), result.getMetadata());
performPut(newAddress, key);
} else {
remove(key);
}
return result;
} finally {
lock.unlock();
}
}
private void executeTask(Consumer> consumer) {
for (int i = 0; i < lockCount; ++i) {
Lock lock = locks.getLockWithOffset(i).readLock();
lock.lock();
try {
checkDeallocation();
for (int j = i; j < memoryAddressCount; j += lockCount) {
long address = memoryLookup.getMemoryAddressOffset(j);
while (address != 0) {
long nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
InternalCacheEntry ice = offHeapEntryFactory.fromMemory(address);
consumer.accept(ice);
address = nextAddress;
}
}
} finally {
lock.unlock();
}
}
}
@Override
public void executeTask(KeyFilter super WrappedBytes> filter,
BiConsumer super WrappedBytes, InternalCacheEntry> action) throws InterruptedException {
executeTask(ice -> {
if (filter.accept(ice.getKey())) {
action.accept(ice.getKey(), ice);
}
});
}
@Override
public void executeTask(KeyValueFilter super WrappedBytes, ? super WrappedBytes> filter,
BiConsumer super WrappedBytes, InternalCacheEntry> action) throws InterruptedException {
executeTask(ice -> {
if (filter.accept(ice.getKey(), ice.getValue(), ice.getMetadata())) {
action.accept(ice.getKey(), ice);
}
});
}
private Stream> entryStream() {
int limit = memoryAddressCount / lockCount;
return IntStream.range(0, lockCount)
// REALLY REALLY stupid there is no flatMapToObj on IntStream...
.boxed()
.flatMap(l -> {
int value = l;
return LongStream.iterate(value, i -> i + lockCount).limit(limit)
.boxed()
.flatMap(a -> {
Lock lock = locks.getLockWithOffset(value).readLock();
lock.lock();
try {
checkDeallocation();
long address = memoryLookup.getMemoryAddressOffset(a.intValue());
if (address == 0) {
return Stream.empty();
}
Stream.Builder> builder = Stream.builder();
while (address != 0) {
long nextAddress;
do {
nextAddress = offHeapEntryFactory.getNextLinkedPointerAddress(address);
builder.accept(offHeapEntryFactory.fromMemory(address));
} while ((address = nextAddress) != 0);
}
return builder.build();
} finally {
lock.unlock();
}
});
});
}
@Override
public Iterator> iterator() {
long time = timeService.time();
return entryStream().filter(e -> !e.isExpired(time)).iterator();
}
@Override
public Iterator> iteratorIncludingExpired() {
return entryStream().iterator();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy