package org.infinispan.distexec; import static org.infinispan.test.Exceptions.expectException; import java.io.Serializable; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.marshall.core.ExternalPojo; import org.infinispan.remoting.transport.Address; import org.infinispan.test.AbstractCacheTest; import org.infinispan.test.TestException; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.util.concurrent.WithinThreadExecutor; import org.testng.AssertJUnit; import org.testng.annotations.Test; /** * Tests basic org.infinispan.distexec.DistributedExecutorService functionality * * @author Vladimir Blagojevic * @author Anna Manukyan */ @Test(groups = "functional", testName = "distexec.BasicDistributedExecutorTest") public class BasicDistributedExecutorTest extends AbstractCacheTest { public BasicDistributedExecutorTest() { } public void testImproperMasterCacheForDistributedExecutor() { expectException(IllegalArgumentException.class, () -> new DefaultExecutorService(null)); } public void testImproperLocalExecutorServiceForDistributedExecutor() { EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createCacheManager(false); try { Cache<Object, Object> cache = cacheManager.getCache(); expectException(IllegalArgumentException.class, () -> new DefaultExecutorService(cache, null)); } finally { TestingUtil.killCacheManagers(cacheManager); } } public void testStoppedLocalExecutorServiceForDistributedExecutor() throws ExecutionException, InterruptedException { ExecutorService service = new WithinThreadExecutor(); service.shutdown(); expectException(IllegalArgumentException.class, () -> customExecutorServiceDistributedExecutorTest(service, false)); } public void testDistributedExecutorWithPassedThreadExecutorOwnership() throws ExecutionException, InterruptedException { ExecutorService service = new WithinThreadExecutor(); customExecutorServiceDistributedExecutorTest(service, true); } public void testDistributedExecutorWithPassedThreadExecutor() throws ExecutionException, InterruptedException { ExecutorService service = new WithinThreadExecutor(); customExecutorServiceDistributedExecutorTest(service, false); } public void testDistributedExecutorWithManagedExecutorService() throws ExecutionException, InterruptedException { ExecutorService service = new ManagedExecutorServicesEmulator(); customExecutorServiceDistributedExecutorTest(service, false); } public void testDistributedExecutorWithManagedExecutorServiceOwnership() throws ExecutionException, InterruptedException { ExecutorService service = new ManagedExecutorServicesEmulator(); expectException(IllegalArgumentException.class, () -> customExecutorServiceDistributedExecutorTest(service, true)); } private void customExecutorServiceDistributedExecutorTest(ExecutorService service, boolean overrideOwnership) throws ExecutionException, InterruptedException { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); try { Cache<Object, Object> cache = cacheManager.getCache(); DistributedExecutorService des; if (overrideOwnership) des = new DefaultExecutorService(cache, service, true); else des = new DefaultExecutorService(cache, service); Future<Integer> future = des.submit(new SimpleCallable()); Integer r = future.get(); assert r == 1; } finally { TestingUtil.killCacheManagers(cacheManager); } } public void testStoppedCacheForDistributedExecutor() { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); try { Cache<Object, Object> cache = cacheManager.getCache(); cache.stop(); expectException(IllegalStateException.class, () -> new DefaultExecutorService(cache)); } finally { TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedExecutorShutDown() { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); try { Cache<Object, Object> cache = cacheManager.getCache(); DistributedExecutorService des = new DefaultExecutorService(cache); des.shutdown(); assert des.isShutdown(); assert des.isTerminated(); } finally { TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedExecutorRealShutdown() { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); ExecutorService service = new WithinThreadExecutor(); try { Cache<Object, Object> cache = cacheManager.getCache(); DistributedExecutorService des = new DefaultExecutorService(cache, service); des.shutdown(); assert des.isShutdown(); assert des.isTerminated(); assert !service.isShutdown(); } finally { TestingUtil.killCacheManagers(cacheManager); service.shutdown(); } } public void testDistributedExecutorRealShutdownWithOwnership() { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); ExecutorService service = new WithinThreadExecutor(); try { Cache<Object, Object> cache = cacheManager.getCache(); DistributedExecutorService des = new DefaultExecutorService(cache, service, true); des.shutdown(); assert des.isShutdown(); assert des.isTerminated(); assert service.isShutdown(); } finally { TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedExecutorShutDownNow() { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); try { Cache<Object, Object> cache = cacheManager.getCache(); DistributedExecutorService des = new DefaultExecutorService(cache); assert !des.isShutdown(); assert !des.isTerminated(); des.shutdownNow(); assert des.isShutdown(); assert des.isTerminated(); } finally { TestingUtil.killCacheManagers(cacheManager); } } /** * Tests that we can invoke DistributedExecutorService on an Infinispan cluster having a single node */ public void testSingleCacheExecution() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache = cacheManager.getCache(); des = new DefaultExecutorService(cache); Future<Integer> future = des.submit(new SimpleCallable()); Integer r = future.get(); assert r == 1; List<CompletableFuture<Integer>> list = des.submitEverywhere(new SimpleCallable()); AssertJUnit.assertEquals(1, list.size()); for (Future<Integer> f : list) { AssertJUnit.assertEquals(new Integer(1), f.get()); } } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager); } } /** * Tests that we can invoke DistributedExecutorService task with keys * https://issues.jboss.org/browse/ISPN-1886 */ public void testSingleCacheWithKeysExecution() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> c1 = cacheManager.getCache(); c1.put("key1", "Manik"); c1.put("key2", "Mircea"); c1.put("key3", "Galder"); c1.put("key4", "Sanne"); des = new DefaultExecutorService(c1); Future<Boolean> future = des.submit(new SimpleDistributedCallable(true), "key1", "key2"); Boolean r = future.get(); assert r; } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedCallableCustomFailoverPolicySuccessfullRetry() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache1 = cacheManager.getCache(); cache1.put("key1", "value1"); cache1.put("key2", "value2"); //initiate task from cache1 and select cache1 as target des = new DefaultExecutorService(cache1); //the same using DistributedTask API DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new FailOnlyOnceCallable()); taskBuilder.failoverPolicy(new DistributedTaskFailoverPolicy() { @Override public Address failover(FailoverContext context) { return context.executionFailureLocation(); } @Override public int maxFailoverAttempts() { return 1; } }); DistributedTask<Integer> task = taskBuilder.build(); AssertJUnit.assertEquals(1, task.getTaskFailoverPolicy().maxFailoverAttempts()); Future<Integer> val = des.submit(task, "key1"); AssertJUnit.assertEquals(new Integer(1), val.get()); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedCallableWithFailingKeysSuccessfullRetry() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.DIST_SYNC); config.clustering().hash().numOwners(1); EmbeddedCacheManager cacheManager1 = TestCacheManagerFactory.createClusteredCacheManager(config); EmbeddedCacheManager cacheManager2 = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; cacheManager1.defineConfiguration("cache1", config.build()); cacheManager2.defineConfiguration("cache1", config.build()); try { Cache<Object, Object> cache1 = cacheManager1.getCache("cache1"); cache1.put("key1", "value1"); cache1.put("key2", "value2"); cache1.put("key3", "value3"); Cache<Object, Object> cache2 = cacheManager2.getCache("cache1"); cache2.put("key4", "value4"); cache2.put("key5", "value5"); cache2.put("key6", "value6"); cache2.put("key7", "value7"); cache2.put("key8", "value8"); //initiate task from cache1 and select cache1 as target des = new DefaultExecutorService(cache1); //the same using DistributedTask API DistributedTaskBuilder<Boolean> taskBuilder = des.createDistributedTaskBuilder(new FailOnlyOnceDistributedCallable()); taskBuilder.failoverPolicy(new DistributedTaskFailoverPolicy() { @Override public Address failover(FailoverContext context) { List<Address> candidates = context.executionCandidates(); Address returnAddress = null; for (Address candidate : candidates) { if (!candidate.equals(context.executionFailureLocation())) { returnAddress = candidate; break; } } return returnAddress; } @Override public int maxFailoverAttempts() { return 1; } }); DistributedTask<Boolean> task = taskBuilder.build(); AssertJUnit.assertEquals(1, task.getTaskFailoverPolicy().maxFailoverAttempts()); Future<Boolean> val = des.submit(task, "key1", "key5"); AssertJUnit.assertEquals(Boolean.TRUE, val.get()); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager1); TestingUtil.killCacheManagers(cacheManager2); } } public void testDistributedCallableEmptyFailoverPolicy() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache1 = cacheManager.getCache(); //initiate task from cache1 and select cache1 as target des = new DefaultExecutorService(cache1); //the same using DistributedTask API DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new ExceptionThrowingCallable()); taskBuilder.failoverPolicy(null); DistributedTask<Integer> task = taskBuilder.build(); assert task.getTaskFailoverPolicy().equals(DefaultExecutorService.NO_FAILOVER); Future<Integer> f = des.submit(task); expectException(ExecutionException.class, TestException.class, f::get); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedCallableRandomFailoverPolicy() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache1 = cacheManager.getCache(); cache1.put("key1", "value1"); cache1.put("key2", "value2"); //initiate task from cache1 and select cache1 as target des = new DefaultExecutorService(cache1); //the same using DistributedTask API DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new FailOnlyOnceCallable()); taskBuilder.failoverPolicy(DefaultExecutorService.RANDOM_NODE_FAILOVER); DistributedTask<Integer> task = taskBuilder.build(); assert task.getTaskFailoverPolicy().equals(DefaultExecutorService.RANDOM_NODE_FAILOVER); Future<Integer> val = des.submit(task, "key1"); val.get(); throw new IllegalStateException("Should have raised exception"); } catch (ExecutionException e){ // The failover policy throws an IllegalStateException because there are no nodes to retry on. // Verify that the distributed executor didn't wrap the exception in too many extra exceptions. AssertJUnit.assertTrue("Wrong exception: " + e, e.getCause() instanceof IllegalStateException); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager); } } public void testDistributedCallableRandomFailoverPolicyWith2Nodes() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); EmbeddedCacheManager cacheManager1 = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache1 = cacheManager.getCache(); cache1.put("key1", "value1"); cache1.put("key2", "value2"); Cache<Object, Object> cache2 = cacheManager1.getCache(); cache2.put("key3", "value3"); //initiate task from cache1 and select cache1 as target des = new DefaultExecutorService(cache1); //the same using DistributedTask API DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new ExceptionThrowingCallable()); taskBuilder.failoverPolicy(DefaultExecutorService.RANDOM_NODE_FAILOVER); DistributedTask<Integer> task = taskBuilder.build(); assert task.getTaskFailoverPolicy().equals(DefaultExecutorService.RANDOM_NODE_FAILOVER); Future<Integer> val = des.submit(task, "key1"); val.get(); throw new IllegalStateException("Should have thrown exception"); } catch (Exception e){ assert e instanceof ExecutionException; ExecutionException ee = (ExecutionException)e; boolean duplicateEEInChain = ee.getCause() instanceof ExecutionException; AssertJUnit.assertEquals(false, duplicateEEInChain); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager, cacheManager1); } } public void testBasicTargetLocalDistributedCallableWithoutAnyTimeout() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(false); config.clustering().cacheMode(CacheMode.REPL_SYNC).remoteTimeout(0L); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); EmbeddedCacheManager cacheManager1 = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache1 = cacheManager.getCache(); Cache<Object, Object> cache2 = cacheManager1.getCache(); // initiate task from cache1 and execute on same node des = new DefaultExecutorService(cache1); Address target = cache1.getAdvancedCache().getRpcManager().getAddress(); DistributedTaskBuilder<Integer> builder = des .createDistributedTaskBuilder(new DistributedExecutorTest.SleepingSimpleCallable()); Future<Integer> future = des.submit(target, builder.build()); AssertJUnit.assertEquals((Integer) 1, future.get()); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager, cacheManager1); } } public void testBasicTargetRemoteDistributedCallableWithoutAnyTimeout() throws Exception { ConfigurationBuilder confBuilder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, false); confBuilder.clustering().remoteTimeout(0L); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(confBuilder); EmbeddedCacheManager cacheManager1 = TestCacheManagerFactory.createClusteredCacheManager(confBuilder); Cache<Object, Object> cache1 = cacheManager.getCache(); Cache<Object, Object> cache2 = cacheManager1.getCache(); DistributedExecutorService des = null; try { // initiate task from cache1 and execute on same node des = new DefaultExecutorService(cache1); Address target = cache2.getAdvancedCache().getRpcManager().getAddress(); DistributedTaskBuilder<Integer> builder = des .createDistributedTaskBuilder(new DistributedExecutorTest.SleepingSimpleCallable()); Future<Integer> future = des.submit(target, builder.build()); AssertJUnit.assertEquals((Integer) 1, future.get()); } finally { if (des != null) des.shutdown(); TestingUtil.killCacheManagers(cacheManager, cacheManager1); } } public void testDistributedCallableCustomFailoverPolicy() throws Exception { ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true); config.clustering().cacheMode(CacheMode.REPL_SYNC); EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createClusteredCacheManager(config); DistributedExecutorService des = null; try { Cache<Object, Object> cache1 = cacheManager.getCache(); cache1.put("key1", "value1"); cache1.put("key2", "value2"); //initiate task from cache1 and select cache1 as target des = new DefaultExecutorService(cache1); //the same using DistributedTask API DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new FailOnlyOnceCallable()); taskBuilder.failoverPolicy(new DistributedTaskFailoverPolicy() { @Override public Address failover(FailoverContext context) { return context.executionFailureLocation(); } @Override public int maxFailoverAttempts() { return 0; } }); DistributedTask<Integer> task = taskBuilder.build(); assert task.getTaskFailoverPolicy().maxFailoverAttempts() == 0; Future<Integer> val = des.submit(task, "key1"); val.get(); throw new IllegalStateException("Should have thrown exception"); } catch (Exception e) { assert e instanceof ExecutionException; ExecutionException ee = (ExecutionException) e; boolean duplicateEEInChain = ee.getCause() instanceof ExecutionException; AssertJUnit.assertEquals(false, duplicateEEInChain); } finally { if (des != null) des.shutdownNow(); TestingUtil.killCacheManagers(cacheManager); } } static class SimpleDistributedCallable implements DistributedCallable<String, String, Boolean>, Serializable, ExternalPojo { /** The serialVersionUID */ private static final long serialVersionUID = 623845442163221832L; private boolean invokedProperly = false; private final boolean hasKeys; public SimpleDistributedCallable(boolean hasKeys) { this.hasKeys = hasKeys; } @Override public Boolean call() throws Exception { return invokedProperly; } @Override public void setEnvironment(Cache<String, String> cache, Set<String> inputKeys) { boolean keysProperlySet = hasKeys ? inputKeys != null && !inputKeys.isEmpty() : inputKeys != null && inputKeys.isEmpty(); invokedProperly = cache != null && keysProperlySet; } public boolean validlyInvoked() { return invokedProperly; } } static class SimpleCallable implements Callable<Integer>, Serializable, ExternalPojo { /** The serialVersionUID */ private static final long serialVersionUID = -8589149500259272402L; @Override public Integer call() throws Exception { return 1; } } static class FailOnlyOnceCallable implements Callable<Integer>, Serializable, ExternalPojo { /** The serialVersionUID */ private static final long serialVersionUID = 3961940091247573385L; boolean throwException = true; @Override public Integer call() throws Exception { if (throwException) { // do to not throw the exception 2nd time during retry. throwException = false; // now throw exception for the first run throw new TestException(); } return 1; } } static class FailOnlyOnceDistributedCallable implements DistributedCallable<String, String, Boolean>, Serializable, ExternalPojo { /** The serialVersionUID **/ private static final long serialVersionUID = 5375461422884389555L; private static boolean throwException = true; @Override public void setEnvironment(Cache<String, String> cache, Set<String> inputKeys) { //do nothing } @Override public Boolean call() throws Exception { if(throwException) { throwException = false; throw new TestException(); } return true; } } static class ExceptionThrowingCallable implements Callable<Integer>, Serializable, ExternalPojo { /** The serialVersionUID */ private static final long serialVersionUID = -8589149500259272402L; @Override public Integer call() throws Exception { throw new TestException(); } } }