int preJoinTopologyId = cache0.getComponentRegistry().getStateTransferManager().getCacheTopology().getTopologyId();
// Block any state response commands on cache0
// So that we can install the spy ClusteringDependentLogic on cache1 before state transfer is applied
final CheckPoint checkPoint = new CheckPoint();
ControlledRpcManager blockingRpcManager0 = blockStateResponseCommand(cache0);
// Block the rebalance confirmation on cache0 (to avoid the retrying of commands)
blockRebalanceConfirmation(manager(0), checkPoint);
// Start the joiner
log.tracef("Starting the cache on the joiner");
ConfigurationBuilder c = getConfigurationBuilder();
c.clustering().stateTransfer().awaitInitialTransfer(false);
addClusterEnabledCacheManager(c);
final AdvancedCache<Object,Object> cache1 = advancedCache(1);
// Wait for the write CH to contain the joiner everywhere
eventually(new Condition() {
@Override
public boolean isSatisfied() throws Exception {
return cache0.getRpcManager().getMembers().size() == 2 &&
cache1.getRpcManager().getMembers().size() == 2;
}
});
// Every PutKeyValueCommand will be blocked before committing the entry on cache1
blockEntryCommit(checkPoint, cache1);
// Wait for cache0 to collect the state to send to cache1 (including our previous value).
blockingRpcManager0.waitForCommandToBlock();
// Allow the state to be applied on cache1 (writing the old value for our entry)
blockingRpcManager0.stopBlocking();
// Wait for state transfer tx/operation to call commitEntry on cache1 and block
checkPoint.awaitStrict("pre_commit_entry_" + key + "_from_" + null, 5, SECONDS);
// Put/Replace/Remove from cache0 with cache0 as primary owner, cache1 as backup owner
// The put command will be blocked on cache1 just before committing the entry.
Future<Object> future = fork(new Callable<Object>() {
@Override
public Object call() throws Exception {
return op.perform(cache0, key);
}
});
// Check that the user write is blocked by the state transfer write
boolean blocked = checkPoint.peek(3, SECONDS, "pre_commit_entry_" + key + "_from_" + address(0)) == null;
assertTrue(blocked);
// Allow state transfer to commit
checkPoint.trigger("resume_commit_entry_" + key + "_from_" + null);
// Check that the user operation can now commit the entry
checkPoint.awaitStrict("pre_commit_entry_" + key + "_from_" + address(0), 5, SECONDS);
// Allow the user put to commit
checkPoint.trigger("resume_commit_entry_" + key + "_from_" + address(0));
// Wait for both state transfer and the command to commit
checkPoint.awaitStrict("post_commit_entry_" + key + "_from_" + null, 10, SECONDS);
checkPoint.awaitStrict("post_commit_entry_" + key + "_from_" + address(0), 10, SECONDS);
// Wait for the command to finish and check that it didn't fail
Object result = future.get(10, TimeUnit.SECONDS);
assertEquals(op.getReturnValue(), result);
log.tracef("%s operation is done", op);
// Allow the rebalance confirmation to proceed and wait for the topology to change everywhere
int rebalanceTopologyId = preJoinTopologyId + 1;
checkPoint.trigger("resume_rebalance_confirmation_" + rebalanceTopologyId + "_from_" + address(0));
checkPoint.trigger("resume_rebalance_confirmation_" + rebalanceTopologyId + "_from_" + address(1));
TestingUtil.waitForRehashToComplete(cache0, cache1);
// Check the value on all the nodes
assertEquals(op.getValue(), cache0.get(key));
assertEquals(op.getValue(), cache1.get(key));