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;
  }
 private void stopStateTransferIfNeeded(FlagAffectedCommand command) {
   if (command instanceof ClearCommand) {
     // If we are committing a ClearCommand now then no keys should be written by state transfer
     // from
     // now on until current rebalance ends.
     if (stateConsumer != null) {
       stateConsumer.stopApplyingState();
     }
     if (xSiteStateConsumer != null) {
       xSiteStateConsumer.endStateTransfer(null);
     }
   }
 }