package org.infinispan.interceptors.locking; import static org.infinispan.commons.util.Util.toStr; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; import org.infinispan.commands.DataCommand; import org.infinispan.commands.FlagAffectedCommand; import org.infinispan.commands.VisitableCommand; import org.infinispan.commands.functional.ReadWriteKeyCommand; import org.infinispan.commands.functional.ReadWriteKeyValueCommand; import org.infinispan.commands.functional.ReadWriteManyCommand; import org.infinispan.commands.functional.ReadWriteManyEntriesCommand; import org.infinispan.commands.functional.WriteOnlyKeyCommand; import org.infinispan.commands.functional.WriteOnlyKeyValueCommand; import org.infinispan.commands.functional.WriteOnlyManyCommand; import org.infinispan.commands.functional.WriteOnlyManyEntriesCommand; import org.infinispan.commands.read.GetCacheEntryCommand; import org.infinispan.commands.read.GetKeyValueCommand; import org.infinispan.commands.write.ClearCommand; import org.infinispan.commands.write.DataWriteCommand; import org.infinispan.commands.write.InvalidateCommand; import org.infinispan.commands.write.InvalidateL1Command; 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.factories.annotations.Inject; import org.infinispan.interceptors.DDAsyncInterceptor; import org.infinispan.interceptors.InvocationFinallyAction; import org.infinispan.util.concurrent.TimeoutException; import org.infinispan.util.concurrent.locks.LockManager; import org.infinispan.util.concurrent.locks.LockUtil; import org.infinispan.util.logging.Log; /** * Base class for various locking interceptors in this package. * * @author Mircea Markus */ public abstract class AbstractLockingInterceptor extends DDAsyncInterceptor { private final boolean trace = getLog().isTraceEnabled(); protected LockManager lockManager; protected ClusteringDependentLogic cdl; protected final InvocationFinallyAction unlockAllReturnHandler = new InvocationFinallyAction() { @Override public void accept(InvocationContext rCtx, VisitableCommand rCommand, Object rv, Throwable throwable) throws Throwable { lockManager.unlockAll(rCtx); } }; protected abstract Log getLog(); @Inject public void setDependencies(LockManager lockManager, ClusteringDependentLogic cdl) { this.lockManager = lockManager; this.cdl = cdl; } @Override public final Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable { return invokeNext(ctx, command); } @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable { return visitDataReadCommand(ctx, command); } @Override public Object visitGetCacheEntryCommand(InvocationContext ctx, GetCacheEntryCommand command) throws Throwable { return visitDataReadCommand(ctx, command); } protected abstract Object visitDataReadCommand(InvocationContext ctx, DataCommand command) throws Throwable; protected abstract Object visitDataWriteCommand(InvocationContext ctx, DataWriteCommand command) throws Throwable; // We need this method in here because of putForExternalRead protected final Object visitNonTxDataWriteCommand(InvocationContext ctx, DataWriteCommand command) throws Throwable { if (hasSkipLocking(command) || !shouldLockKey(command.getKey())) { return invokeNext(ctx, command); } try { lockAndRecord(ctx, command.getKey(), getLockTimeoutMillis(command)); } catch (Throwable t) { lockManager.unlockAll(ctx); throw t; } return invokeNextAndFinally(ctx, command, unlockAllReturnHandler); } @Override public final Object visitInvalidateCommand(InvocationContext ctx, InvalidateCommand command) throws Throwable { if (hasSkipLocking(command)) { return invokeNext(ctx, command); } try { lockAllAndRecord(ctx, Arrays.asList(command.getKeys()), getLockTimeoutMillis(command)); } catch (Throwable t) { lockManager.unlockAll(ctx); } return invokeNextAndFinally(ctx, command, unlockAllReturnHandler); } @Override public final Object visitInvalidateL1Command(InvocationContext ctx, InvalidateL1Command command) throws Throwable { if (command.isCausedByALocalWrite(cdl.getAddress())) { if (trace) getLog().trace("Skipping invalidation as the write operation originated here."); return null; } if (hasSkipLocking(command)) { return invokeNext(ctx, command); } final Object[] keys = command.getKeys(); if (keys == null || keys.length < 1) { return null; } ArrayList<Object> keysToInvalidate = new ArrayList<>(keys.length); for (Object key : keys) { try { lockAndRecord(ctx, key, 0); keysToInvalidate.add(key); } catch (TimeoutException te) { getLog().unableToLockToInvalidate(key, cdl.getAddress()); } } if (keysToInvalidate.isEmpty()) { return null; } command.setKeys(keysToInvalidate.toArray()); return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> { ((InvalidateL1Command) rCommand).setKeys(keys); if (!rCtx.isInTxScope()) lockManager.unlockAll(rCtx); }); } @Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { return handleWriteManyCommand(ctx, command, command.getMap().keySet(), command.isForwarded()); } @Override public Object visitReadWriteKeyValueCommand(InvocationContext ctx, ReadWriteKeyValueCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitReadWriteKeyCommand(InvocationContext ctx, ReadWriteKeyCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitWriteOnlyKeyValueCommand(InvocationContext ctx, WriteOnlyKeyValueCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitWriteOnlyKeyCommand(InvocationContext ctx, WriteOnlyKeyCommand command) throws Throwable { return visitDataWriteCommand(ctx, command); } @Override public Object visitWriteOnlyManyEntriesCommand(InvocationContext ctx, WriteOnlyManyEntriesCommand command) throws Throwable { return handleWriteManyCommand(ctx, command, command.getAffectedKeys(), command.isForwarded()); } @Override public Object visitWriteOnlyManyCommand(InvocationContext ctx, WriteOnlyManyCommand command) throws Throwable { return handleWriteManyCommand(ctx, command, command.getAffectedKeys(), command.isForwarded()); } @Override public Object visitReadWriteManyCommand(InvocationContext ctx, ReadWriteManyCommand command) throws Throwable { return handleWriteManyCommand(ctx, command, command.getAffectedKeys(), command.isForwarded()); } @Override public Object visitReadWriteManyEntriesCommand(InvocationContext ctx, ReadWriteManyEntriesCommand command) throws Throwable { return handleWriteManyCommand(ctx, command, command.getAffectedKeys(), command.isForwarded()); } protected abstract <K> Object handleWriteManyCommand(InvocationContext ctx, FlagAffectedCommand command, Collection<K> keys, boolean forwarded) throws Throwable; protected final long getLockTimeoutMillis(FlagAffectedCommand command) { return command.hasAnyFlag(FlagBitSets.ZERO_LOCK_ACQUISITION_TIMEOUT) ? 0 : cacheConfiguration.locking().lockAcquisitionTimeout(); } protected final boolean shouldLockKey(Object key) { //only the primary owner acquires the lock. boolean shouldLock = LockUtil.isLockOwner(key, cdl); if (trace) getLog().tracef("Are (%s) we the lock owners for key '%s'? %s", cdl.getAddress(), toStr(key), shouldLock); return shouldLock; } protected final void lockAndRecord(InvocationContext context, Object key, long timeout) throws InterruptedException { context.addLockedKey(key); lockManager.lock(key, context.getLockOwner(), timeout, TimeUnit.MILLISECONDS).lock(); } protected final void lockAllAndRecord(InvocationContext context, Stream<?> keys, long timeout) throws InterruptedException { lockAllAndRecord(context, keys.collect(Collectors.toList()), timeout); } protected final void lockAllAndRecord(InvocationContext context, Collection<?> keys, long timeout) throws InterruptedException { keys.forEach(context::addLockedKey); lockManager.lockAll(keys, context.getLockOwner(), timeout, TimeUnit.MILLISECONDS).lock(); } protected final boolean hasSkipLocking(FlagAffectedCommand command) { return command.hasAnyFlag(FlagBitSets.SKIP_LOCKING); } }