/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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.
*/
package com.hazelcast.executor;
import com.hazelcast.core.ExecutionCallback;
import com.hazelcast.core.ICompletableFuture;
import com.hazelcast.spi.ExecutionService;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.ParallelTest;
import com.hazelcast.test.annotation.QuickTest;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class CompletableFutureTest extends HazelcastTestSupport {
private static final RuntimeException THROW_TEST_EXCEPTION = new RuntimeException("Test exception");
private static final RuntimeException NO_EXCEPTION = null;
private ExecutionService executionService;
private CountDownLatch inExecutionLatch, startLogicLatch, executedLogic, callbacksDoneLatch;
private AtomicReference<Object> reference1, reference2;
@Rule
public ExpectedException expected = ExpectedException.none();
@Before
public void setUp() throws Exception {
NodeEngine nodeEngine = getNode(createHazelcastInstance()).getNodeEngine();
executionService = nodeEngine.getExecutionService();
startLogicLatch = new CountDownLatch(1);
executedLogic = new CountDownLatch(1);
inExecutionLatch = new CountDownLatch(1);
reference1 = new AtomicReference<Object>();
reference2 = new AtomicReference<Object>();
}
@Test
public void preregisterCallback() throws Exception {
ICompletableFuture<String> f = submitAwaitingTask(expectedNumberOfCallbacks(1), NO_EXCEPTION);
f.andThen(storeTaskResponseToReference(reference1));
releaseAwaitingTask();
assertCallbacksExecutedEventually();
assertEquals("success", reference1.get());
}
@Test
public void preregisterTwoCallbacks() throws Exception {
ICompletableFuture<String> f = submitAwaitingTask(expectedNumberOfCallbacks(2), NO_EXCEPTION);
f.andThen(storeTaskResponseToReference(reference1));
f.andThen(storeTaskResponseToReference(reference2));
releaseAwaitingTask();
assertCallbacksExecutedEventually();
assertEquals("success", reference1.get());
assertEquals("success", reference2.get());
}
@Test
public void preregisterTwoCallbacks_taskThrowsException() throws Exception {
ICompletableFuture<String> f = submitAwaitingTask(expectedNumberOfCallbacks(2), THROW_TEST_EXCEPTION);
f.andThen(storeTaskResponseToReference(reference1));
f.andThen(storeTaskResponseToReference(reference2));
releaseAwaitingTask();
assertCallbacksExecutedEventually();
assertTestExceptionThrown(reference1, reference2);
}
@Test
// https://github.com/hazelcast/hazelcast/issues/6020
public void postregisterCallback() throws Exception {
ICompletableFuture<String> f = submitAwaitingTask(expectedNumberOfCallbacks(1), NO_EXCEPTION);
releaseAwaitingTask();
assertTaskFinishedEventually(f);
f.andThen(storeTaskResponseToReference(reference1));
assertCallbacksExecutedEventually();
assertEquals("success", reference1.get());
}
@Test
public void postregisterTwoCallbacks() throws Exception {
ICompletableFuture<String> f = submitAwaitingTask(expectedNumberOfCallbacks(2), NO_EXCEPTION);
releaseAwaitingTask();
assertTaskFinishedEventually(f);
f.andThen(storeTaskResponseToReference(reference1));
f.andThen(storeTaskResponseToReference(reference2));
assertCallbacksExecutedEventually();
assertEquals("success", reference1.get());
assertEquals("success", reference2.get());
}
@Test
public void postregisterTwoCallbacks_taskThrowsException() throws Exception {
ICompletableFuture<String> f = submitAwaitingTask(expectedNumberOfCallbacks(2), THROW_TEST_EXCEPTION);
releaseAwaitingTask();
assertTaskFinishedEventually(f);
f.andThen(storeTaskResponseToReference(reference1));
f.andThen(storeTaskResponseToReference(reference2));
assertCallbacksExecutedEventually();
assertTestExceptionThrown(reference1, reference2);
}
@Test(timeout = 60000)
public void get_taskThrowsException() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(THROW_TEST_EXCEPTION);
submitReleasingTask(100);
expected.expect(ExecutionException.class);
f.get();
}
@Test(timeout = 60000)
public void getWithTimeout_taskThrowsException() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(THROW_TEST_EXCEPTION);
submitReleasingTask(200);
expected.expect(ExecutionException.class);
f.get(30000, TimeUnit.MILLISECONDS);
}
@Test(timeout = 60000)
public void getWithTimeout_finishesWithinTime() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
submitReleasingTask(200);
String result = f.get(30000, TimeUnit.MILLISECONDS);
assertEquals("success", result);
}
@Test(timeout = 60000)
public void getWithTimeout_timesOut() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
expected.expect(TimeoutException.class);
f.get(1, TimeUnit.MILLISECONDS);
}
@Test
public void singleCancellation_beforeDone_succeeds() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
assertTaskInExecution();
boolean cancelResult = f.cancel(false);
assertTrue("Task cancellation succeeded should succeed", cancelResult);
}
@Test
public void doubleCancellation_beforeDone_firstSucceeds_secondFails() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
assertTaskInExecution(); // but never released to execute logic
boolean firstCancelResult = f.cancel(false);
boolean secondCancelResult = f.cancel(false);
assertTrue("First task cancellation should succeed", firstCancelResult);
assertFalse("Second task cancellation should failed", secondCancelResult);
}
@Test
public void cancellation_afterDone_taskNotCancelled_flagsSetCorrectly() throws Exception {
final ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
assertTaskInExecution();
releaseAwaitingTask();
assertTaskExecutedItsLogic();
assertTaskFinishedEventually(f);
boolean firstCancelResult = f.cancel(false);
boolean secondCancelResult = f.cancel(false);
assertFalse("Cancellation should not succeed after task is done", firstCancelResult);
assertFalse("Cancellation should not succeed after task is done", secondCancelResult);
assertFalse("Task should NOT be cancelled", f.isCancelled());
assertEquals("success", f.get());
}
@Test
public void noCancellation_afterDone_flagsSetCorrectly() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
assertTaskInExecution();
releaseAwaitingTask();
assertTaskExecutedItsLogic();
assertTaskFinishedEventually(f);
assertTrue("Task should be done", f.isDone());
assertFalse("Task should NOT be cancelled", f.isCancelled());
assertEquals("success", f.get());
}
@Test(timeout = 60000)
public void cancelAndGet_taskCancelled_withoutInterruption_logicExecuted() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
assertTaskInExecution();
boolean cancelResult = f.cancel(false);
releaseAwaitingTask();
assertTaskExecutedItsLogic(); // cancellation came, when task already awaiting, so logic executed
assertTaskFinishedEventually(f);
assertTrue("Task cancellation should succeed", cancelResult);
assertTrue("Task should be done", f.isDone());
assertTrue("Task should be cancelled", f.isCancelled());
expected.expect(CancellationException.class);
f.get();
}
@Test(timeout = 60000)
public void cancelAndGet_taskCancelled_withInterruption_noLogicExecuted() throws Exception {
ICompletableFuture<String> f = submitAwaitingTaskNoCallbacks(NO_EXCEPTION);
assertTaskInExecution();
boolean cancelResult = f.cancel(true);
assertTaskInterruptedAndDidNotExecuteItsLogic();
assertTaskFinishedEventually(f); // task did not have to be releases - interruption was enough
assertTrue("Task cancellation should succeed", cancelResult);
assertTrue("Task should be done", f.isDone());
assertTrue("Task should be cancelled", f.isCancelled());
expected.expect(CancellationException.class);
f.get();
}
private static void assertTestExceptionThrown(AtomicReference<?>... refs) {
for (AtomicReference<?> ref : refs) {
assertThat("ExecutionException expected", ref.get(), instanceOf(ExecutionException.class));
}
for (AtomicReference<?> ref : refs) {
assertThat("TEST_EXCEPTION expected as cause", ((Throwable) ref.get()).getCause(),
Matchers.<Throwable>sameInstance(THROW_TEST_EXCEPTION));
}
}
private ICompletableFuture<String> submitAwaitingTaskNoCallbacks(final Exception exception) {
return submitAwaitingTask(0, exception);
}
private ICompletableFuture<String> submitAwaitingTask(Integer numberOfCallbacks, final Exception exception) {
callbacksDoneLatch = new CountDownLatch(numberOfCallbacks);
return submit(new Callable<String>() {
@Override
public String call() throws Exception {
inExecutionLatch.countDown();
assertOpenEventually(startLogicLatch);
executedLogic.countDown();
if (exception != null) {
throw exception;
}
return "success";
}
});
}
private void submitReleasingTask(final long millisToAwaitBeforeRelease) {
submit(new Runnable() {
@Override
public void run() {
sleepAtLeastMillis(millisToAwaitBeforeRelease);
releaseAwaitingTask();
}
});
}
private ICompletableFuture<String> submit(final Callable<String> callable) {
return executionService.asCompletableFuture(executionService.submit("default", callable));
}
private void submit(final Runnable runnable) {
executionService.submit("default", runnable);
}
private Integer expectedNumberOfCallbacks(int number) {
return number;
}
private void releaseAwaitingTask() {
startLogicLatch.countDown();
}
private void assertCallbacksExecutedEventually() {
assertOpenEventually(callbacksDoneLatch);
}
private void assertTaskExecutedItsLogic() {
assertOpenEventually(executedLogic);
}
private void assertTaskInterruptedAndDidNotExecuteItsLogic() {
assertEquals(1, executedLogic.getCount());
}
private void assertTaskFinishedEventually(final ICompletableFuture future) {
assertTrueEventually(new AssertTask() {
@Override
public void run()
throws Exception {
assertTrue(future.isDone());
}
});
}
private void assertTaskInExecution() {
assertOpenEventually(inExecutionLatch);
}
private ExecutionCallback<String> storeTaskResponseToReference(final AtomicReference<Object> ref) {
return new ExecutionCallback<String>() {
@Override
public void onResponse(String response) {
doit(response);
}
@Override
public void onFailure(Throwable t) {
doit(t);
}
private void doit(Object response) {
ref.set(response);
callbacksDoneLatch.countDown();
}
};
}
}