package org.infinispan.interceptors.impl; import static org.infinispan.persistence.manager.PersistenceManager.AccessMode.BOTH; import static org.infinispan.persistence.manager.PersistenceManager.AccessMode.PRIVATE; import java.util.Map; import org.infinispan.commands.FlagAffectedCommand; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.commands.write.PutMapCommand; import org.infinispan.commands.write.RemoveCommand; import org.infinispan.commands.write.ReplaceCommand; import org.infinispan.context.InvocationContext; import org.infinispan.context.impl.FlagBitSets; import org.infinispan.distribution.DistributionManager; import org.infinispan.distribution.LocalizedCacheTopology; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Cache store interceptor specific for the distribution and replication cache modes. * * <p>If the cache store is shared, only the primary owner of the key writes to the cache store.</p> * <p>If the cache store is not shared, every owner of a key writes to the cache store.</p> * <p>In non-tx caches, if the originator is an owner, the command is executed there twice. The first time, * ({@code isOriginLocal() == true}) we don't write anything to the cache store; the second time, * the normal rules apply.</p> * <p>For clear operations, either only the originator of the command clears the cache store (if it is * shared), or every node clears its cache store (if it is not shared). Note that in non-tx caches, this * happens without holding a lock on the primary owner of all the keys.</p> * * @author Galder ZamarreƱo * @author Dan Berindei * @since 9.0 */ public class DistCacheWriterInterceptor extends CacheWriterInterceptor { private static final Log log = LogFactory.getLog(DistCacheWriterInterceptor.class); private static final boolean trace = log.isTraceEnabled(); private DistributionManager dm; private boolean isUsingLockDelegation; @Override protected Log getLog() { return log; } @Inject public void inject(DistributionManager dm) { this.dm = dm; } @Start(priority = 25) // after the distribution manager! @SuppressWarnings("unused") protected void start() { super.start(); this.isUsingLockDelegation = !cacheConfiguration.transaction().transactionMode().isTransactional(); } // ---- WRITE commands @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { return invokeNextThenApply(ctx, command, (rCtx, rCommand, rv) -> { PutKeyValueCommand putKeyValueCommand = (PutKeyValueCommand) rCommand; Object key = putKeyValueCommand.getKey(); if (!putKeyValueCommand.hasAnyFlag(FlagBitSets.ROLLING_UPGRADE) && (!isStoreEnabled(putKeyValueCommand) || rCtx.isInTxScope() || !putKeyValueCommand.isSuccessful())) return rv; if (!isProperWriter(rCtx, putKeyValueCommand, putKeyValueCommand.getKey())) return rv; storeEntry(rCtx, key, putKeyValueCommand); if (getStatisticsEnabled()) cacheStores.incrementAndGet(); return rv; }); } @Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { if (!isStoreEnabled(command) || ctx.isInTxScope()) return invokeNext(ctx, command); return invokeNextThenAccept(ctx, command, (rCtx, rCommand, rv) -> { PutMapCommand putMapCommand = (PutMapCommand) rCommand; LocalizedCacheTopology cacheTopology = dm.getCacheTopology(); Map<Object, Object> map = putMapCommand.getMap(); int count = 0; for (Object key : map.keySet()) { // In non-tx mode, a node may receive the same forwarded PutMapCommand many times - but each time // it must write only the keys locked on the primary owner that forwarded the command if (isUsingLockDelegation && putMapCommand.isForwarded() && !cacheTopology.getDistribution(key).primary().equals(rCtx.getOrigin())) continue; if (isProperWriter(rCtx, putMapCommand, key)) { storeEntry(rCtx, key, putMapCommand); count++; } } if (getStatisticsEnabled()) cacheStores.getAndAdd(count); }); } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { return invokeNextThenApply(ctx, command, (rCtx, rCommand, rv) -> { RemoveCommand removeCommand = (RemoveCommand) rCommand; Object key = removeCommand.getKey(); if (!isStoreEnabled(removeCommand) || rCtx.isInTxScope() || !removeCommand.isSuccessful()) return rv; if (!isProperWriter(rCtx, removeCommand, key)) return rv; boolean resp = persistenceManager .deleteFromAllStores(key, skipSharedStores(rCtx, key, removeCommand) ? PRIVATE : BOTH); if (trace) log.tracef("Removed entry under key %s and got response %s from CacheStore", key, resp); return rv; }); } @Override public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { return invokeNextThenApply(ctx, command, (rCtx, rCommand, rv) -> { ReplaceCommand replaceCommand = (ReplaceCommand) rCommand; Object key = replaceCommand.getKey(); if (!isStoreEnabled(replaceCommand) || rCtx.isInTxScope() || !replaceCommand.isSuccessful()) return rv; if (!isProperWriter(rCtx, replaceCommand, replaceCommand.getKey())) return rv; storeEntry(rCtx, key, replaceCommand); if (getStatisticsEnabled()) cacheStores.incrementAndGet(); return rv; }); } @Override protected boolean skipSharedStores(InvocationContext ctx, Object key, FlagAffectedCommand command) { return !dm.getCacheTopology().getDistribution(key).isPrimary() || command.hasAnyFlag(FlagBitSets.SKIP_SHARED_CACHE_STORE); } @Override protected boolean isProperWriter(InvocationContext ctx, FlagAffectedCommand command, Object key) { if (command.hasAnyFlag(FlagBitSets.SKIP_OWNERSHIP_CHECK)) return true; if (isUsingLockDelegation && ctx.isOriginLocal() && !command.hasAnyFlag(FlagBitSets.CACHE_MODE_LOCAL)) { // If the originator is a backup, the command will be forwarded back to it, and the value will be stored then // (while holding the lock on the primary owner). return dm.getCacheTopology().getDistribution(key).isPrimary(); } else { return dm.getCacheTopology().isWriteOwner(key); } } }