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

org.infinispan.interceptors.locking.ClusteringDependentLogic Maven / Gradle / Ivy

package org.infinispan.interceptors.locking;

import static org.infinispan.transaction.impl.WriteSkewHelper.performTotalOrderWriteSkewCheckAndReturnNewVersions;
import static org.infinispan.transaction.impl.WriteSkewHelper.performWriteSkewCheckAndReturnNewVersions;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.infinispan.commands.FlagAffectedCommand;
import org.infinispan.commands.tx.VersionedPrepareCommand;
import org.infinispan.commands.tx.totalorder.TotalOrderPrepareCommand;
import org.infinispan.commands.write.ClearCommand;
import org.infinispan.commands.write.InvalidateCommand;
import org.infinispan.commands.write.RemoveCommand;
import org.infinispan.commands.write.WriteCommand;
import org.infinispan.commons.util.Immutables;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.container.DataContainer;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.entries.ClearCacheEntry;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.container.entries.MVCCEntry;
import org.infinispan.container.versioning.EntryVersionsMap;
import org.infinispan.container.versioning.VersionGenerator;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.FlagBitSets;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.functional.impl.EntryViews;
import org.infinispan.functional.impl.FunctionalNotifier;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.metadata.Metadata;
import org.infinispan.metadata.impl.L1Metadata;
import org.infinispan.notifications.cachelistener.CacheNotifier;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.remoting.transport.LocalModeAddress;
import org.infinispan.statetransfer.CommitManager;
import org.infinispan.statetransfer.StateTransferLock;
import org.infinispan.statetransfer.StateTransferManager;
import org.infinispan.topology.CacheTopology;
import org.infinispan.transaction.impl.WriteSkewHelper;
import org.infinispan.transaction.xa.CacheTransaction;
import org.infinispan.util.TimeService;

/**
 * Abstractization for logic related to different clustering modes: replicated or distributed. This implements the Bridge pattern as described by the GoF: this plays the role of
 * the Implementor and various LockingInterceptors are the Abstraction.
 *
 * @author Mircea Markus
 * @author Pedro Ruivo
 * @deprecated Since 9.0, no longer public API.
 */
@Deprecated
@Scope(Scopes.NAMED_CACHE)
public interface ClusteringDependentLogic {

   enum Commit {
      /**
       * Do not commit the entry.
       */
      NO_COMMIT(false, false),
      /**
       * Commit the entry but this node is not an owner, therefore, listeners should not be fired.
       */
      COMMIT_NON_LOCAL(true, false),
      /**
       * Commit the entry, this is the owner.
       */
      COMMIT_LOCAL(true, true);

      private boolean commit;
      private boolean local;

      Commit(boolean commit, boolean local) {
         this.commit = commit;
         this.local = local;
      }

      public boolean isCommit() {
         return commit;
      }

      public boolean isLocal() {
         return local;
      }
   }

   boolean localNodeIsOwner(Object key);

   boolean localNodeIsPrimaryOwner(Object key);

   Address getPrimaryOwner(Object key);

   void commitEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx,
                    Flag trackFlag, boolean l1Invalidation);

   Commit commitType(FlagAffectedCommand command, InvocationContext ctx, Object key, boolean removed);

   List
getOwners(Collection keys); List
getOwners(Object key); EntryVersionsMap createNewVersionsAndCheckForWriteSkews(VersionGenerator versionGenerator, TxInvocationContext context, VersionedPrepareCommand prepareCommand); Address getAddress(); int getSegmentForKey(Object key); abstract class AbstractClusteringDependentLogic implements ClusteringDependentLogic { protected DataContainer dataContainer; protected CacheNotifier notifier; protected boolean totalOrder; private WriteSkewHelper.KeySpecificLogic keySpecificLogic; protected CommitManager commitManager; protected PersistenceManager persistenceManager; protected TimeService timeService; protected FunctionalNotifier functionalNotifier; @Inject public void init(DataContainer dataContainer, CacheNotifier notifier, Configuration configuration, CommitManager commitManager, PersistenceManager persistenceManager, TimeService timeService, FunctionalNotifier functionalNotifier) { this.dataContainer = dataContainer; this.notifier = notifier; this.totalOrder = configuration.transaction().transactionProtocol().isTotalOrder(); this.keySpecificLogic = initKeySpecificLogic(totalOrder); this.commitManager = commitManager; this.persistenceManager = persistenceManager; this.timeService = timeService; this.functionalNotifier = functionalNotifier; } @Override public EntryVersionsMap createNewVersionsAndCheckForWriteSkews(VersionGenerator versionGenerator, TxInvocationContext context, VersionedPrepareCommand prepareCommand) { return totalOrder ? totalOrderCreateNewVersionsAndCheckForWriteSkews(versionGenerator, context, prepareCommand) : clusteredCreateNewVersionsAndCheckForWriteSkews(versionGenerator, context, prepareCommand); } @Override public final void commitEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx, Flag trackFlag, boolean l1Invalidation) { if (entry instanceof ClearCacheEntry) { //noinspection unchecked commitClearCommand(dataContainer, (ClearCacheEntry) entry, ctx, command); } else { commitSingleEntry(entry, metadata, command, ctx, trackFlag, l1Invalidation); } } private void commitClearCommand(DataContainer dataContainer, ClearCacheEntry cacheEntry, InvocationContext context, FlagAffectedCommand command) { List> copyEntries = new ArrayList<>(dataContainer.entrySet()); cacheEntry.commit(dataContainer, null); for (InternalCacheEntry entry : copyEntries) { notifier.notifyCacheEntryRemoved(entry.getKey(), entry.getValue(), entry.getMetadata(), false, context, command); } } protected abstract void commitSingleEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx, Flag trackFlag, boolean l1Invalidation); @Override public Commit commitType(FlagAffectedCommand command, InvocationContext ctx, Object key, boolean removed) { // ignore locality for removals, even if skipOwnershipCheck is not true if (command != null && command.hasAnyFlag(FlagBitSets.SKIP_OWNERSHIP_CHECK)) { return Commit.COMMIT_LOCAL; } boolean transactional = ctx.isInTxScope() && (command == null || !command.hasAnyFlag(FlagBitSets.PUT_FOR_EXTERNAL_READ)); // When a command is local-mode, it does not get written by replicating origin -> primary -> backup but // when origin == backup it's written right from the original context // ClearCommand is also broadcast to all nodes from originator, and on originator it should remove entries // for which this node is backup owner even though it did not get the removal from primary owner. if (transactional || !ctx.isOriginLocal() || (command != null && (command.hasAnyFlag(FlagBitSets.CACHE_MODE_LOCAL) || command instanceof ClearCommand))) { // During ST, entries whose ownership is lost are invalidated by InvalidateCommand // and at that point we're no longer owners - the only information is that the origin // is local and the entry is removed. if (localNodeIsOwner(key)) { return Commit.COMMIT_LOCAL; } else if (removed) { return Commit.COMMIT_NON_LOCAL; } } else { // in non-tx mode, on backup we don't commit in original context, backup command has its own context. return localNodeIsPrimaryOwner(key) ? Commit.COMMIT_LOCAL : Commit.NO_COMMIT; } return Commit.NO_COMMIT; } protected abstract WriteSkewHelper.KeySpecificLogic initKeySpecificLogic(boolean totalOrder); protected void notifyCommitEntry(boolean created, boolean removed, boolean expired, CacheEntry entry, InvocationContext ctx, FlagAffectedCommand command, Object previousValue, Metadata previousMetadata) { boolean isWriteOnly = (command instanceof WriteCommand) && ((WriteCommand) command).isWriteOnly(); if (removed) { if (command instanceof RemoveCommand) { ((RemoveCommand) command).notify(ctx, previousValue, previousMetadata, false); } else if (command instanceof InvalidateCommand) { notifier.notifyCacheEntryInvalidated(entry.getKey(), entry.getValue(), entry.getMetadata(), false, ctx, command); } else { if (expired) { notifier.notifyCacheEntryExpired(entry.getKey(), previousValue, previousMetadata, ctx); } else { notifier.notifyCacheEntryRemoved(entry.getKey(), previousValue, previousMetadata, false, ctx, command); } // A write-only command only writes and so can't 100% guarantee // to be able to retrieve previous value when removed, so only // send remove event when the command is read-write. if (!isWriteOnly) functionalNotifier.notifyOnRemove(EntryViews.readOnly(entry.getKey(), previousValue, previousMetadata)); functionalNotifier.notifyOnWrite(() -> EntryViews.noValue(entry.getKey())); } } else { // Notify entry event after container has been updated if (created) { notifier.notifyCacheEntryCreated( entry.getKey(), entry.getValue(), entry.getMetadata(), false, ctx, command); // A write-only command only writes and so can't 100% guarantee // that an entry has been created, so only send create event // when the command is read-write. if (!isWriteOnly) functionalNotifier.notifyOnCreate(EntryViews.readOnly(entry)); functionalNotifier.notifyOnWrite(() -> EntryViews.readOnly(entry)); } else { notifier.notifyCacheEntryModified(entry.getKey(), entry.getValue(), entry.getMetadata(), previousValue, previousMetadata, false, ctx, command); // A write-only command only writes and so can't 100% guarantee // that an entry has been created, so only send modify when the // command is read-write. if (!isWriteOnly) functionalNotifier.notifyOnModify( EntryViews.readOnly(entry.getKey(), previousValue, previousMetadata), EntryViews.readOnly(entry)); functionalNotifier.notifyOnWrite(() -> EntryViews.readOnly(entry)); } } } private EntryVersionsMap totalOrderCreateNewVersionsAndCheckForWriteSkews(VersionGenerator versionGenerator, TxInvocationContext context, VersionedPrepareCommand prepareCommand) { if (context.isOriginLocal()) { throw new IllegalStateException("This must not be reached"); } EntryVersionsMap updatedVersionMap = new EntryVersionsMap(); if (!((TotalOrderPrepareCommand) prepareCommand).skipWriteSkewCheck()) { updatedVersionMap = performTotalOrderWriteSkewCheckAndReturnNewVersions(prepareCommand, dataContainer, persistenceManager, versionGenerator, context, keySpecificLogic, timeService); } for (WriteCommand c : prepareCommand.getModifications()) { for (Object k : c.getAffectedKeys()) { if (keySpecificLogic.performCheckOnKey(k)) { if (!updatedVersionMap.containsKey(k)) { updatedVersionMap.put(k, null); } } } } context.getCacheTransaction().setUpdatedEntryVersions(updatedVersionMap); return updatedVersionMap; } private EntryVersionsMap clusteredCreateNewVersionsAndCheckForWriteSkews(VersionGenerator versionGenerator, TxInvocationContext context, VersionedPrepareCommand prepareCommand) { // Perform a write skew check on mapped entries. EntryVersionsMap uv = performWriteSkewCheckAndReturnNewVersions(prepareCommand, dataContainer, persistenceManager, versionGenerator, context, keySpecificLogic, timeService); CacheTransaction cacheTransaction = context.getCacheTransaction(); EntryVersionsMap uvOld = cacheTransaction.getUpdatedEntryVersions(); if (uvOld != null && !uvOld.isEmpty()) { uvOld.putAll(uv); uv = uvOld; } cacheTransaction.setUpdatedEntryVersions(uv); return (uv.isEmpty()) ? null : uv; } } /** * This logic is used in local mode caches. */ class LocalLogic extends AbstractClusteringDependentLogic { private EmbeddedCacheManager cacheManager; @Inject public void init(EmbeddedCacheManager cacheManager) { this.cacheManager = cacheManager; } @Override public boolean localNodeIsOwner(Object key) { return true; } @Override public boolean localNodeIsPrimaryOwner(Object key) { return true; } @Override public Address getPrimaryOwner(Object key) { throw new IllegalStateException("Cannot invoke this method for local caches"); } @Override public List
getOwners(Collection keys) { return null; } @Override public List
getOwners(Object key) { return null; } @Override public Address getAddress() { Address address = cacheManager.getAddress(); if (address == null) { address = LocalModeAddress.INSTANCE; } return address; } @Override public int getSegmentForKey(Object key) { return 0; } @Override protected void commitSingleEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx, Flag trackFlag, boolean l1Invalidation) { // Cache flags before they're reset // TODO: Can the reset be done after notification instead? boolean created = entry.isCreated(); boolean removed = entry.isRemoved(); boolean expired; if (removed && entry instanceof MVCCEntry) { expired = ((MVCCEntry) entry).isExpired(); } else { expired = false; } InternalCacheEntry previousEntry = dataContainer.peek(entry.getKey()); Object previousValue = null; Metadata previousMetadata = null; if (previousEntry != null) { previousValue = previousEntry.getValue(); previousMetadata = previousEntry.getMetadata(); } commitManager.commit(entry, metadata, trackFlag, l1Invalidation); // Notify after events if necessary notifyCommitEntry(created, removed, expired, entry, ctx, command, previousValue, previousMetadata); } @Override public EntryVersionsMap createNewVersionsAndCheckForWriteSkews(VersionGenerator versionGenerator, TxInvocationContext context, VersionedPrepareCommand prepareCommand) { throw new IllegalStateException("Cannot invoke this method for local caches"); } @Override protected WriteSkewHelper.KeySpecificLogic initKeySpecificLogic(boolean totalOrder) { return null; //not used } } /** * This logic is used in invalidation mode caches. */ class InvalidationLogic extends AbstractClusteringDependentLogic { private StateTransferManager stateTransferManager; private RpcManager rpcManager; @Inject public void init(RpcManager rpcManager, StateTransferManager stateTransferManager) { this.rpcManager = rpcManager; this.stateTransferManager = stateTransferManager; } @Override public boolean localNodeIsOwner(Object key) { CacheTopology cacheTopology = stateTransferManager.getCacheTopology(); return cacheTopology == null || cacheTopology.getWriteConsistentHash().isKeyLocalToNode(rpcManager.getAddress(), key); } @Override public boolean localNodeIsPrimaryOwner(Object key) { CacheTopology cacheTopology = stateTransferManager.getCacheTopology(); return cacheTopology == null || cacheTopology.getWriteConsistentHash().locatePrimaryOwner(key).equals(rpcManager.getAddress()); } @Override public Address getPrimaryOwner(Object key) { return stateTransferManager.getCacheTopology().getWriteConsistentHash().locatePrimaryOwner(key); } @Override protected void commitSingleEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx, Flag trackFlag, boolean l1Invalidation) { // Cache flags before they're reset // TODO: Can the reset be done after notification instead? boolean created = entry.isCreated(); boolean removed = entry.isRemoved(); boolean expired; if (removed && entry instanceof MVCCEntry) { expired = ((MVCCEntry) entry).isExpired(); } else { expired = false; } InternalCacheEntry previousEntry = dataContainer.peek(entry.getKey()); Object previousValue = null; Metadata previousMetadata = null; if (previousEntry != null) { previousValue = previousEntry.getValue(); previousMetadata = previousEntry.getMetadata(); } commitManager.commit(entry, metadata, trackFlag, l1Invalidation); // Notify after events if necessary notifyCommitEntry(created, removed, expired, entry, ctx, command, previousValue, previousMetadata); } @Override public List
getOwners(Collection keys) { return null; //todo [anistor] should I actually return this based on current CH? } @Override public List
getOwners(Object key) { return null; } @Override public Address getAddress() { return rpcManager.getAddress(); } @Override public int getSegmentForKey(Object key) { return stateTransferManager.getCacheTopology().getWriteConsistentHash().getSegment(key); } @Override protected WriteSkewHelper.KeySpecificLogic initKeySpecificLogic(boolean totalOrder) { return null; //not used because write skew check is not allowed with invalidation } } /** * This logic is used in replicated mode caches. */ class ReplicationLogic extends InvalidationLogic { private StateTransferLock stateTransferLock; private final WriteSkewHelper.KeySpecificLogic localNodeIsPrimaryOwner = this::localNodeIsPrimaryOwner; @Inject public void init(StateTransferLock stateTransferLock) { this.stateTransferLock = stateTransferLock; } @Override protected void commitSingleEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx, Flag trackFlag, boolean l1Invalidation) { // Don't allow the CH to change (and state transfer to invalidate entries) // between the ownership check and the commit stateTransferLock.acquireSharedTopologyLock(); try { Commit doCommit = commitType(command, ctx, entry.getKey(), entry.isRemoved()); if (doCommit.isCommit()) { boolean created = false; boolean removed = false; boolean expired = false; if (doCommit.isLocal()) { created = entry.isCreated(); removed = entry.isRemoved(); if (removed && entry instanceof MVCCEntry) { expired = ((MVCCEntry) entry).isExpired(); } } InternalCacheEntry previousEntry = dataContainer.peek(entry.getKey()); Object previousValue = null; Metadata previousMetadata = null; if (previousEntry != null) { previousValue = previousEntry.getValue(); previousMetadata = previousEntry.getMetadata(); } commitManager.commit(entry, metadata, trackFlag, l1Invalidation); if (doCommit.isLocal()) { notifyCommitEntry(created, removed, expired, entry, ctx, command, previousValue, previousMetadata); } } } finally { stateTransferLock.releaseSharedTopologyLock(); } } @Override protected WriteSkewHelper.KeySpecificLogic initKeySpecificLogic(boolean totalOrder) { return totalOrder //in total order, all nodes perform the write skew check ? key -> true //in two phase commit, only the primary owner should perform the write skew check : localNodeIsPrimaryOwner; } } /** * This logic is used in distributed mode caches. */ class DistributionLogic extends AbstractClusteringDependentLogic { private DistributionManager dm; private Configuration configuration; private RpcManager rpcManager; private StateTransferLock stateTransferLock; private final WriteSkewHelper.KeySpecificLogic localNodeIsOwner = this::localNodeIsOwner; private final WriteSkewHelper.KeySpecificLogic localNodeIsPrimaryOwner = this::localNodeIsPrimaryOwner; @Inject public void init(DistributionManager dm, Configuration configuration, RpcManager rpcManager, StateTransferLock stateTransferLock) { this.dm = dm; this.configuration = configuration; this.rpcManager = rpcManager; this.stateTransferLock = stateTransferLock; } @Override public boolean localNodeIsOwner(Object key) { return dm.getLocality(key).isLocal(); } @Override public Address getAddress() { return rpcManager.getAddress(); } @Override public int getSegmentForKey(Object key) { return dm.getWriteConsistentHash().getSegment(key); } @Override public boolean localNodeIsPrimaryOwner(Object key) { final Address address = rpcManager.getAddress(); return dm.getPrimaryLocation(key).equals(address); } @Override public Address getPrimaryOwner(Object key) { return dm.getPrimaryLocation(key); } @Override protected void commitSingleEntry(CacheEntry entry, Metadata metadata, FlagAffectedCommand command, InvocationContext ctx, Flag trackFlag, boolean l1Invalidation) { // Don't allow the CH to change (and state transfer to invalidate entries) // between the ownership check and the commit stateTransferLock.acquireSharedTopologyLock(); try { Commit doCommit = commitType(command, ctx, entry.getKey(), entry.isRemoved()); boolean isL1Write = false; if (!doCommit.isCommit() && configuration.clustering().l1().enabled()) { // transform for L1 if (!entry.isRemoved()) { long lifespan; if (metadata != null) { lifespan = metadata.lifespan(); } else { lifespan = entry.getLifespan(); } if (lifespan < 0 || lifespan > configuration.clustering().l1().lifespan()) { Metadata.Builder builder; if (metadata != null) { builder = metadata.builder(); } else { builder = entry.getMetadata().builder(); } metadata = builder .lifespan(configuration.clustering().l1().lifespan()) .build(); metadata = new L1Metadata(metadata); } } isL1Write = true; doCommit = Commit.COMMIT_NON_LOCAL; } if (doCommit.isCommit()) { boolean created = false; boolean removed = false; boolean expired = false; if (doCommit.isLocal()) { created = entry.isCreated(); removed = entry.isRemoved(); if (removed && entry instanceof MVCCEntry) { expired = ((MVCCEntry) entry).isExpired(); } } // TODO use value from the entry InternalCacheEntry previousEntry = dataContainer.peek(entry.getKey()); Object previousValue = null; Metadata previousMetadata = null; if (previousEntry != null) { previousValue = previousEntry.getValue(); previousMetadata = previousEntry.getMetadata(); } if (isL1Write && previousEntry != null && !previousEntry.isL1Entry()) { // don't overwrite non-L1 entry with L1 (e.g. when originator == backup // and therefore we have two contexts on one node) } else { commitManager.commit(entry, metadata, trackFlag, l1Invalidation); if (doCommit.isLocal()) { notifyCommitEntry(created, removed, expired, entry, ctx, command, previousValue, previousMetadata); } } } } finally { stateTransferLock.releaseSharedTopologyLock(); } } @Override public List
getOwners(Collection affectedKeys) { if (affectedKeys.isEmpty()) { return Collections.emptyList(); } return Immutables.immutableListConvert(dm.locateAll(affectedKeys)); } @Override public List
getOwners(Object key) { return Immutables.immutableListConvert(dm.locate(key)); } @Override protected WriteSkewHelper.KeySpecificLogic initKeySpecificLogic(boolean totalOrder) { return totalOrder //in total order, all the owners can perform the write skew check. ? localNodeIsOwner //in two phase commit, only the primary owner should perform the write skew check : localNodeIsPrimaryOwner; } } }