/*
* Copyright 2012 LinkedIn, Inc
*
* 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.linkedin.parseq;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.testng.Assert.*;
import org.testng.annotations.Test;
import com.linkedin.parseq.promise.Promises;
/**
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*/
public class TestTaskCancellation extends BaseEngineTest {
@Test
public void testTaskCancellationAfterRun() throws InterruptedException {
final AtomicReference<Throwable> cancelActionValue = new AtomicReference<>();
final CountDownLatch runLatch = new CountDownLatch(1);
final CountDownLatch listenerLatch = new CountDownLatch(1);
final CountDownLatch pendingLatch = new CountDownLatch(2);
Task<?> uncompleted = Task.async(() -> {
runLatch.countDown();
return Promises.settable();
});
uncompleted.addListener(p -> {
if (p.isFailed() && Exceptions.isCancellation(p.getError())) {
cancelActionValue.set(p.getError().getCause());
}
listenerLatch.countDown();
} );
getEngine().run(Task.par(Task.action(() -> pendingLatch.countDown()), uncompleted, Task.action(() -> pendingLatch.countDown())));
assertTrue(runLatch.await(5, TimeUnit.SECONDS));
/*
* pendingLatch makes sure that uncompleted transitioned to PENDING state
* This is a trick, we are relying here on a fact that Task.par will schedule
* tasks in order in which they are being passed to Tasp.par() method.
* The order might be either FIFO or LIFO - this is why we need two tasks
* that wrap the 'uncompleted' one.
*/
assertTrue(pendingLatch.await(5, TimeUnit.SECONDS));
logTracingResults("TestTaskCancellation.testTaskCancellationAfterRun-before-cancellation", uncompleted);
Exception cancelReason = new Exception();
assertTrue(uncompleted.cancel(cancelReason));
logTracingResults("TestTaskCancellation.testTaskCancellationAfterRun-after-cancellation", uncompleted);
assertTrue(listenerLatch.await(5, TimeUnit.SECONDS));
assertEquals(cancelActionValue.get(), cancelReason);
}
@Test
public void testTaskCancellationBeforeRun() throws InterruptedException {
final AtomicReference<Throwable> cancelActionValue = new AtomicReference<>();
Task<?> uncompleted = Task.async(() -> Promises.settable());
uncompleted.addListener(p -> {
if (p.isFailed() && Exceptions.isCancellation(p.getError())) {
cancelActionValue.set(p.getError().getCause());
}
} );
Exception cancelReason = new Exception();
assertTrue(uncompleted.cancel(cancelReason));
getEngine().run(uncompleted);
uncompleted.await(5, TimeUnit.SECONDS);
logTracingResults("TestTaskCancellation.testTaskCancellationBeforeRun", uncompleted);
assertEquals(cancelActionValue.get(), cancelReason);
}
@Test
public void testTaskCancellationAfterCompleted() throws InterruptedException {
final AtomicReference<Throwable> cancelActionValue = new AtomicReference<>();
Task<?> completed = Task.value(10);
completed.addListener(p -> {
if (p.isFailed() && Exceptions.isCancellation(p.getError())) {
cancelActionValue.set(p.getError().getCause());
}
} );
runAndWait("TestTaskCancellation.testTaskCancellationAfterCompleted", completed);
Exception cancelReason = new Exception();
assertFalse(completed.cancel(cancelReason));
assertNull(cancelActionValue.get());
}
@Test
public void testTaskCancellationPar() throws InterruptedException {
final AtomicReference<Throwable> cancelActionValue = new AtomicReference<>();
Task<Integer> completed = Task.value(10);
final CountDownLatch runLatch = new CountDownLatch(1);
final CountDownLatch listenerLatch = new CountDownLatch(1);
Task<Integer> uncompleted = Task.async(() -> {
runLatch.countDown();
return Promises.settable();
});
uncompleted.addListener(p -> {
if (p.isFailed() && Exceptions.isCancellation(p.getError())) {
cancelActionValue.set(p.getError().getCause());
}
listenerLatch.countDown();
} );
Task<?> task =
Task.par(completed, uncompleted).map((x, y) -> x + y).withTimeout(100, TimeUnit.MILLISECONDS).recover(e -> 0);
runAndWait("TestTaskCancellation.testTaskCancellationPar", task);
assertTrue(listenerLatch.await(5, TimeUnit.SECONDS));
assertTrue(cancelActionValue.get() instanceof EarlyFinishException);
}
@Test
public void testTaskCancellationTimeout() throws InterruptedException {
final AtomicReference<Throwable> cancelActionValue = new AtomicReference<>();
final CountDownLatch runLatch = new CountDownLatch(1);
final CountDownLatch listenerLatch = new CountDownLatch(1);
Task<Integer> uncompleted = Task.async(() -> {
runLatch.countDown();
return Promises.settable();
});
uncompleted.addListener(p -> {
if (p.isFailed() && Exceptions.isCancellation(p.getError())) {
cancelActionValue.set(p.getError().getCause());
}
listenerLatch.countDown();
} );
Task<?> task = uncompleted.withTimeout(10, TimeUnit.MILLISECONDS).recover(e -> 0);
runAndWait("TestTaskCancellation.testTaskCancellationTimeout", task);
assertTrue(listenerLatch.await(5, TimeUnit.SECONDS));
assertTrue(cancelActionValue.get() instanceof EarlyFinishException);
}
}