/** * 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 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()); } } }