/** Special processing required for transaction commands. */ private Object handleTxCommand(TxInvocationContext ctx, TransactionBoundaryCommand command) throws Throwable { // For local commands we may not have a GlobalTransaction yet Address address = ctx.isOriginLocal() ? ctx.getOrigin() : ctx.getGlobalTransaction().getAddress(); return handleTopologyAffectedCommand(ctx, command, address, true); }
@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); } } }
private CompletableFuture<Void> visitSecondPhaseCommand( TxInvocationContext ctx, TransactionBoundaryCommand command, boolean commit, ExtendedStatistic duration, ExtendedStatistic counter) throws Throwable { GlobalTransaction globalTransaction = command.getGlobalTransaction(); if (trace) { log.tracef( "Visit 2nd phase command %s. Is it local? %s. Transaction is %s", command, ctx.isOriginLocal(), globalTransaction.globalId()); } long start = timeService.time(); return ctx.onReturn( (rCtx, rCommand, rv, throwable) -> { if (throwable != null) { throw throwable; } long end = timeService.time(); updateTime(duration, counter, start, end, globalTransaction, rCtx.isOriginLocal()); cacheStatisticManager.setTransactionOutcome( commit, globalTransaction, rCtx.isOriginLocal()); cacheStatisticManager.terminateTransaction(globalTransaction, true, true); return null; }); }
@Override protected void prepareOnAffectedNodes( TxInvocationContext<?> ctx, PrepareCommand command, Collection<Address> recipients) { if (log.isTraceEnabled()) { log.tracef( "Total Order Anycast transaction %s with Total Order", command.getGlobalTransaction().globalId()); } if (!ctx.hasModifications()) { return; } if (!ctx.isOriginLocal()) { throw new IllegalStateException("Expected a local context while TO-Anycast prepare command"); } if (!(command instanceof VersionedPrepareCommand)) { throw new IllegalStateException( "Expected a Versioned Prepare Command in version aware component"); } try { KeysValidateFilter responseFilter = ctx.getCacheTransaction().hasModification(ClearCommand.class) || isSyncCommitPhase() ? null : new KeysValidateFilter(rpcManager.getAddress(), ctx.getAffectedKeys()); totalOrderPrepare(recipients, command, responseFilter); } finally { transactionRemotelyPrepared(ctx); } }
private void invalidateAcrossCluster(boolean synchronous, Object[] keys, InvocationContext ctx) throws Throwable { // increment invalidations counter if statistics maintained incrementInvalidations(); final InvalidateCommand invalidateCommand = commandsFactory.buildInvalidateCommand(InfinispanCollections.<Flag>emptySet(), keys); if (log.isDebugEnabled()) log.debug("Cache [" + rpcManager.getAddress() + "] replicating " + invalidateCommand); ReplicableCommand command = invalidateCommand; if (ctx.isInTxScope()) { TxInvocationContext txCtx = (TxInvocationContext) ctx; // A Prepare command containing the invalidation command in its 'modifications' list is sent // to the remote nodes // so that the invalidation is executed in the same transaction and locks can be acquired and // released properly. // This is 1PC on purpose, as an optimisation, even if the current TX is 2PC. // If the cache uses 2PC it's possible that the remotes will commit the invalidation and the // originator rolls back, // but this does not impact consistency and the speed benefit is worth it. command = commandsFactory.buildPrepareCommand( txCtx.getGlobalTransaction(), Collections.<WriteCommand>singletonList(invalidateCommand), true); } rpcManager.invokeRemotely(null, command, rpcManager.getDefaultRpcOptions(synchronous)); }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { Object retVal = invokeNextInterceptor(ctx, command); boolean sync = isSynchronous(ctx); if (shouldInvokeRemoteTxCommand(ctx)) { int newCacheViewId = -1; stateTransferLock.waitForStateTransferToEnd(ctx, command, newCacheViewId); if (command.isOnePhaseCommit()) flushL1Caches(ctx); // if we are one-phase, don't block on this future. Collection<Address> recipients = dm.getAffectedNodes(ctx.getAffectedKeys()); prepareOnAffectedNodes(ctx, command, recipients, sync); ((LocalTxInvocationContext) ctx).remoteLocksAcquired(recipients); } else if (isL1CacheEnabled && command.isOnePhaseCommit() && !ctx.isOriginLocal() && !ctx.getLockedKeys().isEmpty()) { // We fall into this block if we are a remote node, happen to be the primary data owner and // have locked keys. // it is still our responsibility to invalidate L1 caches in the cluster. flushL1Caches(ctx); } return retVal; }
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; }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { wrapEntriesForPrepare(ctx, command); EntryVersionsMap newVersionData = null; if (ctx.isOriginLocal() && !((LocalTransaction) ctx.getCacheTransaction()).isFromStateTransfer()) newVersionData = cdl.createNewVersionsAndCheckForWriteSkews( versionGenerator, ctx, (VersionedPrepareCommand) command); Object retval = invokeNextInterceptor(ctx, command); if (!ctx.isOriginLocal()) newVersionData = cdl.createNewVersionsAndCheckForWriteSkews( versionGenerator, ctx, (VersionedPrepareCommand) command); if (command.isOnePhaseCommit()) ctx.getCacheTransaction() .setUpdatedEntryVersions(((VersionedPrepareCommand) command).getVersionsSeen()); if (newVersionData != null) retval = newVersionData; if (command.isOnePhaseCommit()) commitContextEntries(ctx, null); return retval; }
@Override public CompletableFuture<Void> visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { try { return ctx.shortCircuit(ctx.forkInvocationSync(command)); } finally { commitContextEntries(ctx, null, null); } }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (ctx.getCacheTransaction() instanceof RemoteTransaction) { ((RemoteTransaction) ctx.getCacheTransaction()).setMissingLookedUpEntries(false); } return handleTxCommand(ctx, command); }
protected boolean shouldInvokeRemoteTxCommand(TxInvocationContext ctx) { // ISPN-2362: For backups, we should only replicate to the remote site if there are // modifications to replay. boolean shouldBackupRemotely = ctx.isOriginLocal() && ctx.hasModifications() && !ctx.getCacheTransaction().isFromStateTransfer(); getLog().tracef("Should backup remotely? %s", shouldBackupRemotely); return shouldBackupRemotely; }
@Override public CompletableFuture<Void> visitPrepareCommand( TxInvocationContext ctx, PrepareCommand command) throws Throwable { wrapEntriesForPrepare(ctx, command); Object result = ctx.forkInvocationSync(command); if (shouldCommitDuringPrepare(command, ctx)) { commitContextEntries(ctx, null, null); } return ctx.shortCircuit(result); }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { GMUPrepareCommand spc = convert(command, GMUPrepareCommand.class); if (ctx.isOriginLocal()) { spc.setVersion(ctx.getTransactionVersion()); spc.setReadSet(ctx.getReadSet()); } else { ctx.setTransactionVersion(spc.getPrepareVersion()); } wrapEntriesForPrepare(ctx, command); performValidation(ctx, spc); Object retVal = invokeNextInterceptor(ctx, command); if (ctx.isOriginLocal() && command.getModifications().length > 0) { EntryVersion commitVersion = calculateCommitVersion( ctx.getTransactionVersion(), versionGenerator, cll.getWriteOwners(ctx.getCacheTransaction())); ctx.setTransactionVersion(commitVersion); } else { retVal = ctx.getTransactionVersion(); } if (command.isOnePhaseCommit()) { commitContextEntries.commitContextEntries(ctx); } return retVal; }
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; }
protected final void wrapEntriesForPrepare(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (!ctx.isOriginLocal() || command.isReplayEntryWrapping()) { for (WriteCommand c : command.getModifications()) { c.acceptVisitor(ctx, entryWrappingVisitor); if (c.hasFlag(Flag.PUT_FOR_X_SITE_STATE_TRANSFER)) { ctx.getCacheTransaction().setStateTransferFlag(Flag.PUT_FOR_X_SITE_STATE_TRANSFER); } } } }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (ctx.isOriginLocal() && !isMaster() && (ctx.hasModifications() || command.hasModifications())) { throw new IllegalStateException( "Write transaction not allowed in Passive Replication, in backup node"); } return invokeNextInterceptor(ctx, command); }
// We need to intercept PrepareCommand, not InvalidateCommand since the interception takes // place beforeQuery EntryWrappingInterceptor and the PrepareCommand is multiplexed into // InvalidateCommands // as part of EntryWrappingInterceptor @Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (ctx.isOriginLocal()) { // We can't wait to commit phase to remove the entry locally (invalidations are processed in // 1pc // on remote nodes, so only local case matters here). The problem is that while the entry is // locked // reads still can take place and we can read outdated collection afterQuery reading updated // entity // owning this collection from DB; when this happens, the version lock on entity cannot // protect // us against concurrent modification of the collection. Therefore, we need to remove the // entry // here (even without lock!) and let possible update happen in commit phase. for (WriteCommand wc : command.getModifications()) { if (wc instanceof InvalidateCommand) { // ISPN-5605 InvalidateCommand does not correctly implement getAffectedKeys() for (Object key : ((InvalidateCommand) wc).getKeys()) { dataContainer.remove(key); } } else { for (Object key : wc.getAffectedKeys()) { dataContainer.remove(key); } } } } else { for (WriteCommand wc : command.getModifications()) { if (wc instanceof InvalidateCommand) { // ISPN-5605 InvalidateCommand does not correctly implement getAffectedKeys() for (Object key : ((InvalidateCommand) wc).getKeys()) { if (log.isTraceEnabled()) { log.tracef("Invalidating key %s with lock owner %s", key, ctx.getLockOwner()); } putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key); } } else { Set<Object> keys = wc.getAffectedKeys(); if (log.isTraceEnabled()) { log.tracef("Invalidating keys %s with lock owner %s", keys, ctx.getLockOwner()); } for (Object key : keys) { putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key); } } } } return invokeNextInterceptor(ctx, command); }
private void acquireReadLocks(TxInvocationContext ctx, Object[] readSet) throws InterruptedException { for (Object key : readSet) { lockAndRegisterShareBackupLock(ctx, key); ctx.addAffectedKey(key); } }
private void sendCommitCommand( TxInvocationContext ctx, CommitCommand command, Collection<Address> preparedOn) throws TimeoutException, InterruptedException { // we only send the commit command to the nodes that Collection<Address> recipients = dm.getAffectedNodes(ctx.getAffectedKeys()); // By default, use the configured commit sync settings boolean syncCommitPhase = configuration.isSyncCommitPhase(); for (Address a : preparedOn) { if (!recipients.contains(a)) { // However if we have prepared on some nodes and are now committing on different nodes, make // sure we // force sync commit so we can respond to prepare resend requests. syncCommitPhase = true; } } Map<Address, Response> responses = rpcManager.invokeRemotely(recipients, command, syncCommitPhase, true); if (!responses.isEmpty()) { List<Address> resendTo = new LinkedList<Address>(); for (Map.Entry<Address, Response> r : responses.entrySet()) { if (needToResendPrepare(r.getValue())) resendTo.add(r.getKey()); } if (!resendTo.isEmpty()) { log.debugf( "Need to resend prepares for %s to %s", command.getGlobalTransaction(), resendTo); PrepareCommand pc = buildPrepareCommandForResend(ctx, command); rpcManager.invokeRemotely(resendTo, pc, true, true); } } }
private void applyStateInTransaction(XSiteState[] chunk) throws Exception { try { transactionManager.begin(); InvocationContext ctx = invocationContextFactory.createInvocationContext( transactionManager.getTransaction(), true); ((TxInvocationContext) ctx) .getCacheTransaction() .setStateTransferFlag(PUT_FOR_X_SITE_STATE_TRANSFER); for (XSiteState siteState : chunk) { interceptorChain.invoke(ctx, createPut(siteState)); if (trace) { log.tracef("Successfully applied key'%s'", siteState); } } transactionManager.commit(); if (debug) { log.debugf("Successfully applied state. %s keys inserted", chunk.length); } } catch (Exception e) { log.unableToApplyXSiteState(e); safeRollback(); throw e; } }
/** * If this is a transactional cache and autoCommit is set to true then starts a transaction if * this is not a transactional call. */ private InvocationContext getInvocationContextWithImplicitTransaction( EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { InvocationContext invocationContext; boolean txInjected = false; if (config.isTransactionalCache()) { Transaction transaction = getOngoingTransaction(); if (transaction == null && config.isTransactionAutoCommit()) { try { transactionManager.begin(); transaction = transactionManager.getTransaction(); txInjected = true; if (trace) log.trace("Implicit transaction started!"); } catch (Exception e) { throw new CacheException("Could not start transaction", e); } } invocationContext = getInvocationContext(transaction, explicitFlags, explicitClassLoader); } else { invocationContext = getInvocationContextForWrite(explicitFlags, explicitClassLoader); } if (txInjected) { ((TxInvocationContext) invocationContext).setImplicitTransaction(true); if (trace) log.tracef("Marked tx as implicit."); } return invocationContext; }
@Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { try { if (ctx.isOriginLocal()) ((VersionedCommitCommand) command) .setUpdatedVersions(ctx.getCacheTransaction().getUpdatedEntryVersions()); return invokeNextInterceptor(ctx, command); } finally { if (!ctx.isOriginLocal()) ctx.getCacheTransaction() .setUpdatedEntryVersions(((VersionedCommitCommand) command).getUpdatedVersions()); commitContextEntries(ctx, null); } }
@Override public Object visitPrepareCommand(TxInvocationContext tcx, PrepareCommand cc) throws Throwable { Object o = super.visitPrepareCommand(tcx, cc); if (tx.equals(tcx.getTransaction())) { txsReady.countDown(); } return o; }
@Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { GMUCommitCommand gmuCommitCommand = convert(command, GMUCommitCommand.class); if (ctx.isOriginLocal()) { gmuCommitCommand.setCommitVersion(ctx.getTransactionVersion()); } else { ctx.setTransactionVersion(gmuCommitCommand.getCommitVersion()); } transactionCommitManager.commitTransaction( ctx.getCacheTransaction(), gmuCommitCommand.getCommitVersion()); Object retVal = null; try { retVal = invokeNextInterceptor(ctx, command); } catch (Throwable throwable) { // let ignore the exception. we cannot have some nodes applying the write set and another not // another one // receives the rollback and don't applies the write set } finally { transactionCommitManager.awaitUntilCommitted( ctx.getCacheTransaction(), ctx.isOriginLocal() ? null : gmuCommitCommand); } return ctx.isOriginLocal() ? retVal : RequestHandler.DO_NOT_REPLY; }
@Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { if (ctx.getCacheTransaction() instanceof RemoteTransaction) { // If a commit is received for a transaction that doesn't have its 'lookedUpEntries' populated // we know for sure this transaction is 2PC and was received via state transfer but the // preceding PrepareCommand // was not received by local node because it was executed on the previous key owners. We need // to re-prepare // the transaction on local node to ensure its locks are acquired and lookedUpEntries is // properly populated. RemoteTransaction remoteTx = (RemoteTransaction) ctx.getCacheTransaction(); if (remoteTx.isMissingLookedUpEntries()) { remoteTx.setMissingLookedUpEntries(false); PrepareCommand prepareCommand; if (useVersioning) { prepareCommand = commandFactory.buildVersionedPrepareCommand( ctx.getGlobalTransaction(), ctx.getModifications(), false); } else { prepareCommand = commandFactory.buildPrepareCommand( ctx.getGlobalTransaction(), ctx.getModifications(), false); } commandFactory.initializeReplicableCommand(prepareCommand, true); prepareCommand.setOrigin(ctx.getOrigin()); log.tracef( "Replaying the transactions received as a result of state transfer %s", prepareCommand); prepareCommand.perform(null); } } return handleTxCommand(ctx, command); }
@Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { if (Configurations.isOnePhaseTotalOrderCommit(cacheConfiguration) || !ctx.hasModifications()) { return invokeNextInterceptor(ctx, command); } totalOrderTxCommit(ctx); return super.visitCommitCommand(ctx, command); }
@Override public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable { try { return invokeNextInterceptor(ctx, command); } finally { transactionCommitManager.rollbackTransaction(ctx.getCacheTransaction()); } }
@Override public CompletableFuture<Void> visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { return ctx.onReturn( (rCtx, rCommand, rv, throwable) -> { commitContextEntries(rCtx, null, null); return null; }); }
@Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { Object result = super.visitCommitCommand(ctx, command); if (!blockPrepare && !ctx.getCacheTransaction().isFromStateTransfer()) { doBlock(ctx, command); } return result; }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { Object retval = invokeNextInterceptor(ctx, command); log.tracef( "Entering InvalidationInterceptor's prepare phase. Ctx flags are %s", ctx.getFlags()); // fetch the modifications before the transaction is committed (and thus removed from the // txTable) if (shouldInvokeRemoteTxCommand(ctx)) { List<WriteCommand> mods = Arrays.asList(command.getModifications()); Transaction runningTransaction = ctx.getTransaction(); if (runningTransaction == null) throw new IllegalStateException("we must have an associated transaction"); broadcastInvalidateForPrepare(mods, runningTransaction, ctx); } else { log.tracef("Nothing to invalidate - no modifications in the transaction."); } return retval; }