All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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> 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 filter,
         BiConsumer> action) throws InterruptedException {
      executeTask(ice -> {
         if (filter.accept(ice.getKey())) {
            action.accept(ice.getKey(), ice);
         }
      });
   }

   @Override
   public void executeTask(KeyValueFilter filter,
         BiConsumer> 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