@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); } } }
/** * 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 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); }
/** * Method that skips invocation if: - No store defined or, - The context contains * Flag.SKIP_CACHE_STORE or, - The store is a shared one and node storing the key is not the 1st * owner of the key or, - This is an L1 put operation. */ private boolean skip(InvocationContext ctx, Object key, FlagAffectedCommand command) { return skip(ctx, command) || skipKey(key) || (isUsingLockDelegation && !cdl.localNodeIsPrimaryOwner(key) && (!cdl.localNodeIsOwner(key) || ctx.isOriginLocal())); }
private void processWriteException( InvocationContext ctx, GlobalTransaction globalTransaction, Throwable throwable) { if (!ctx.isOriginLocal()) return; ExtendedStatistic stat = null; if (throwable instanceof TimeoutException) { if (isLockTimeout(((TimeoutException) throwable))) { stat = NUM_LOCK_FAILED_TIMEOUT; } } else if (throwable instanceof DeadlockDetectedException) { stat = NUM_LOCK_FAILED_DEADLOCK; } else if (throwable instanceof WriteSkewException) { stat = NUM_WRITE_SKEW; } else if (throwable instanceof RemoteException) { Throwable cause = throwable.getCause(); while (cause != null) { if (cause instanceof TimeoutException) { stat = NUM_LOCK_FAILED_TIMEOUT; break; } else if (cause instanceof DeadlockDetectedException) { stat = NUM_LOCK_FAILED_DEADLOCK; break; } else if (cause instanceof WriteSkewException) { stat = NUM_WRITE_SKEW; break; } cause = cause.getCause(); } } if (stat != null) { cacheStatisticManager.increment(stat, globalTransaction, true); } }
@Override protected boolean skipLoadForFunctionalWriteCommand( WriteCommand cmd, Object key, InvocationContext ctx) { // the custom loading behaviour for functional commands happens only in DIST mode: if (distributed // TODO: functional API is not yet implemented for TX mode && !transactional) { /* * Functional API in DIST: if we're the originator, we load only when * we're the primary owner because the primary owner is responsible for * replicating the command to other owners - and if we're not the * primary owner, we forward it to one. And if we're not the * originator, then this is either forwarded from non-primary owner, or * replicated by primary owner to secondary owners, and the semantics * of Functional API require that we must load. */ if ((ctx.isOriginLocal() ? !cdl.localNodeIsPrimaryOwner(key) : !cdl.localNodeIsOwner(key)) // TODO Do we replicate CACHE_MODE_LOCAL commands? && !cmd.hasFlag(Flag.CACHE_MODE_LOCAL)) { if (trace) { log.tracef( "Skip load for functional command %s. This node is not an owner of %s", cmd, key); } return true; } else { // we can short-circuit here for we must load and no other condition will change it return false; } } return super.skipLoadForFunctionalWriteCommand(cmd, key, ctx); }
private Object handleWriteCommand(InvocationContext ctx, WriteCommand command) throws Throwable { if (ctx.isOriginLocal() && !isMaster()) { throw new IllegalStateException( "Write operation not allowed in Passive Replication, in backup nodes"); } return invokeNextInterceptor(ctx, command); }
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); }
/** Don't forward in the case of clear commands, just acquire local locks and broadcast. */ @Override public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable { if (ctx.isOriginLocal() && !isLocalModeForced(command)) { rpcManager.broadcastRpcCommand(command, isSynchronous(command)); } return invokeNextInterceptor(ctx, command); }
@Override public void notifyCacheEntryModified( Object key, Object value, boolean created, boolean pre, InvocationContext ctx, FlagAffectedCommand command) { if (!cacheEntryModifiedListeners.isEmpty()) { boolean originLocal = ctx.isOriginLocal(); EventImpl<Object, Object> e = EventImpl.createEvent(cache, CACHE_ENTRY_MODIFIED); e.setOriginLocal(originLocal); e.setValue(value); e.setPre(pre); e.setKey(key); // Even if CacheEntryCreatedEvent.getValue() has been added, to // avoid breaking old behaviour and make it easy to comply with // JSR-107 specification TCK, it's necessary to find out whether a // modification is the result of a cache entry being created or not. // This is needed because on JSR-107, a modification is only fired // when the entry is updated, and only one event is fired, so you // want to fire it when isPre=false. e.setCreated(created); setTx(ctx, e); for (ListenerInvocation listener : cacheEntryModifiedListeners) listener.invoke(e); } }
@Override protected boolean isProperWriter(InvocationContext ctx, FlagAffectedCommand command, Object key) { if (command.hasFlag(Flag.SKIP_OWNERSHIP_CHECK)) return true; if (loaderConfig.shared()) { if (!dm.getPrimaryLocation(key).equals(address)) { log.tracef( "Skipping cache store since the cache loader is shared " + "and the caller is not the first owner of the key %s", key); return false; } } else { if (isUsingLockDelegation && !command.hasFlag(Flag.CACHE_MODE_LOCAL)) { if (ctx.isOriginLocal() && !dm.getPrimaryLocation(key).equals(address)) { // The command will be forwarded back to the originator, and the value will be stored then // (while holding the lock on the primary owner). log.tracef( "Skipping cache store on the originator because it is not the primary owner " + "of key %s", key); return false; } } if (!dm.getWriteConsistentHash().isKeyLocalToNode(address, key)) { log.tracef("Skipping cache store since the key is not local: %s", key); return false; } } return true; }
@Override public BasicInvocationStage visitGetKeyValueCommand( InvocationContext ctx, GetKeyValueCommand command) throws Throwable { if (streamSummaryContainer.isEnabled() && ctx.isOriginLocal()) { streamSummaryContainer.addGet(command.getKey(), command.getRemotelyFetchedValue() != null); } return invokeNext(ctx, command); }
@Override public BasicInvocationStage visitPutKeyValueCommand( InvocationContext ctx, PutKeyValueCommand command) throws Throwable { if (streamSummaryContainer.isEnabled() && ctx.isOriginLocal()) { streamSummaryContainer.addPut(command.getKey(), isRemote(command.getKey())); } return invokeNext(ctx, command).handle(writeSkewReturnHandler); }
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 protected boolean shouldModifyIndexes(FlagAffectedCommand command, InvocationContext ctx) { // will index only local updates that were not flagged with SKIP_INDEXING and are not caused // internally by state transfer return ctx.isOriginLocal() && !command.hasFlag(Flag.SKIP_INDEXING) && !command.hasFlag(Flag.PUT_FOR_STATE_TRANSFER); }
@Override public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable { Object retval = invokeNextInterceptor(ctx, command); if (!isLocalModeForced(ctx)) { // just broadcast the clear command - this is simplest! if (ctx.isOriginLocal()) rpcManager.broadcastRpcCommand(command, defaultSynchronous, false); } return retval; }
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; }
@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); }
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; }
@Override public BasicInvocationStage visitGetAllCommand(InvocationContext ctx, GetAllCommand command) throws Throwable { if (streamSummaryContainer.isEnabled() && ctx.isOriginLocal()) { for (Object key : command.getKeys()) { streamSummaryContainer.addGet(key, command.getRemotelyFetched().containsKey(key)); } } return invokeNext(ctx, command); }
@Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { if (ctx.isOriginLocal()) { Entity entity = extractEntity(command.getValue()); EntryVersion entryVersion = getEntryVersion(ctx, command.getKey()); applyVersion(entity, entryVersion); } return invokeNextInterceptor(ctx, command); }
@Override public void notifyTransactionRegistered( GlobalTransaction globalTransaction, InvocationContext ctx) { if (!transactionRegisteredListeners.isEmpty()) { boolean isOriginLocal = ctx.isOriginLocal(); EventImpl<Object, Object> e = EventImpl.createEvent(cache, TRANSACTION_REGISTERED); e.setOriginLocal(isOriginLocal); e.setTransactionId(globalTransaction); for (ListenerInvocation listener : transactionRegisteredListeners) listener.invoke(e); } }
@Override public void notifyTransactionCompleted( GlobalTransaction transaction, boolean successful, InvocationContext ctx) { if (!transactionCompletedListeners.isEmpty()) { boolean isOriginLocal = ctx.isOriginLocal(); EventImpl<Object, Object> e = EventImpl.createEvent(cache, TRANSACTION_COMPLETED); e.setOriginLocal(isOriginLocal); e.setTransactionId(transaction); e.setTransactionSuccessful(successful); for (ListenerInvocation listener : transactionCompletedListeners) listener.invoke(e); } }
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))); } }
@Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { if (ctx.isOriginLocal()) { Map<Object, Object> map = command.getMap(); for (Map.Entry<Object, Object> entry : map.entrySet()) { Entity entity = extractEntity(entry.getValue()); EntryVersion entryVersion = getEntryVersion(ctx, entry.getKey()); applyVersion(entity, entryVersion); } } return invokeNextInterceptor(ctx, command); }
@Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { SingleKeyRecipientGenerator skrg = new SingleKeyRecipientGenerator(command.getKey()); Object returnValue = handleWriteCommand(ctx, command, skrg, false, false); // If this was a remote put record that which sent it if (isL1CacheEnabled && !ctx.isOriginLocal() && !skrg.generateRecipients().contains(ctx.getOrigin())) l1Manager.addRequestor(command.getKey(), ctx.getOrigin()); return returnValue; }
@Override public void accept( InvocationContext rCtx, VisitableCommand rCommand, Object rv, Throwable throwable) throws Throwable { if (throwable instanceof WriteSkewException) { WriteSkewException wse = (WriteSkewException) throwable; Object key = wse.getKey(); if (streamSummaryContainer.isEnabled() && key != null && rCtx.isOriginLocal()) { streamSummaryContainer.addWriteSkewFailed(key); } throw wse; } }
@Override public void notifyCacheEntryActivated( Object key, Object value, boolean pre, InvocationContext ctx, FlagAffectedCommand command) { if (isNotificationAllowed(command, cacheEntryActivatedListeners)) { boolean originLocal = ctx.isOriginLocal(); EventImpl<Object, Object> e = EventImpl.createEvent(cache, CACHE_ENTRY_ACTIVATED); e.setOriginLocal(originLocal); e.setPre(pre); e.setKey(key); e.setValue(value); setTx(ctx, e); for (ListenerInvocation listener : cacheEntryActivatedListeners) listener.invoke(e); } }
/** * For non-tx write commands, we retry the command locally if the topology changed, instead of * forwarding to the new owners like we do for tx commands. But we only retry on the originator, * and only if the command doesn't have the {@code CACHE_MODE_LOCAL} flag. */ private Object handleNonTxWriteCommand(InvocationContext ctx, WriteCommand command) throws Throwable { log.tracef("handleNonTxWriteCommand for command %s", command); if (isLocalOnly(ctx, command)) { return invokeNextInterceptor(ctx, command); } updateTopologyId(command); // Only catch OutdatedTopologyExceptions on the originator if (!ctx.isOriginLocal()) { return invokeNextInterceptor(ctx, command); } int commandTopologyId = command.getTopologyId(); Object localResult; try { localResult = invokeNextInterceptor(ctx, command); return localResult; } catch (CacheException e) { Throwable ce = e; while (ce instanceof RemoteException) { ce = ce.getCause(); } if (!(ce instanceof OutdatedTopologyException)) throw e; log.tracef("Retrying command because of topology change: %s", command); // We increment the topology id so that updateTopologyIdAndWaitForTransactionData waits for // the next topology. // Without this, we could retry the command too fast and we could get the // OutdatedTopologyException again. int newTopologyId = Math.max(stateTransferManager.getCacheTopology().getTopologyId(), commandTopologyId + 1); command.setTopologyId(newTopologyId); stateTransferLock.waitForTransactionData( newTopologyId, transactionDataTimeout, TimeUnit.MILLISECONDS); localResult = handleNonTxWriteCommand(ctx, command); } // We retry the command every time the topology changes, either in // NonTxConcurrentDistributionInterceptor or in // EntryWrappingInterceptor. So we don't need to forward the command again here (without holding // a lock). // stateTransferManager.forwardCommandIfNeeded(command, command.getAffectedKeys(), // ctx.getOrigin(), false); return localResult; }
@Override public void notifyCacheEntryActivated( K key, V value, boolean pre, InvocationContext ctx, FlagAffectedCommand command) { if (isNotificationAllowed(command, cacheEntryActivatedListeners)) { boolean originLocal = ctx.isOriginLocal(); EventImpl<K, V> e = EventImpl.createEvent(cache, CACHE_ENTRY_ACTIVATED); e.setOriginLocal(originLocal); e.setPre(pre); e.setKey(key); e.setValue(value); setTx(ctx, e); boolean isLocalNodePrimaryOwner = clusteringDependentLogic.localNodeIsPrimaryOwner(key); for (CacheEntryListenerInvocation<K, V> listener : cacheEntryActivatedListeners) listener.invoke(e, isLocalNodePrimaryOwner); } }