@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 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 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 { 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 visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (ctx.getCacheTransaction() instanceof RemoteTransaction) { ((RemoteTransaction) ctx.getCacheTransaction()).setMissingLookedUpEntries(false); } return handleTxCommand(ctx, command); }
@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 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 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 { 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; }
@Override public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable { try { return invokeNextInterceptor(ctx, command); } finally { transactionCommitManager.rollbackTransaction(ctx.getCacheTransaction()); } }
@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 final CompletableFuture<Void> visitPrepareCommand( TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (ctx.isOriginLocal()) { ((VersionedPrepareCommand) command) .setVersionsSeen(ctx.getCacheTransaction().getVersionsRead()); // for local mode keys ctx.getCacheTransaction().setUpdatedEntryVersions(EMPTY_VERSION_MAP); return ctx.onReturn( (rCtx, rCommand, rv, throwable) -> { if (throwable == null && shouldCommitDuringPrepare((PrepareCommand) rCommand, ctx)) { commitContextEntries(ctx, null, null); } return null; }); } // Remote context, delivered in total order wrapEntriesForPrepare(ctx, command); return ctx.onReturn( (rCtx, rCommand, rv, throwable) -> { if (throwable != null) throw throwable; TxInvocationContext txInvocationContext = (TxInvocationContext) rCtx; VersionedPrepareCommand prepareCommand = (VersionedPrepareCommand) rCommand; EntryVersionsMap versionsMap = cdl.createNewVersionsAndCheckForWriteSkews( versionGenerator, txInvocationContext, prepareCommand); if (prepareCommand.isOnePhaseCommit()) { commitContextEntries(txInvocationContext, null, null); } else { if (trace) log.tracef( "Transaction %s will be committed in the 2nd phase", txInvocationContext.getGlobalTransaction().globalId()); } return CompletableFuture.completedFuture( versionsMap == null ? rv : new ArrayList<Object>(versionsMap.keySet())); }); }
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; }
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); } } } }
/** * validates the read set and returns the prepare version from the commit queue * * @param ctx the context * @param command the prepare command * @throws InterruptedException if interrupted */ protected void performValidation(TxInvocationContext ctx, GMUPrepareCommand command) throws InterruptedException { boolean hasToUpdateLocalKeys = hasLocalKeysToUpdate(command.getModifications()); boolean isReadOnly = command.getModifications().length == 0; if (!isReadOnly) { cll.performReadSetValidation(ctx, command); if (hasToUpdateLocalKeys) { transactionCommitManager.prepareTransaction(ctx.getCacheTransaction()); } else { transactionCommitManager.prepareReadOnlyTransaction(ctx.getCacheTransaction()); } } if (log.isDebugEnabled()) { log.debugf( "Transaction %s can commit on this node. Prepare Version is %s", command.getGlobalTransaction().prettyPrint(), ctx.getTransactionVersion()); } }
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))); } }
private EntryVersionsMap clusteredCreateNewVersionsAndCheckForWriteSkews( VersionGenerator versionGenerator, TxInvocationContext context, VersionedPrepareCommand prepareCommand) { // Perform a write skew check on mapped entries. EntryVersionsMap uv = performWriteSkewCheckAndReturnNewVersions( prepareCommand, dataContainer, persistenceManager, versionGenerator, context, keySpecificLogic, timeService); CacheTransaction cacheTransaction = context.getCacheTransaction(); EntryVersionsMap uvOld = cacheTransaction.getUpdatedEntryVersions(); if (uvOld != null && !uvOld.isEmpty()) { uvOld.putAll(uv); uv = uvOld; } cacheTransaction.setUpdatedEntryVersions(uv); return (uv.isEmpty()) ? null : uv; }