private boolean needsRemoteGet(InvocationContext ctx, Object key, boolean retvalCheck) { final CacheEntry entry; return retvalCheck && !ctx.hasFlag(Flag.CACHE_MODE_LOCAL) && !ctx.hasFlag(Flag.SKIP_REMOTE_LOOKUP) && ((entry = ctx.lookupEntry(key)) == null || entry.isNull() || entry.isLockPlaceholder()); }
/** * Simply check if there is an ongoing tx. * * <ul> * <li>If there is one, this is a no-op and just passes the call up the chain. * <li>If there isn't one and there is a batch in progress, resume the batch's tx, pass up, and * finally suspend the batch's tx. * <li>If there is no batch in progress, just pass the call up the chain. * </ul> */ @Override protected Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable { Transaction tx; if (!ctx.isOriginLocal()) return invokeNextInterceptor(ctx, command); // if in a batch, attach tx if (transactionManager.getTransaction() == null && (tx = batchContainer.getBatchTransaction()) != null) { try { transactionManager.resume(tx); // If there's no ongoing tx then BatchingInterceptor creates one and then invokes next // interceptor, // so that all interceptors in the stack will be executed in a transactional context. // This is where a new context (TxInvocationContext) is created, as the existing context is // not transactional: NonTxInvocationContext. InvocationContext txContext = icc.createInvocationContext(true, -1); txContext.setFlags(ctx.getFlags()); return invokeNextInterceptor(txContext, command); } finally { if (transactionManager.getTransaction() != null && batchContainer.isSuspendTxAfterInvocation()) transactionManager.suspend(); } } else { return invokeNextInterceptor(ctx, command); } }
@Override public final CacheEntry wrapEntryForReading(InvocationContext ctx, Object key) throws InterruptedException { CacheEntry cacheEntry = getFromContext(ctx, key); if (cacheEntry == null) { cacheEntry = getFromContainer(key); // do not bother wrapping though if this is not in a tx. repeatable read etc are all // meaningless unless there is a tx. if (useRepeatableRead) { MVCCEntry mvccEntry = cacheEntry == null ? createWrappedEntry(key, null, null, false, false, -1) : createWrappedEntry( key, cacheEntry.getValue(), cacheEntry.getVersion(), false, false, cacheEntry.getLifespan()); if (mvccEntry != null) ctx.putLookedUpEntry(key, mvccEntry); return mvccEntry; } else if (cacheEntry != null) { // if not in transaction and repeatable read, or simply read committed // (regardless of whether in TX or not), do not wrap ctx.putLookedUpEntry(key, cacheEntry); } return cacheEntry; } return cacheEntry; }
@Override public CompletableFuture<Void> visitReadOnlyKeyCommand( InvocationContext ctx, ReadOnlyKeyCommand command) throws Throwable { try { CacheEntry entry = entryFactory.wrapEntryForReading(ctx, command.getKey(), null); // Null entry is often considered to mean that entry is not available // locally, but if there's no need to get remote, the read-only // function needs to be executed, so force a non-null entry in // context with null content if (entry == null && cdl.localNodeIsOwner(command.getKey())) { entryFactory.wrapEntryForReading(ctx, command.getKey(), NullCacheEntry.getInstance()); } return ctx.shortCircuit(ctx.forkInvocationSync(command)); } finally { // needed because entries might be added in L1 if (!ctx.isInTxScope()) commitContextEntries(ctx, command, null); else { CacheEntry entry = ctx.lookupEntry(command.getKey()); if (entry != null) { entry.setSkipLookup(true); } } } }
private InvocationContext setInvocationContextFlagsAndClassLoader( InvocationContext ctx, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { if (explicitFlags != null) ctx.setFlags(explicitFlags); if (explicitClassLoader != null) ctx.setClassLoader(explicitClassLoader); return ctx; }
@Override public Object perform(InvocationContext ctx) throws Throwable { MVCCEntry e = (MVCCEntry) ctx.lookupEntry(key); if (e != null) { if (ctx.isOriginLocal()) { // ISPN-514 if (e.isNull() || e.getValue() == null) { // Revert assumption that new value is to be committed e.setChanged(false); return returnValue(null, false, ctx); } } if (oldValue == null || oldValue.equals(e.getValue()) || ignorePreviousValue) { e.setChanged(true); Object old = e.setValue(newValue); e.setLifespan(lifespanMillis); e.setMaxIdle(maxIdleTimeMillis); e.setLoaded(false); return returnValue(old, true, ctx); } // Revert assumption that new value is to be committed e.setChanged(false); } return returnValue(null, false, ctx); }
@SuppressWarnings("unchecked") final V get(Object key, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextForRead(null, explicitFlags, explicitClassLoader); GetKeyValueCommand command = commandsFactory.buildGetKeyValueCommand(key, ctx.getFlags()); return (V) invoker.invoke(ctx, command); }
private Future<?> flushL1Caches(InvocationContext ctx) { // TODO how do we tell the L1 manager which keys are removed and which keys may still exist in // remote L1? return isL1CacheEnabled ? l1Manager.flushCacheWithSimpleFuture(ctx.getLockedKeys(), null, ctx.getOrigin(), true) : null; }
private Object handleTopologyAffectedCommand( InvocationContext ctx, VisitableCommand command, Address origin, boolean sync) throws Throwable { log.tracef("handleTopologyAffectedCommand for command %s", command); if (isLocalOnly(ctx, command)) { return invokeNextInterceptor(ctx, command); } updateTopologyId((TopologyAffectedCommand) command); // TODO we may need to skip local invocation for read/write/tx commands if the command is too // old and none of its keys are local Object localResult = invokeNextInterceptor(ctx, command); boolean isNonTransactionalWrite = !ctx.isInTxScope() && command instanceof WriteCommand; boolean isTransactionalAndNotRolledBack = false; if (ctx.isInTxScope()) { isTransactionalAndNotRolledBack = !((TxInvocationContext) ctx).getCacheTransaction().isMarkedForRollback(); } if (isNonTransactionalWrite || isTransactionalAndNotRolledBack) { stateTransferManager.forwardCommandIfNeeded( ((TopologyAffectedCommand) command), getAffectedKeys(ctx, command), origin, sync); } return localResult; }
@Override public CompletableFuture<Void> visitGetKeysInGroupCommand( final InvocationContext ctx, GetKeysInGroupCommand command) throws Throwable { final String groupName = command.getGroupName(); if (!command.isGroupOwner()) { return ctx.continueInvocation(); } final KeyFilter<Object> keyFilter = new CompositeKeyFilter<>( new GroupFilter<>(groupName, groupManager), new CollectionKeyFilter<>(ctx.getLookedUpEntries().keySet())); dataContainer.executeTask( keyFilter, (o, internalCacheEntry) -> { synchronized (ctx) { // the process can be made in multiple threads, so we need to synchronize in the // context. entryFactory.wrapExternalEntry( ctx, internalCacheEntry.getKey(), internalCacheEntry, EntryFactory.Wrap.STORE, false); } }); return ctx.continueInvocation(); }
@Override protected void checkIfKeyRead(InvocationContext context, Object key, VisitableCommand command) { if (command instanceof AbstractDataWriteCommand) { AbstractDataWriteCommand writeCommand = (AbstractDataWriteCommand) command; // keep track is only need in a clustered and transactional environment to perform the write // skew check if (context.isInTxScope() && context.isOriginLocal()) { TxInvocationContext txInvocationContext = (TxInvocationContext) context; if (!writeCommand.hasFlag(Flag.PUT_FOR_STATE_TRANSFER) && writeCommand.isConditional() || !writeCommand.hasFlag(Flag.IGNORE_RETURN_VALUES)) { // State transfer does not show the old value for the application neither with the // IGNORE_RETURN_VALUES. // on other hand, the conditional always read key! txInvocationContext.getCacheTransaction().addReadKey(key); } writeCommand.setPreviousRead(txInvocationContext.getCacheTransaction().keyRead(key)); } } else if (command instanceof GetKeyValueCommand) { if (context.isInTxScope() && context.isOriginLocal()) { // always show the value to the application TxInvocationContext txInvocationContext = (TxInvocationContext) context; txInvocationContext.getCacheTransaction().addReadKey(key); } } }
@Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { Object returnValue = invokeNextInterceptor(ctx, command); if (!isStoreEnabled(command) || ctx.isInTxScope()) return returnValue; Map<Object, Object> map = command.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 && command.isForwarded() && !dm.getPrimaryLocation(key).equals(ctx.getOrigin())) continue; if (isProperWriter(ctx, command, key)) { InternalCacheEntry se = getStoredEntry(key, ctx); store.store(se); log.tracef("Stored entry %s under key %s", se, key); count++; } } if (getStatisticsEnabled()) cacheStores.getAndAdd(count); return returnValue; }
private void configureEvent( EventImpl<K, V> e, K key, V value, boolean pre, InvocationContext ctx, FlagAffectedCommand command, V previousValue, Metadata previousMetadata) { boolean originLocal = ctx.isOriginLocal(); e.setOriginLocal(originLocal); e.setValue(pre ? previousValue : value); e.setPre(pre); e.setOldValue(previousValue); e.setOldMetadata(previousMetadata); CacheEntry entry = ctx.lookupEntry(key); if (entry != null) { e.setMetadata(entry.getMetadata()); } Set<Flag> flags; if (command != null && (flags = command.getFlags()) != null && flags.contains(Flag.COMMAND_RETRY)) { e.setCommandRetried(true); } e.setKey(key); setTx(ctx, e); }
@SuppressWarnings("unchecked") final V remove(Object key, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); RemoveCommand command = commandsFactory.buildRemoveCommand(key, null, ctx.getFlags()); return (V) executeCommandAndCommitIfNeeded(ctx, command); }
final boolean containsKey( Object key, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextForRead(null, explicitFlags, explicitClassLoader); GetKeyValueCommand command = commandsFactory.buildGetKeyValueCommand(key, ctx.getFlags()); Object response = invoker.invoke(ctx, command); return response != null; }
final boolean remove( Object key, Object value, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); RemoveCommand command = commandsFactory.buildRemoveCommand(key, value, ctx.getFlags()); return (Boolean) executeCommandAndCommitIfNeeded(ctx, command); }
final NotifyingFuture<Void> clearAsync( EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); ctx.setUseFutureReturnType(true); ClearCommand command = commandsFactory.buildClearCommand(ctx.getFlags()); return wrapInFuture(executeCommandAndCommitIfNeeded(ctx, command)); }
private boolean isClusterInvocation(InvocationContext ctx) { // If the cache is local, the interceptor should only be enabled in case // of lazy deserialization or when an async store is in place. So, if // any cache store is configured, check whether it'll be skipped return ctx.isOriginLocal() && configuration.getCacheMode().isClustered() && !ctx.hasFlag(Flag.CACHE_MODE_LOCAL); }
@Override public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable { // Clear is not key specific, so take into account origin of call if ((ctx.isOriginLocal() || !loaderConfig.shared()) && !skip(ctx, command) && !ctx.isInTxScope()) clearCacheStore(); return invokeNextInterceptor(ctx, command); }
final NotifyingFuture<Boolean> removeAsync( Object key, Object value, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); ctx.setUseFutureReturnType(true); RemoveCommand command = commandsFactory.buildRemoveCommand(key, value, ctx.getFlags()); return wrapInFuture(executeCommandAndCommitIfNeeded(ctx, command)); }
private Object markTxForRollbackAndRethrow(InvocationContext ctx, Throwable te) throws Throwable { if (ctx.isOriginLocal() && ctx.isInTxScope()) { Transaction transaction = tm.getTransaction(); if (transaction != null && isValidRunningTx(transaction)) { transaction.setRollbackOnly(); } } throw te; }
private Object invokeNextAndApplyChanges( InvocationContext ctx, FlagAffectedCommand command, Metadata metadata) throws Throwable { final Object result = ctx.forkInvocationSync(command); if (!ctx.isInTxScope()) { stateTransferLock.acquireSharedTopologyLock(); try { // We only retry non-tx write commands if (command instanceof WriteCommand) { WriteCommand writeCommand = (WriteCommand) command; // Can't perform the check during preload or if the cache isn't clustered boolean isSync = (cacheConfiguration.clustering().cacheMode().isSynchronous() && !command.hasFlag(Flag.FORCE_ASYNCHRONOUS)) || command.hasFlag(Flag.FORCE_SYNCHRONOUS); if (writeCommand.isSuccessful() && stateConsumer != null && stateConsumer.getCacheTopology() != null) { int commandTopologyId = command.getTopologyId(); int currentTopologyId = stateConsumer.getCacheTopology().getTopologyId(); // TotalOrderStateTransferInterceptor doesn't set the topology id for PFERs. if (isSync && currentTopologyId != commandTopologyId && commandTopologyId != -1) { // If we were the originator of a data command which we didn't own the key at the time // means it // was already committed, so there is no need to throw the OutdatedTopologyException // This will happen if we submit a command to the primary owner and it responds and // then a topology // change happens before we get here if (!ctx.isOriginLocal() || !(command instanceof DataCommand) || ctx.hasLockedKey(((DataCommand) command).getKey())) { if (trace) log.tracef( "Cache topology changed while the command was executing: expected %d, got %d", commandTopologyId, currentTopologyId); // This shouldn't be necessary, as we'll have a fresh command instance when retrying writeCommand.setValueMatcher(writeCommand.getValueMatcher().matcherForRetry()); throw new OutdatedTopologyException( "Cache topology changed while the command was executing: expected " + commandTopologyId + ", got " + currentTopologyId); } } } } commitContextEntries(ctx, command, metadata); } finally { stateTransferLock.releaseSharedTopologyLock(); } } if (trace) log.tracef("The return value is %s", toStr(result)); return result; }
boolean lock( Collection<? extends K> keys, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { if (keys == null || keys.isEmpty()) { throw new IllegalArgumentException("Cannot lock empty list of keys"); } InvocationContext ctx = getInvocationContextForWrite(explicitFlags, explicitClassLoader); LockControlCommand command = commandsFactory.buildLockControlCommand(keys, false, ctx.getFlags()); return (Boolean) invoker.invoke(ctx, command); }
private Object realRemoteGet( InvocationContext ctx, Object key, boolean storeInL1, boolean isWrite) throws Throwable { if (trace) log.tracef("Doing a remote get for key %s", key); boolean acquireRemoteLock = false; if (ctx.isInTxScope()) { TxInvocationContext txContext = (TxInvocationContext) ctx; acquireRemoteLock = isWrite && isPessimisticCache && !txContext.getAffectedKeys().contains(key); } // attempt a remote lookup InternalCacheEntry ice = dm.retrieveFromRemoteSource(key, ctx, acquireRemoteLock); if (acquireRemoteLock) { ((TxInvocationContext) ctx).addAffectedKey(key); } if (ice != null) { if (storeInL1) { if (isL1CacheEnabled) { if (trace) log.tracef("Caching remotely retrieved entry for key %s in L1", key); // This should be fail-safe try { long lifespan = ice.getLifespan() < 0 ? configuration.getL1Lifespan() : Math.min(ice.getLifespan(), configuration.getL1Lifespan()); PutKeyValueCommand put = cf.buildPutKeyValueCommand( ice.getKey(), ice.getValue(), lifespan, -1, ctx.getFlags()); lockAndWrap(ctx, key, ice); invokeNextInterceptor(ctx, put); } catch (Exception e) { // Couldn't store in L1 for some reason. But don't fail the transaction! log.infof("Unable to store entry %s in L1 cache", key); log.debug("Inability to store in L1 caused by", e); } } else { CacheEntry ce = ctx.lookupEntry(key); if (ce == null || ce.isNull() || ce.isLockPlaceholder() || ce.getValue() == null) { if (ce != null && ce.isChanged()) { ce.setValue(ice.getValue()); } else { if (isWrite) lockAndWrap(ctx, key, ice); else ctx.putLookedUpEntry(key, ice); } } } } else { if (trace) log.tracef("Not caching remotely retrieved entry for key %s in L1", key); } return ice.getValue(); } return null; }
@Override protected ClassLoader getClassLoader() { if (icc != null) { InvocationContext ctx = icc.getInvocationContext(true); if (ctx != null) { ClassLoader cl = ctx.getClassLoader(); if (cl != null) return cl; } } return super.getClassLoader(); }
private Object handleInvalidate(InvocationContext ctx, WriteCommand command, Object... keys) throws Throwable { Object retval = invokeNextInterceptor(ctx, command); if (command.isSuccessful() && !ctx.isInTxScope()) { if (keys != null && keys.length != 0) { return invalidateAcrossCluster( isSynchronous(ctx), ctx, keys, ctx.isUseFutureReturnType(), retval); } } return retval; }
/** * Locks the value for the keys accessed by the command to avoid being override from a remote get. */ private CompletableFuture<Void> setSkipRemoteGetsAndInvokeNextForDataCommand( InvocationContext context, DataWriteCommand command, Metadata metadata) throws Throwable { Object retVal = invokeNextAndApplyChanges(context, command, metadata); if (context.isInTxScope()) { CacheEntry entry = context.lookupEntry(command.getKey()); if (entry != null) { entry.setSkipLookup(true); } } return context.shortCircuit(retVal); }
private void updateTransactionVersion(InvocationContext context) { if (!context.isInTxScope() && !context.isOriginLocal()) { return; } if (context instanceof SingleKeyNonTxInvocationContext) { if (log.isDebugEnabled()) { log.debugf( "Received a SingleKeyNonTxInvocationContext... This should be a single read operation"); } return; } TxInvocationContext txInvocationContext = (TxInvocationContext) context; List<EntryVersion> entryVersionList = new LinkedList<EntryVersion>(); entryVersionList.add(txInvocationContext.getTransactionVersion()); if (log.isTraceEnabled()) { log.tracef( "[%s] Keys read in this command: %s", txInvocationContext.getGlobalTransaction().prettyPrint(), txInvocationContext.getKeysReadInCommand()); } for (InternalGMUCacheEntry internalGMUCacheEntry : txInvocationContext.getKeysReadInCommand().values()) { Object key = internalGMUCacheEntry.getKey(); boolean local = cll.localNodeIsOwner(key); if (log.isTraceEnabled()) { log.tracef( "[%s] Analyze entry [%s]: local?=%s", txInvocationContext.getGlobalTransaction().prettyPrint(), internalGMUCacheEntry, local); } if (txInvocationContext.hasModifications() && !internalGMUCacheEntry.isMostRecent()) { throw new CacheException("Read-Write transaction read an old value and should rollback"); } if (internalGMUCacheEntry.getMaximumTransactionVersion() != null) { entryVersionList.add(internalGMUCacheEntry.getMaximumTransactionVersion()); } txInvocationContext.getCacheTransaction().addReadKey(key); if (local) { txInvocationContext.setAlreadyReadOnThisNode(true); txInvocationContext.addReadFrom(cll.getAddress()); } } if (entryVersionList.size() > 1) { EntryVersion[] txVersionArray = new EntryVersion[entryVersionList.size()]; txInvocationContext.setTransactionVersion( versionGenerator.mergeAndMax(entryVersionList.toArray(txVersionArray))); } }
/** * Locks the value for the keys accessed by the command to avoid being override from a remote get. */ private CompletableFuture<Void> setSkipRemoteGetsAndInvokeNextForPutMapCommand( InvocationContext context, WriteCommand command) throws Throwable { Object retVal = invokeNextAndApplyChanges(context, command, command.getMetadata()); if (context.isInTxScope()) { for (Object key : command.getAffectedKeys()) { CacheEntry entry = context.lookupEntry(key); if (entry != null) { entry.setSkipLookup(true); } } } return context.shortCircuit(retVal); }
public void applyRemoteTxLog(List<WriteCommand> commands) { for (WriteCommand cmd : commands) { try { // this is a remotely originating tx cf.initializeReplicableCommand(cmd, true); InvocationContext ctx = icc.createInvocationContext(); ctx.setFlags(SKIP_REMOTE_LOOKUP, CACHE_MODE_LOCAL, SKIP_SHARED_CACHE_STORE, SKIP_LOCKING); interceptorChain.invoke(ctx, cmd); } catch (Exception e) { log.exceptionWhenReplaying(cmd, e); } } }