package org.infinispan.container.versioning; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.infinispan.Cache; import org.infinispan.factories.ComponentRegistry; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged; import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent; import org.infinispan.remoting.transport.Address; import org.infinispan.remoting.transport.Transport; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Generates unique numeric versions for both local and clustered environments. * When used on clustered caches, node information is used to guarantee versions * are unique cluster-wide. * * If the cache is configured to be local, the version generated is based * around an atomic counter. On the contrary, if the cache is clustered, the * generated version is composed of: * [view id (2 bytes)][rank (2 bytes)][version counter (4 bytes)], where rank * refers to the position of this node within the view. * * @author Galder ZamarreƱo * @since 5.3 */ public class NumericVersionGenerator implements VersionGenerator { private static final Log log = LogFactory.getLog(NumericVersionGenerator.class); private static final boolean trace = log.isTraceEnabled(); // TODO: Possibly seed version counter on capped System.currentTimeMillis, to avoid issues with clients holding to versions in between restarts final AtomicInteger versionCounter = new AtomicInteger(); final AtomicLong versionPrefix = new AtomicLong(); private static final NumericVersion NON_EXISTING = new NumericVersion(0); private Cache<?, ?> cache; private boolean isClustered; @Inject public void init(Cache<?, ?> cache) { this.cache = cache; } @Start(priority = 11) // after Transport public void start() { SecurityActions.addCacheManagerListener(cache.getCacheManager(), new RankCalculator()); isClustered = SecurityActions.getCacheConfiguration(cache.getAdvancedCache()).clustering().cacheMode().isClustered(); if (isClustered) { // Use component registry to avoid keeping an instance ref simply used on start ComponentRegistry componentRegistry = SecurityActions.getCacheComponentRegistry(cache.getAdvancedCache()); Transport transport = componentRegistry .getGlobalComponentRegistry().getComponent(Transport.class); calculateRank(transport.getAddress(), transport.getMembers(), transport.getViewId()); } } public NumericVersionGenerator clustered(boolean clustered) { isClustered = clustered; return this; } @Override public IncrementableEntryVersion generateNew() { long counter = versionCounter.incrementAndGet(); return createNumericVersion(counter); } private IncrementableEntryVersion createNumericVersion(long counter) { // Version counter occupies the least significant 4 bytes of the version return isClustered ? new NumericVersion(versionPrefix.get() | counter) : new NumericVersion(counter); } @Override public IncrementableEntryVersion increment(IncrementableEntryVersion initialVersion) { if (initialVersion instanceof NumericVersion) { NumericVersion old = (NumericVersion) initialVersion; long counter = old.getVersion() + 1; return createNumericVersion(counter); } throw log.unexpectedInitialVersion(initialVersion.getClass().getName()); } @Override public IncrementableEntryVersion nonExistingVersion() { return NON_EXISTING; } long calculateRank(Address address, List<Address> members, long viewId) { long rank = findAddressRank(address, members, 1); // Version is composed of: <view id (2 bytes)><rank (2 bytes)><version counter (4 bytes)> // View id and rank form the prefix which is updated on a view change. long newVersionPrefix = (viewId << 48) | (rank << 32); // TODO: Deal with compareAndSet failures? versionPrefix.compareAndSet(versionPrefix.get(), newVersionPrefix); return versionPrefix.get(); } void resetCounter() { versionCounter.set(0); } private int findAddressRank(Address address, List<Address> members, int rank) { if (address.equals(members.get(0))) return rank; else return findAddressRank(address, members.subList(1, members.size()), rank + 1); } @Listener public class RankCalculator { @ViewChanged @SuppressWarnings("unused") public void calculateRank(ViewChangedEvent e) { long rank = NumericVersionGenerator.this .calculateRank(e.getLocalAddress(), e.getNewMembers(), e.getViewId()); if (trace) log.tracef("Calculated rank based on view %s and result was %d", e, rank); } } }