/** * When L1 is enabled this test should not be ran when a previous value is present as it will * cause timeouts. Due to how locking works with L1 this cannot occur when the previous value * exists. * * @param op * @throws Exception */ protected void doTestWhereCommitOccursAfterStateTransferBeginsBeforeCompletion( final TestWriteOperation op) throws Exception { if (l1Enabled() && op.getPreviousValue() != null) { fail("This test cannot be ran with L1 when a previous value is set"); } // Test scenario: // cache0,1,2 are in the cluster, an owner leaves // Key k is in the cache, and is transferred to the non owner // A user operation also modifies key k causing an invalidation // on the non owner which is getting the state transfer final AdvancedCache<Object, Object> primaryOwnerCache = cache(0, cacheName).getAdvancedCache(); final AdvancedCache<Object, Object> backupOwnerCache = cache(1, cacheName).getAdvancedCache(); final AdvancedCache<Object, Object> nonOwnerCache = cache(2, cacheName).getAdvancedCache(); final MagicKey key = new MagicKey(primaryOwnerCache, backupOwnerCache); // Prepare for replace/remove: put a previous value in cache0 final Object previousValue = op.getPreviousValue(); if (previousValue != null) { primaryOwnerCache.put(key, previousValue); assertEquals(previousValue, primaryOwnerCache.get(key)); log.tracef("Previous value inserted: %s = %s", key, previousValue); assertEquals(previousValue, nonOwnerCache.get(key)); if (l1Enabled()) { assertIsInL1(nonOwnerCache, key); } } int preJoinTopologyId = primaryOwnerCache .getComponentRegistry() .getStateTransferManager() .getCacheTopology() .getTopologyId(); // Block any state response commands on cache0 CheckPoint checkPoint = new CheckPoint(); ControlledRpcManager blockingRpcManager0 = blockStateResponseCommand(primaryOwnerCache); // Block the rebalance confirmation on cache0 blockRebalanceConfirmation(primaryOwnerCache.getCacheManager(), checkPoint); assertEquals( primaryOwnerCache.getCacheManager().getCoordinator(), primaryOwnerCache.getCacheManager().getAddress()); // Remove the leaver log.trace("Stopping the cache"); backupOwnerCache.getCacheManager().stop(); int rebalanceTopologyId = preJoinTopologyId + 2; // Wait for the write CH to contain the joiner everywhere eventually( new Condition() { @Override public boolean isSatisfied() throws Exception { return primaryOwnerCache.getRpcManager().getMembers().size() == 2 && nonOwnerCache.getRpcManager().getMembers().size() == 2; } }); assertEquals( primaryOwnerCache.getCacheManager().getCoordinator(), primaryOwnerCache.getCacheManager().getAddress()); // Wait for cache0 to collect the state to send to cache1 (including our previous value). blockingRpcManager0.waitForCommandToBlock(); // Every PutKeyValueCommand will be blocked before committing the entry on cache1 CyclicBarrier beforeCommitCache1Barrier = new CyclicBarrier(2); BlockingInterceptor blockingInterceptor1 = new BlockingInterceptor(beforeCommitCache1Barrier, op.getCommandClass(), true); nonOwnerCache.addInterceptorAfter(blockingInterceptor1, EntryWrappingInterceptor.class); // Put/Replace/Remove from cache0 with cache0 as primary owner, cache1 will become a backup // owner for the retry // 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(primaryOwnerCache, key); } }); // Wait for the entry to be wrapped on cache1 beforeCommitCache1Barrier.await(10, TimeUnit.SECONDS); // Remove the interceptor so we don't mess up any other state transfer puts removeAllBlockingInterceptorsFromCache(nonOwnerCache); // Allow the state to be applied on cache1 (writing the old value for our entry) blockingRpcManager0.stopBlocking(); // Wait for second in line to finish applying the state, but don't allow the rebalance // confirmation to be processed. // (It would change the topology and it would trigger a retry for the command.) checkPoint.awaitStrict( "pre_rebalance_confirmation_" + rebalanceTopologyId + "_from_" + primaryOwnerCache.getCacheManager().getAddress(), 10, SECONDS); // Now allow the command to commit on cache1 beforeCommitCache1Barrier.await(10, TimeUnit.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 checkPoint.trigger( "resume_rebalance_confirmation_" + rebalanceTopologyId + "_from_" + primaryOwnerCache.getCacheManager().getAddress()); checkPoint.trigger( "resume_rebalance_confirmation_" + rebalanceTopologyId + "_from_" + nonOwnerCache.getCacheManager().getAddress()); TestingUtil.waitForRehashToComplete(primaryOwnerCache, nonOwnerCache); switch (op) { case REMOVE: case REMOVE_EXACT: break; default: assertIsInContainerImmortal(primaryOwnerCache, key); assertIsInContainerImmortal(nonOwnerCache, key); break; } // Check the value to make sure data container contains correct value assertEquals(op.getValue(), primaryOwnerCache.get(key)); assertEquals(op.getValue(), nonOwnerCache.get(key)); }
private void doL1InvalidationOldTopologyComesAfterRebalance(final TestWriteOperation op) throws Exception { final String key = getClass().getName() + "-key"; // Test scenario: // cache0,1,2 are in the cluster, an owner leaves // Key k is in the cache, and is transferred to the non owner // A user operation also modifies key k causing an invalidation // on the non owner which is getting the state transfer final AdvancedCache<Object, Object> primaryOwnerCache = getFirstOwner(key).getAdvancedCache(); final AdvancedCache<Object, Object> backupOwnerCache = getOwners(key)[1].getAdvancedCache(); final AdvancedCache<Object, Object> nonOwnerCache = getFirstNonOwner(key).getAdvancedCache(); // Prepare for replace/remove: put a previous value in cache0 final Object previousValue = op.getPreviousValue(); if (previousValue != null) { primaryOwnerCache.put(key, previousValue); assertEquals(previousValue, primaryOwnerCache.get(key)); log.tracef("Previous value inserted: %s = %s", key, previousValue); assertEquals(previousValue, nonOwnerCache.get(key)); if (l1Enabled()) { assertIsInL1(nonOwnerCache, key); } } // Block on the interceptor right after ST which should now have the soon to be old topology id CyclicBarrier beforeCommitCache1Barrier = new CyclicBarrier(2); BlockingInterceptor blockingInterceptor1 = new BlockingInterceptor(beforeCommitCache1Barrier, getVisitableCommand(op), false); primaryOwnerCache.addInterceptorAfter(blockingInterceptor1, StateTransferInterceptor.class); // Put/Replace/Remove from primary owner. This will block before it is committing on remote // nodes Future<Object> future = fork( new Callable<Object>() { @Override public Object call() throws Exception { try { return op.perform(primaryOwnerCache, key); } finally { log.tracef("%s operation is done", op); } } }); beforeCommitCache1Barrier.await(10, SECONDS); // Remove blocking interceptor now since we have blocked removeAllBlockingInterceptorsFromCache(primaryOwnerCache); // Remove the leaver log.tracef("Stopping the cache"); backupOwnerCache.getCacheManager().stop(); // Wait for the write CH to contain the joiner everywhere eventually( new Condition() { @Override public boolean isSatisfied() throws Exception { return primaryOwnerCache.getRpcManager().getMembers().size() == 2 && nonOwnerCache.getRpcManager().getMembers().size() == 2; } }); TestingUtil.waitForRehashToComplete(primaryOwnerCache, nonOwnerCache); // Now let the update go through beforeCommitCache1Barrier.await(10, SECONDS); // Run the update now that we are in the middle of a rebalance assertEquals(op.getReturnValue(), future.get(10, SECONDS)); log.tracef("%s operation is done", op); switch (op) { case REMOVE: case REMOVE_EXACT: break; default: assertIsInContainerImmortal(primaryOwnerCache, key); assertIsInContainerImmortal(nonOwnerCache, key); break; } // Check the value to make sure data container contains correct value assertEquals(op.getValue(), primaryOwnerCache.get(key)); assertEquals(op.getValue(), nonOwnerCache.get(key)); }
protected void doStateTransferInBetweenPrepareCommit( final TestWriteOperation op, final boolean additionalValueOnNonOwner) throws Exception { final String key = getClass().getName() + "-key"; // Test scenario: // cache0,1,2 are in the cluster, an owner leaves // Key k is in the cache, and is transferred to the non owner // A user operation also modifies key k causing an invalidation // on the non owner which is getting the state transfer final AdvancedCache<Object, Object> primaryOwnerCache = getFirstOwner(key).getAdvancedCache(); final AdvancedCache<Object, Object> backupOwnerCache = getOwners(key)[1].getAdvancedCache(); final AdvancedCache<Object, Object> nonOwnerCache = getFirstNonOwner(key).getAdvancedCache(); // Prepare for replace/remove: put a previous value in cache0 final Object previousValue = op.getPreviousValue(); if (previousValue != null) { primaryOwnerCache.put(key, previousValue); assertEquals(previousValue, primaryOwnerCache.get(key)); log.tracef("Previous value inserted: %s = %s", key, previousValue); assertEquals(previousValue, nonOwnerCache.get(key)); if (l1Enabled()) { assertIsInL1(nonOwnerCache, key); } } // Need to block after Prepare command was sent after it clears the StateTransferInterceptor final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); try { TransactionManager tm = primaryOwnerCache.getTransactionManager(); Future<Object> future = fork( runWithTx( tm, new Callable<Object>() { @Override public Object call() throws Exception { if (additionalValueOnNonOwner) { MagicKey mk = new MagicKey("placeholder", nonOwnerCache); String value = "somevalue"; primaryOwnerCache.put(mk, value); log.tracef( "Adding additional value on nonOwner value inserted: %s = %s", mk, value); } primaryOwnerCache .getAdvancedCache() .addInterceptorBefore( new BlockingInterceptor(cyclicBarrier, getVisitableCommand(op), true), StateTransferInterceptor.class); return op.perform(primaryOwnerCache, key); } })); cyclicBarrier.await(10, SECONDS); // Block the rebalance confirmation on nonOwnerCache CheckPoint checkPoint = new CheckPoint(); log.trace("Adding proxy to state transfer"); waitUntilStateBeingTransferred(nonOwnerCache, checkPoint); backupOwnerCache.getCacheManager().stop(); // Wait for non owner to just about get state checkPoint.awaitStrict("pre_state_apply_invoked_for_" + nonOwnerCache, 10, SECONDS); // let prepare complete and thus commit command invalidating on nonOwner cyclicBarrier.await(10, SECONDS); assertEquals(op.getReturnValue(), future.get(10, SECONDS)); // let state transfer go checkPoint.trigger("pre_state_apply_release_for_" + nonOwnerCache); TestingUtil.waitForRehashToComplete(primaryOwnerCache, nonOwnerCache); switch (op) { case REMOVE: case REMOVE_EXACT: break; default: assertIsInContainerImmortal(primaryOwnerCache, key); assertIsInContainerImmortal(nonOwnerCache, key); break; } // Check the value to make sure data container contains correct value assertEquals(op.getValue(), primaryOwnerCache.get(key)); assertEquals(op.getValue(), nonOwnerCache.get(key)); } finally { removeAllBlockingInterceptorsFromCache(primaryOwnerCache); } }