package org.infinispan.distexec;
import static org.infinispan.test.Exceptions.expectException;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.fail;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.distribution.TestAddress;
import org.infinispan.marshall.core.ExternalPojo;
import org.infinispan.remoting.transport.Address;
import org.infinispan.remoting.transport.jgroups.SuspectException;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestException;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
/**
* Test for verifying that the DistributedExecutors also work on the Local Cache.
*
* @author Anna Manukyan
*/
@Test(groups = "functional", testName = "distexec.LocalDistributedExecutorTest")
public class LocalDistributedExecutorTest extends MultipleCacheManagersTest {
private DistributedExecutorService cleanupService;
static final Map<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
public LocalDistributedExecutorTest() {
cleanup = CleanupPhase.AFTER_METHOD;
}
protected CacheMode getCacheMode() {
return CacheMode.LOCAL;
}
@Override
protected void createCacheManagers() throws Throwable {
ConfigurationBuilder builder = getDefaultClusteredCacheConfig(getCacheMode(), false);
createClusteredCaches(1, cacheName(), builder);
}
@AfterMethod(alwaysRun = true)
public void shutDownDistributedExecutorService() {
if (cleanupService != null) {
cleanupService.shutdownNow();
} else {
log.warn("Should have shutdown DistributedExecutorService but none was set");
}
}
protected String cacheName() {
return "LocalDistributedExecutorTest";
}
protected Cache<Object, Object> getCache() {
return cache(0, cacheName());
}
public void testBasicInvocation() throws Exception {
basicInvocation(new SimpleCallable());
}
/**
* Helper public method (used by CDI module), disabled as some IDEs invoke it as a test method
*/
@Test(enabled = false) // Disable explicitly to avoid TestNG thinking this is a test!!
public void basicInvocation(Callable<Integer> call, Object... keys) throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<Integer> future = des.submit(call, keys);
Integer r = future.get();
assertEquals((Integer) 1, r);
}
/**
* Helper public method (used by CDI module), disabled as some IDEs invoke it as a test method
*
*/
@Test(enabled = false) // Disable explicitly to avoid TestNG thinking this is a test!!
public void addEntries(Map<Object,Object> entries) {
getCache().putAll(entries);
}
/**
* Helper public method (used by CDI module), disabled as some IDEs invoke it as a test method
*/
@Test(enabled = false) // Disable explicitly to avoid TestNG thinking this is a test!!
public void basicInvocation(Runnable call) throws Exception {
DistributedExecutorService des = createDES(getCache());
des.submit(call).get();
}
protected DistributedExecutorService createDES(Cache<?,?> cache){
ExecutorService executorService = Executors.newCachedThreadPool(getTestThreadFactory("LocalExecutor"));
DistributedExecutorService des = new DefaultExecutorService(cache, executorService, true);
cleanupService = des;
return des;
}
public void testExceptionInvocation() throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<Integer> future = des.submit(new ExceptionThrowingCallable());
int exceptionCount = 0;
try {
future.get();
throw new IllegalStateException("Should not have reached this code");
} catch (ExecutionException ex) {
exceptionCount++;
}
assertEquals(1, exceptionCount);
List<CompletableFuture<Integer>> list = des.submitEverywhere(new ExceptionThrowingCallable());
exceptionCount = 0;
for (Future<Integer> f : list) {
try {
f.get();
throw new IllegalStateException("Should not have reached this code");
} catch (ExecutionException ex) {
exceptionCount++;
}
}
assertEquals(list.size(), exceptionCount);
}
public void testRunnableInvocation() throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<?> future = des.submit(new BoringRunnable());
Object object = future.get();
assertEquals(null, object);
}
public void testRunnableInvocationWith2Params() throws Exception {
DistributedExecutorService des = createDES(getCache());
Integer result = 5;
Future<Integer> future = des.submit(new BoringRunnable(), result);
assertEquals(result, future.get());
}
public void testRunnableExecution() throws InterruptedException {
final String uuid = UUID.randomUUID().toString();
try {
DistributedExecutorService des = createDES(getCache());
BoringRunnable runnable = new BoringRunnable(uuid);
des.execute(runnable);
eventually(() -> counterMap.get(uuid) != null && counterMap.get(uuid).get() >= 1);
} finally {
counterMap.remove(uuid);
}
}
public void testNonSerializableRunnableExecution() {
DistributedExecutorService des = createDES(getCache());
expectException(IllegalArgumentException.class, () -> des.execute(() -> log.trace("Non Serializable Runnable")));
}
public void testRunnableExecutionOnTerminatedExecutor() {
DistributedExecutorService des = createDES(getCache());
des.shutdown();
BoringRunnable runnable = new BoringRunnable();
expectException(RejectedExecutionException.class, () -> des.execute(runnable));
}
public void testNullRunnableExecution() {
DistributedExecutorService des = createDES(getCache());
BoringRunnable runnable = null;
expectException(IllegalArgumentException.class, () -> des.execute(runnable));
}
public void testInvokeAny() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<SimpleCallable> tasks = new ArrayList<>();
tasks.add(new SimpleCallable());
Integer result = des.invokeAny(tasks);
assertEquals((Integer) 1, result);
tasks = new ArrayList<>();
tasks.add(new SimpleCallable());
tasks.add(new SimpleCallable());
result = des.invokeAny(tasks);
assertEquals((Integer) 1, result);
}
public void testInvokeAnyWithTimeout() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<SimpleCallable> tasks = new ArrayList<>();
tasks.add(new SimpleCallable());
Integer result = des.invokeAny(tasks, 1000, TimeUnit.MILLISECONDS);
assertEquals((Integer) 1, result);
tasks = new ArrayList<>();
tasks.add(new SimpleCallable());
tasks.add(new SimpleCallable());
result = des.invokeAny(tasks, 1000, TimeUnit.MILLISECONDS);
assertEquals((Integer) 1, result);
}
public void testInvokeAnyNoTask() throws Exception {
DistributedExecutorService des = createDES(getCache());
expectException(NullPointerException.class, () -> des.invokeAny(null));
}
public void testInvokeAnyEmptyTasks() throws Exception {
DistributedExecutorService des = createDES(getCache());
expectException(IllegalArgumentException.class, () -> des.invokeAny(new ArrayList<SimpleCallable>()));
}
public void testInvokeAnyExceptionTasks() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(new ExceptionThrowingCallable());
tasks.add(new ExceptionThrowingCallable());
expectException(ExecutionException.class, () -> des.invokeAny(tasks));
}
public void testInvokeAnySleepingTasks() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(new ExceptionThrowingCallable());
tasks.add(new SleepingSimpleCallable());
Object result = des.invokeAny(tasks);
assertEquals(1, result);
}
public void testInvokeAnyTimedSleepingTasks() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<SleepingSimpleCallable> tasks = new ArrayList<>();
tasks.add(new SleepingSimpleCallable());
expectException(TimeoutException.class, () -> des.invokeAny(tasks, 1000, TimeUnit.MILLISECONDS));
}
public void testInvokeAll() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<SimpleCallable> tasks = new ArrayList<>();
tasks.add(new SimpleCallable());
List<Future<Integer>> list = des.invokeAll(tasks);
assertEquals(1, list.size());
Future<Integer> future = list.get(0);
assertEquals((Integer) 1, future.get());
tasks = new ArrayList<>();
tasks.add(new SimpleCallable());
tasks.add(new SimpleCallable());
tasks.add(new SimpleCallable());
list = des.invokeAll(tasks);
assertEquals(3, list.size());
for (Future<Integer> f : list) {
assertEquals((Integer) 1, f.get());
}
}
/**
* Tests Callable isolation as it gets invoked across the cluster
* https://issues.jboss.org/browse/ISPN-1041
*/
public void testCallableIsolation() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<CompletableFuture<Integer>> list = des.submitEverywhere(new SimpleCallableWithField());
assert list != null && !list.isEmpty();
for (Future<Integer> f : list) {
assertEquals((Integer) 0, f.get());
}
}
public void testBasicDistributedCallable() throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<Boolean> future = des.submit(new SimpleDistributedCallable(false));
Boolean r = future.get();
assert r;
// the same using DistributedTask API
DistributedTaskBuilder<Boolean> taskBuilder = des
.createDistributedTaskBuilder(new SimpleDistributedCallable(false));
DistributedTask<Boolean> distributedTask = taskBuilder.build();
future = des.submit(distributedTask);
r = future.get();
assert r;
}
public void testSleepingCallableWithTimeoutOption() throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<Integer> future = des.submit(new SleepingSimpleCallable());
Integer r = future.get(10, TimeUnit.SECONDS);
assertEquals((Integer) 1, r);
//the same using DistributedTask API
DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new SleepingSimpleCallable());
DistributedTask<Integer> distributedTask = taskBuilder.build();
future = des.submit(distributedTask);
r = future.get(10, TimeUnit.SECONDS);
assertEquals((Integer) 1, r);
}
public void testSleepingCallableWithTimeoutExc() throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<Integer> future = des.submit(new SleepingSimpleCallable());
log.tracef("Sleeping task submitted");
expectException(TimeoutException.class, () -> future.get(1000, TimeUnit.MILLISECONDS));
}
public void testSleepingCallableWithTimeoutExcDistApi() throws Exception {
DistributedExecutorService des = createDES(getCache());
DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new SleepingSimpleCallable());
DistributedTask<Integer> distributedTask = taskBuilder.build();
Future<Integer> future = des.submit(distributedTask);
log.tracef("Sleeping task submitted");
expectException(TimeoutException.class, () -> future.get(1000, TimeUnit.MILLISECONDS));
}
public void testExceptionCallableWithTimedCall() throws Exception {
DistributedExecutorService des = createDES(getCache());
Future<Integer> future = des.submit(new ExceptionThrowingCallable(true));
expectException(TimeoutException.class, () -> future.get(10, TimeUnit.MILLISECONDS));
}
public void testExceptionCallableWithTimedCallDistApi() throws Exception {
DistributedExecutorService des = createDES(getCache());
DistributedTaskBuilder<Integer> taskBuilder = des.createDistributedTaskBuilder(new ExceptionThrowingCallable(true));
DistributedTask<Integer> distributedTask = taskBuilder.build();
Future<Integer> future = des.submit(distributedTask);
expectException(TimeoutException.class, () -> future.get(10, TimeUnit.MILLISECONDS));
}
public void testBasicTargetDistributedCallableWithNullExecutionPolicy() throws Exception {
Cache<Object, Object> cache1 = getCache();
//initiate task from cache1 and select cache2 as target
DistributedExecutorService des = createDES(cache1);
//the same using DistributedTask API
DistributedTaskBuilder<Boolean> taskBuilder = des.createDistributedTaskBuilder(new SimpleDistributedCallable(false));
expectException(IllegalArgumentException.class, () -> taskBuilder.executionPolicy(null));
}
public void testBasicTargetCallableWithNullTarget() {
Cache<Object, Object> cache1 = getCache();
DistributedExecutorService des = createDES(cache1);
expectException(NullPointerException.class, () -> des.submit((Address) null, new SimpleCallable()));
}
public void testBasicTargetCallableWithIllegalTarget() throws InterruptedException, ExecutionException {
Cache<Object, Object> cache1 = getCache();
DistributedExecutorService des = createDES(cache1);
Address fakeAddress = new TestAddress(0, "fake");
Future<?> future = des.submit(fakeAddress, new SimpleCallable());
try {
future.get();
fail("Test should have thrown an execution exception!");
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (!(t instanceof SuspectException)) {
throw e;
}
}
}
public void testBasicDistributedCallableWitkKeys() throws Exception {
Cache<Object, Object> c1 = getCache();
c1.put("key1", "Manik");
c1.put("key2", "Mircea");
c1.put("key3", "Galder");
c1.put("key4", "Sanne");
DistributedExecutorService des = createDES(getCache());
Future<Boolean> future = des.submit(new SimpleDistributedCallable(true), "key1", "key2");
Boolean r = future.get();
assert r;
// the same using DistributedTask API
DistributedTaskBuilder<Boolean> taskBuilder = des
.createDistributedTaskBuilder(new SimpleDistributedCallable(true));
DistributedTask<Boolean> distributedTask = taskBuilder.build();
future = des.submit(distributedTask, "key1", "key2");
r = future.get();
assert r;
}
public void testBasicDistributedCallableWithNullTask() throws Exception {
Cache<Object, Object> c1 = getCache();
DistributedExecutorService des = createDES(getCache());
DistributedTask task = null;
expectException(NullPointerException.class, () -> des.submit(task, "key1", "key2"));
}
public void testBasicDistributedCallableWithNullKeys() throws Exception {
Cache<Object, Object> c1 = getCache();
c1.put("key1", "value1");
c1.put("key2", "value2");
c1.put("key3", "value3");
c1.put("key4", "value4");
DistributedExecutorService des = createDES(getCache());
des.submit(new SimpleDistributedCallable(false));
}
public void testDistributedCallableEverywhereWithKeys() throws Exception {
Cache<Object, Object> c1 = getCache();
c1.put("key1", "Manik");
c1.put("key2", "Mircea");
c1.put("key3", "Galder");
c1.put("key4", "Sanne");
DistributedExecutorService des = createDES(getCache());
List<CompletableFuture<Boolean>> list = des.submitEverywhere(new SimpleDistributedCallable(true),
"key1", "key2");
assert list != null && !list.isEmpty();
for (Future<Boolean> f : list) {
assert f.get();
}
// the same using DistributedTask API
DistributedTaskBuilder<Boolean> taskBuilder = des
.createDistributedTaskBuilder(new SimpleDistributedCallable(true));
DistributedTask<Boolean> distributedTask = taskBuilder.build();
list = des.submitEverywhere(distributedTask, "key1", "key2");
assert list != null && !list.isEmpty();
for (Future<Boolean> f : list) {
assert f.get();
}
}
public void testDistributedCallableEverywhereWithEmptyKeys() throws Exception {
Cache<Object, Object> c1 = getCache();
c1.put("key1", "Manik");
c1.put("key2", "Mircea");
c1.put("key3", "Galder");
c1.put("key4", "Sanne");
DistributedExecutorService des = createDES(getCache());
List<CompletableFuture<Boolean>> list = des.submitEverywhere(new SimpleDistributedCallable(false),
new String[]{});
assert list != null && !list.isEmpty();
for (Future<Boolean> f : list) {
assert f.get();
}
//the same using DistributedTask API
DistributedTaskBuilder<Boolean> taskBuilder = des.createDistributedTaskBuilder(new SimpleDistributedCallable(false));
DistributedTask<Boolean> distributedTask = taskBuilder.build();
list = des.submitEverywhere(distributedTask, new String[]{});
assert list != null && !list.isEmpty();
for (Future<Boolean> f : list) {
assert f.get();
}
}
public void testBasicDistributedCallableEverywhereWithKeysAndNullTask() throws Exception {
DistributedExecutorService des = createDES(getCache());
DistributedTask task = null;
expectException(NullPointerException.class, () -> des.submitEverywhere(task, "key1", "key2"));
}
public void testBasicDistributedCallableEverywhereWithNullTask() throws Exception {
DistributedExecutorService des = createDES(getCache());
DistributedTask task = null;
expectException(NullPointerException.class, () -> des.submitEverywhere(task));
}
public void testDistributedCallableEverywhere() throws Exception {
DistributedExecutorService des = createDES(getCache());
List<CompletableFuture<Boolean>> list = des.submitEverywhere(new SimpleDistributedCallable(false));
assert list != null && !list.isEmpty();
for (Future<Boolean> f : list) {
assert f.get();
}
// the same using DistributedTask API
DistributedTaskBuilder<Boolean> taskBuilder = des
.createDistributedTaskBuilder(new SimpleDistributedCallable(false));
DistributedTask<Boolean> distributedTask = taskBuilder.build();
list = des.submitEverywhere(distributedTask);
assert list != null && !list.isEmpty();
for (Future<Boolean> f : list) {
assert f.get();
}
}
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 SleepingSimpleCallable implements Callable<Integer>, Serializable, ExternalPojo {
private static final Log log = LogFactory.getLog(SleepingSimpleCallable.class);
/** The serialVersionUID */
private static final long serialVersionUID = -8589149500259272402L;
@Override
public Integer call() throws Exception {
log.tracef("Sleeping for 2 seconds");
Thread.sleep(2000);
return 1;
}
}
static class SimpleCallableWithField implements Callable<Integer>, Serializable, ExternalPojo {
/** The serialVersionUID */
private static final long serialVersionUID = -6262148927734766558L;
private int count;
@Override
public Integer call() throws Exception {
return count++;
}
}
static class ExceptionThrowingCallable implements Callable<Integer>, Serializable, ExternalPojo {
/** The serialVersionUID */
private static final long serialVersionUID = -8682463816319507893L;
private boolean needToSleep;
public ExceptionThrowingCallable() {
this.needToSleep = false;
}
public ExceptionThrowingCallable(boolean needToSleep) {
this.needToSleep = needToSleep;
}
@Override
public Integer call() throws Exception {
if(needToSleep) {
Thread.sleep(10000);
}
throw new TestException("Intentional Exception from ExceptionThrowingCallable");
}
}
static class BoringRunnable implements Runnable, Serializable {
/** The serialVersionUID */
private static final long serialVersionUID = 6898519516955822402L;
private String uuid;
public BoringRunnable() {
}
public BoringRunnable(final String uuid) {
this.uuid = uuid;
}
@Override
public void run() {
if(uuid != null) {
AtomicInteger counter = counterMap.computeIfAbsent(uuid, k -> new AtomicInteger());
counter.incrementAndGet();
}
}
}
}