public void testReplace() throws Exception { cache(0).put("myKey", "myValue"); // add an interceptor on second node that will block REPLACE commands right after // EntryWrappingInterceptor until we are ready final CountDownLatch replaceStartedLatch = new CountDownLatch(1); final CountDownLatch replaceProceedLatch = new CountDownLatch(1); boolean isVersioningEnabled = cache(0).getCacheConfiguration().versioning().enabled(); cacheConfigBuilder .customInterceptors() .addInterceptor() .after( isVersioningEnabled ? VersionedEntryWrappingInterceptor.class : EntryWrappingInterceptor.class) .interceptor( new CommandInterceptor() { @Override protected Object handleDefault(InvocationContext ctx, VisitableCommand cmd) throws Throwable { if (cmd instanceof ReplaceCommand) { // signal we encounter a REPLACE replaceStartedLatch.countDown(); // wait until it is ok to continue with REPLACE if (!replaceProceedLatch.await(15, TimeUnit.SECONDS)) { throw new TimeoutException(); } } return super.handleDefault(ctx, cmd); } }); // do not allow coordinator to send topology updates to node B final ClusterTopologyManager ctm0 = TestingUtil.extractGlobalComponent(manager(0), ClusterTopologyManager.class); ctm0.setRebalancingEnabled(false); log.info("Adding a new node .."); addClusterEnabledCacheManager(cacheConfigBuilder); log.info("Added a new node"); // node B is not a member yet and rebalance has not started yet CacheTopology cacheTopology = advancedCache(1).getComponentRegistry().getStateTransferManager().getCacheTopology(); assertNull(cacheTopology.getPendingCH()); assertTrue(cacheTopology.getMembers().contains(address(0))); assertFalse(cacheTopology.getMembers().contains(address(1))); assertFalse(cacheTopology.getCurrentCH().getMembers().contains(address(1))); // no keys should be present on node B yet because state transfer is blocked assertTrue(cache(1).keySet().isEmpty()); // initiate a REPLACE Future<Object> getFuture = fork( new Callable<Object>() { @Override public Object call() throws Exception { try { return cache(1).replace("myKey", "newValue"); } catch (Exception e) { log.errorf(e, "REPLACE failed: %s", e.getMessage()); throw e; } } }); // wait for REPLACE command on node B to reach beyond *EntryWrappingInterceptor, where it will // block. // the value seen so far is null if (!replaceStartedLatch.await(15, TimeUnit.SECONDS)) { throw new TimeoutException(); } // paranoia, yes the value is still missing from data container assertTrue(cache(1).keySet().isEmpty()); // allow rebalance to start ctm0.setRebalancingEnabled(true); // wait for state transfer to end TestingUtil.waitForRehashToComplete(cache(0), cache(1)); // the state should be already transferred now assertEquals(1, cache(1).keySet().size()); // allow REPLACE to continue replaceProceedLatch.countDown(); Object oldVal = getFuture.get(15, TimeUnit.SECONDS); assertNotNull(oldVal); assertEquals("myValue", oldVal); assertEquals("newValue", cache(0).get("myKey")); assertEquals("newValue", cache(1).get("myKey")); }
@Override public void setRebalancingEnabled(boolean enabled) { instance.setRebalancingEnabled(enabled); }