package org.infinispan.notifications.cachelistener.cluster; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.distribution.MagicKey; import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent; import org.infinispan.notifications.cachelistener.event.CacheEntryEvent; import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent; import org.infinispan.notifications.cachelistener.event.Event; import org.infinispan.remoting.transport.Address; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.CheckPoint; import org.infinispan.util.concurrent.CommandAckCollector; import org.testng.annotations.Test; /** * Tests for cluster listeners that are specific to non tx * * @author wburns * @since 7.0 */ @Test(groups = "functional") public abstract class AbstractClusterListenerNonTxTest extends AbstractClusterListenerTest { protected AbstractClusterListenerNonTxTest(boolean tx, CacheMode cacheMode) { super(tx, cacheMode); } @Test public void testPrimaryOwnerGoesDownAfterSendingEvent() throws InterruptedException, ExecutionException, TimeoutException { final Cache<Object, String> cache0 = cache(0, CACHE_NAME); Cache<Object, String> cache1 = cache(1, CACHE_NAME); Cache<Object, String> cache2 = cache(2, CACHE_NAME); ClusterListener clusterListener = listener(); cache0.addListener(clusterListener); CheckPoint checkPoint = new CheckPoint(); waitUntilNotificationRaised(cache1, checkPoint); checkPoint.triggerForever("pre_raise_notification_release"); final MagicKey key = new MagicKey(cache1, cache2); Future<String> future = fork(() -> cache0.put(key, FIRST_VALUE)); checkPoint.awaitStrict("post_raise_notification_invoked", 10, TimeUnit.SECONDS); awaitForBackups(cache0); // Kill the cache now - note this will automatically unblock the fork thread TestingUtil.killCacheManagers(cache1.getCacheManager()); future.get(10, TimeUnit.SECONDS); TestingUtil.waitForNoRebalance(cache0, cache2); // The command is retried during rebalance, but there are two topologies - in the first (rebalancing) topology // one node can be primary owner and in the second (rebalanced) the other. In this case, it's possible that // the listener is fired both in the first topology and then after the response from primary owner arrives // and the originator now has become the new primary owner. // Similar situation is possible with triangle algorithm (TODO pruivo: elaborate) assertTrue(clusterListener.events.size() >= 2); assertTrue(clusterListener.events.size() <= 3); checkEvent(clusterListener.events.get(0), key, true, false); Address cache0primary = cache0.getAdvancedCache().getDistributionManager().getPrimaryLocation(key); Address cache2primary = cache2.getAdvancedCache().getDistributionManager().getPrimaryLocation(key); // we expect that now both nodes have the same topology assertEquals(cache0primary, cache2primary); // This is possible after rebalance; when rebalancing, primary owner is always the old backup checkEvent(clusterListener.events.get(1), key, false, true); if (clusterListener.events.size() == 3) { checkEvent(clusterListener.events.get(2), key, false, true); } } protected void checkEvent(CacheEntryEvent<Object, String> event, MagicKey key, boolean isCreated, boolean isRetried) { CacheEntryCreatedEvent<Object, String> createEvent; if (isCreated) { assertEquals(event.getType(), Event.Type.CACHE_ENTRY_CREATED); createEvent = (CacheEntryCreatedEvent<Object, String>)event; assertEquals(createEvent.isCommandRetried(), isRetried); } else { assertEquals(event.getType(), Event.Type.CACHE_ENTRY_MODIFIED); CacheEntryModifiedEvent<Object, String> modEvent = (CacheEntryModifiedEvent<Object, String>) event; assertTrue(modEvent.isCommandRetried()); } assertEquals(event.getKey(), key); assertEquals(event.getValue(), FIRST_VALUE); } protected void awaitForBackups(Cache<?, ?> cache) { if (TestingUtil.isTriangleAlgorithm(cacheMode, tx)) { CommandAckCollector collector = TestingUtil.extractComponent(cache, CommandAckCollector.class); List<Long> pendingCommands = collector.getPendingCommands(); //only 1 put is waiting (it may receive the backup ack, but not the primary ack since it is blocked!) assertEquals(1, pendingCommands.size()); //make sure that the backup received the update eventually(() -> !collector.hasPendingBackupAcks(pendingCommands.get(0))); } } }