/**
   * 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;
  }
Example #2
0
  @Override
  public void onTopologyUpdate(CacheTopology cacheTopology, boolean isRebalance) {
    if (trace)
      log.tracef(
          "Received new CH %s for cache %s", cacheTopology.getWriteConsistentHash(), cacheName);

    int numStartedTopologyUpdates = activeTopologyUpdates.incrementAndGet();
    if (isRebalance) {
      rebalanceInProgress.set(true);
    }
    final ConsistentHash previousCh =
        this.cacheTopology != null ? this.cacheTopology.getWriteConsistentHash() : null;
    // Ensures writes to the data container use the right consistent hash
    // No need for a try/finally block, since it's just an assignment
    stateTransferLock.acquireExclusiveTopologyLock();
    this.cacheTopology = cacheTopology;
    if (numStartedTopologyUpdates == 1) {
      updatedKeys = new ConcurrentHashSet<Object>();
    }
    stateTransferLock.releaseExclusiveTopologyLock();
    stateTransferLock.notifyTopologyInstalled(cacheTopology.getTopologyId());

    try {
      // fetch transactions and data segments from other owners if this is enabled
      if (isTransactional || isFetchEnabled) {
        Set<Integer> addedSegments;
        if (previousCh == null) {
          // we start fresh, without any data, so we need to pull everything we own according to
          // writeCh

          addedSegments = getOwnedSegments(cacheTopology.getWriteConsistentHash());

          if (trace) {
            log.tracef("On cache %s we have: added segments: %s", cacheName, addedSegments);
          }
        } else {
          Set<Integer> previousSegments = getOwnedSegments(previousCh);
          Set<Integer> newSegments = getOwnedSegments(cacheTopology.getWriteConsistentHash());

          Set<Integer> removedSegments = new HashSet<Integer>(previousSegments);
          removedSegments.removeAll(newSegments);

          // This is a rebalance, we need to request the segments we own in the new CH.
          addedSegments = new HashSet<Integer>(newSegments);
          addedSegments.removeAll(previousSegments);

          if (trace) {
            log.tracef(
                "On cache %s we have: removed segments: %s; new segments: %s; old segments: %s; added segments: %s",
                cacheName, removedSegments, newSegments, previousSegments, addedSegments);
          }

          // remove inbound transfers and any data for segments we no longer own
          cancelTransfers(removedSegments);

          // If L1.onRehash is enabled, "removed" segments are actually moved to L1. The new (and
          // old) owners
          // will automatically add the nodes that no longer own a key to that key's requestors
          // list.
          invalidateSegments(newSegments, removedSegments);

          // check if any of the existing transfers should be restarted from a different source
          // because the initial source is no longer a member
          Set<Address> members =
              new HashSet<Address>(cacheTopology.getReadConsistentHash().getMembers());
          synchronized (this) {
            for (Iterator<Address> it = transfersBySource.keySet().iterator(); it.hasNext(); ) {
              Address source = it.next();
              if (!members.contains(source)) {
                if (trace) {
                  log.tracef(
                      "Removing inbound transfers from source %s for cache %s", source, cacheName);
                }
                List<InboundTransferTask> inboundTransfers = transfersBySource.get(source);
                it.remove();
                for (InboundTransferTask inboundTransfer : inboundTransfers) {
                  // these segments will be restarted if they are still in new write CH
                  if (trace) {
                    log.tracef(
                        "Removing inbound transfers for segments %s from source %s for cache %s",
                        inboundTransfer.getSegments(), source, cacheName);
                  }
                  transfersBySegment.keySet().removeAll(inboundTransfer.getSegments());
                  addedSegments.addAll(inboundTransfer.getUnfinishedSegments());
                }
              }
            }

            // exclude those that are already in progress from a valid source
            addedSegments.removeAll(transfersBySegment.keySet());
          }
        }

        if (!addedSegments.isEmpty()) {
          addTransfers(addedSegments); // add transfers for new or restarted segments
        }
      }
    } finally {
      stateTransferLock.notifyTransactionDataReceived(cacheTopology.getTopologyId());

      if (activeTopologyUpdates.decrementAndGet() == 0) {
        notifyEndOfTopologyUpdate(cacheTopology.getTopologyId());
      }
    }
  }