@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);
   }
 }
Beispiel #3
0
  @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);
   }
 }