/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2015 Wisdom Framework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.wisdom.executors;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Test;
import org.wisdom.api.concurrent.ExecutionContext;
import org.wisdom.api.concurrent.ExecutionContextService;
import org.wisdom.api.concurrent.ManagedExecutorService;
import org.wisdom.api.concurrent.ManagedFutureTask;
import org.wisdom.test.parents.FakeConfiguration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
public class ManagedExecutorServiceImplTest {
AtomicInteger counter = new AtomicInteger();
ManagedExecutorServiceImpl executor = new ManagedExecutorServiceImpl(
"test",
ManagedExecutorService.ThreadType.POOLED,
10,
10,
25,
1000,
true,
20,
Thread.NORM_PRIORITY,
null);
@Before
public void setUp() {
counter.set(0);
executor.ecs = new ArrayList<>();
}
@Test
public void testCreation() throws ExecutionException, InterruptedException {
Future<String> future = executor.submit(new MyCallable());
assertThat(future.get()).isEqualTo("hello");
assertThat(executor.getInternalPool()).isNotNull();
assertThat(executor.getExecutor()).isNotNull();
}
@Test
public void testThatCreatedFuturesAreListenable() throws ExecutionException, InterruptedException {
final Semaphore semaphore = new Semaphore(0);
final StringBuilder builder = new StringBuilder();
Future<String> future = executor.submit(new MyCallable());
assertThat(future).isInstanceOf(ListenableFuture.class);
assertThat(future).isInstanceOf(Task.class);
((Task<String>) future).onSuccess(new ManagedFutureTask.SuccessCallback<String>() {
@Override
public void onSuccess(ManagedFutureTask<String> future, String result) {
builder.append(result).append(" wisdom");
}
}, MoreExecutors.sameThreadExecutor()).onSuccess(new ManagedFutureTask.SuccessCallback<String>() {
@Override
public void onSuccess(ManagedFutureTask<String> future, String result) {
builder.append(" !");
semaphore.release();
}
}, MoreExecutors.sameThreadExecutor());
assertThat(future.get()).isEqualTo("hello");
semaphore.tryAcquire(10, TimeUnit.SECONDS);
assertThat(builder.toString()).isEqualTo("hello wisdom !");
}
@Test
public void testThatErrorCallbacksAreCalled() throws InterruptedException {
final StringBuilder builder = new StringBuilder();
final Semaphore semaphore = new Semaphore(0);
ManagedFutureTask<?> future = executor.submit(new Runnable() {
@Override
public void run() {
throw new NullPointerException("expected");
}
});
((Task) future).onFailure(new ManagedFutureTask.FailureCallback() {
@Override
public void onFailure(ManagedFutureTask future, Throwable throwable) {
builder.append(throwable.getMessage());
semaphore.release();
}
});
semaphore.acquire();
assertThat(builder.toString()).isEqualTo("expected");
assertThat(((Task) future).cause().getMessage()).isEqualTo("expected");
}
@Test
public void testManagementAPI() throws InterruptedException {
final Semaphore semaphore = new Semaphore(0);
long begin = System.currentTimeMillis();
ManagedFutureTask<?> future = executor.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// Ignore it.
} finally {
semaphore.release();
}
}
});
semaphore.acquire();
// We should sleep here until the task are been completely done (may be in the enhanced runnable wrapper).
Thread.sleep(20);
assertThat(future.getTaskStartTime()).isGreaterThanOrEqualTo(begin);
assertThat(future.getTaskCompletionTime())
.isGreaterThanOrEqualTo(begin)
.isGreaterThan(future.getTaskStartTime());
assertThat(future.getTaskRunTime()).isGreaterThanOrEqualTo(10);
assertThat(future.isDone()).isTrue();
}
@Test
public void testTheExecutionOfFiftyThreads() throws InterruptedException, ExecutionException {
List<Callable<String>> callables = new ArrayList<>();
for (int i = 0; i < 50; i++) {
callables.add(new MyCallable());
}
List<Future<String>> futures = executor.invokeAll(callables, 1, TimeUnit.MINUTES);
for (Future<String> future : futures) {
assertThat(future.get()).isEqualTo("hello");
}
assertThat(counter.get()).isEqualTo(50);
counter.set(0);
futures = executor.invokeAll(callables);
for (Future<String> future : futures) {
assertThat(future.get()).isEqualTo("hello");
}
assertThat(counter.get()).isEqualTo(50);
// Management API
// We aware that getActiveCount, getTaskCount and getCompleteTaskCount are 'approximations'.
assertThat(executor.getActiveCount()).isBetween(0, 3);
assertThat(executor.getCorePoolSize()).isEqualTo(10);
assertThat(executor.getMaximumPoolSize()).isEqualTo(25);
assertThat(executor.getLargestPoolSize()).isGreaterThanOrEqualTo(10);
assertThat(executor.getPoolSize()).isGreaterThanOrEqualTo(10);
assertThat(executor.getCompletedTaskCount()).isBetween(90l, 110l);
assertThat(executor.getTaskCount()).isBetween(90l, 110l);
}
@Test
public void testHungTaskDetection() throws InterruptedException {
ManagedFutureTask<?> future = executor.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore it.
}
}
});
assertThat(future.getHungTaskThreshold()).isEqualTo(10);
Thread.sleep(100);
// The task should hung
assertThat(future.isTaskHang()).isTrue();
assertThat(executor.getHungTasks()).hasSize(1).contains(future);
// Has time is not exact, reduce the limit.
assertThat(future.getTaskRunTime()).isGreaterThanOrEqualTo(95);
// Management API
assertThat(executor.getQueue()).hasSize(0);
assertThat(executor.getActiveCount()).isEqualTo(1);
}
@Test
public void testExecutionContextSwitch() throws InterruptedException {
final ThreadLocal<String> context = new ThreadLocal();
final Semaphore semaphore = new Semaphore(0);
final StringBuilder builder = new StringBuilder();
executor.ecs = new ArrayList<>();
// First call without context
executor.submit(new Runnable() {
@Override
public void run() {
builder.append(context.get());
semaphore.release();
}
});
semaphore.acquire();
assertThat(builder.toString()).isEqualTo("null");
// Then, set a context service, but nothing in the context.
executor.ecs.add(new ExecutionContextService() {
@Override
public String name() {
return "context";
}
@Override
public ExecutionContext prepare() {
return new ExecutionContext() {
private String local = context.get();
@Override
public void apply() {
context.set(local);
}
@Override
public void unapply() {
context.remove();
}
};
}
});
executor.submit(new Runnable() {
@Override
public void run() {
builder.append(context.get());
semaphore.release();
}
});
semaphore.acquire();
assertThat(builder.toString()).isEqualTo("nullnull");
// Finally, set the context
context.set("Stuff");
executor.submit(new Runnable() {
@Override
public void run() {
builder.append(context.get());
semaphore.release();
}
});
semaphore.acquire();
assertThat(builder.toString()).isEqualTo("nullnullStuff");
}
@Test
public void testExecutorAPI() {
assertThat(executor.name()).isEqualToIgnoringCase("test");
assertThat(executor.isShutdown()).isFalse();
assertThat(executor.isTerminated()).isFalse();
assertThat(executor.getActiveCount()).isEqualTo(0);
assertThat(executor.getCorePoolSize()).isEqualTo(10);
assertThat(executor.getMaximumPoolSize()).isEqualTo(25);
// Nothing started.
assertThat(executor.getLargestPoolSize()).isEqualTo(0);
assertThat(executor.getPoolSize()).isEqualTo(0);
assertThat(executor.getCompletedTaskCount()).isEqualTo(0);
assertThat(executor.getTaskCount()).isEqualTo(0);
assertThat(executor.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(1);
}
@Test(expected = NullPointerException.class)
public void testThatWeCannotSubmitNullRunnable() {
executor.submit((Runnable) null);
}
@Test(expected = NullPointerException.class)
public void testThatWeCannotSubmitNullCallable() {
executor.submit((Callable) null);
}
@Test
public void testThatWeCanSubmitARunnableWithAnExpectedResult() throws ExecutionException, InterruptedException {
final Semaphore semaphore = new Semaphore(0);
ManagedFutureTask<String> future = executor.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5);
semaphore.release();
} catch (InterruptedException e) {
// Ignore it.
}
}
}, "hello");
assertThat(future.get()).isEqualToIgnoringCase("hello");
}
@Test
public void testCreationWithUnboundQueue() throws ExecutionException, InterruptedException {
ManagedExecutorServiceImpl service = new ManagedExecutorServiceImpl("unbound",
ManagedExecutorService.ThreadType.POOLED, 60000, 10, 25, 1000, true,
Integer.MAX_VALUE, Thread.NORM_PRIORITY, null);
assertThat(service.getQueue()).isInstanceOf(LinkedBlockingQueue.class);
}
@Test
public void testCreationWithHandOffQueue() throws ExecutionException, InterruptedException {
ManagedExecutorServiceImpl service = new ManagedExecutorServiceImpl("unbound",
ManagedExecutorService.ThreadType.POOLED, 60000, 10, 25, 1000, true,
0, Thread.NORM_PRIORITY, null);
assertThat(service.getQueue()).isInstanceOf(SynchronousQueue.class);
}
@Test
public void testCreationWithDefaultConfiguration() {
FakeConfiguration configuration = new FakeConfiguration(ImmutableMap.<String, Object>of("name", "default"));
ManagedExecutorServiceImpl service = new ManagedExecutorServiceImpl("default", configuration, null);
assertThat(service).isNotNull();
assertThat(service.getCorePoolSize()).isEqualTo(5);
assertThat(service.getActiveCount()).isEqualTo(0);
assertThat(service.getMaximumPoolSize()).isEqualTo(25);
assertThat(service.getKeepAliveTime(TimeUnit.MILLISECONDS)).isEqualTo(5000);
assertThat(service.getQueue()).isInstanceOf(LinkedBlockingQueue.class);
}
private class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
counter.incrementAndGet();
return "hello";
}
}
}