/** 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);
      }
    }
  }
Пример #20
0
 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;
   }
 }
Пример #21
0
 /**
  * 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;
       });
 }
Пример #29
0
 @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;
 }