package org.threadly.concurrent;
import static org.junit.Assert.*;
import static org.threadly.TestConstants.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import org.junit.Test;
import org.threadly.BlockingTestRunnable;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.test.concurrent.TestCondition;
import org.threadly.test.concurrent.TestRunnable;
import org.threadly.util.Clock;
@SuppressWarnings("javadoc")
public class SingleThreadSchedulerTest extends AbstractPrioritySchedulerTest {
@Override
protected AbstractPrioritySchedulerFactory getAbstractPrioritySchedulerFactory() {
return new SingleThreadSchedulerFactory();
}
@Override
protected boolean isSingleThreaded() {
return true;
}
@SuppressWarnings("unused")
@Test (expected = IllegalArgumentException.class)
public void constructorFail() {
new SingleThreadScheduler(null);
}
@Test
public void isShutdownTest() {
SingleThreadScheduler sts = new SingleThreadScheduler();
assertFalse(sts.isShutdown());
executeTestRunnables(sts, 0);
sts.shutdown();
assertTrue(sts.isShutdown());
sts = new SingleThreadScheduler();
executeTestRunnables(sts, 0);
sts.shutdownNow();
assertTrue(sts.isShutdown());
}
@Test
public void isTerminatedTest() throws InterruptedException {
SingleThreadScheduler sts = new SingleThreadScheduler();
assertFalse(sts.isTerminated());
sts.shutdown();
assertTrue(sts.isTerminated()); // termination should be immediate since no tasks were submitted
BlockingTestRunnable btr = new BlockingTestRunnable();
sts = new SingleThreadScheduler();
try {
sts.execute(btr);
btr.blockTillStarted();
sts.shutdown();
// not terminated yet
assertFalse(sts.isTerminated());
btr.unblock();
sts.awaitTermination();
assertTrue(sts.isTerminated());
} finally {
btr.unblock();
sts.shutdown();
}
}
@Test
public void shutdownTest() {
SingleThreadScheduler sts = new SingleThreadScheduler();
/* adding a run time to have greater chances that runnable
* will be waiting to execute after shutdown call.
*/
TestRunnable lastRunnable = executeTestRunnables(sts, 5).get(TEST_QTY - 1);
sts.shutdown();
// runnable should finish
lastRunnable.blockTillFinished();
}
@Test
public void shutdownRecurringTest() {
final SingleThreadScheduler sts = new SingleThreadScheduler();
TestRunnable tr = new TestRunnable();
sts.scheduleWithFixedDelay(tr, 0, 0);
tr.blockTillStarted();
sts.shutdown();
new TestCondition(() -> ! sts.sManager.execThread.isAlive()).blockTillTrue();
}
@Test
public void shutdownNowTest() {
SingleThreadScheduler sts = new SingleThreadScheduler();
// execute one runnable which will not complete
BlockingTestRunnable btr = new BlockingTestRunnable();
sts.execute(btr);
try {
List<TestRunnable> expectedRunnables = executeTestRunnables(sts, 0);
btr.blockTillStarted();
List<Runnable> canceledRunnables = sts.shutdownNow();
// unblock now so that others can run (if the unit test fails)
btr.unblock();
assertNotNull(canceledRunnables);
assertTrue(canceledRunnables.containsAll(expectedRunnables));
assertTrue(expectedRunnables.containsAll(canceledRunnables));
Iterator<TestRunnable> it = expectedRunnables.iterator();
while (it.hasNext()) {
assertEquals(0, it.next().getRunCount());
}
} finally {
btr.unblock();
}
}
@Test
public void shutdownNowIgnoreCanceledFuturesTest() {
SingleThreadScheduler sts = new SingleThreadScheduler();
try {
Runnable nonCanceledRunnable = new TestRunnable();
sts.submitScheduled(nonCanceledRunnable, 1000 * 60 * 60);
ListenableFuture<?> future = sts.submitScheduled(DoNothingRunnable.instance(),
1000 * 60 * 60);
future.cancel(false);
List<Runnable> result = sts.shutdownNow();
assertEquals(1, result.size()); // only canceled task removed
} finally {
sts.shutdown();
}
}
@Test
public void awaitTerminationTest() throws InterruptedException {
SingleThreadScheduler sts = new SingleThreadScheduler();
/* adding a run time to have greater chances that runnable
* will be waiting to execute after shutdown call.
*/
TestRunnable lastRunnable = executeTestRunnables(sts, 5).get(TEST_QTY - 1);
sts.shutdown();
sts.awaitTermination();
// runnable should already be done
assertTrue(lastRunnable.ranOnce());
}
@Test
public void awaitTerminationWithTimeoutTest() throws InterruptedException {
SingleThreadScheduler sts = new SingleThreadScheduler();
/* adding a run time to have greater chances that runnable
* will be waiting to execute after shutdown call.
*/
TestRunnable lastRunnable = executeTestRunnables(sts, 5).get(TEST_QTY - 1);
sts.shutdown();
assertTrue(sts.awaitTermination(1000 * 10));
// runnable should already be done
assertTrue(lastRunnable.ranOnce());
}
@Test
public void awaitTerminationWithTimeoutExpireTest() throws InterruptedException {
SingleThreadScheduler sts = new SingleThreadScheduler();
executeTestRunnables(sts, 1000 * 10);
long start = Clock.accurateForwardProgressingMillis();
sts.shutdown();
assertFalse(sts.awaitTermination(DELAY_TIME));
assertTrue(Clock.accurateForwardProgressingMillis() - start >= (DELAY_TIME - ALLOWED_VARIANCE));
}
@Test (expected = RejectedExecutionException.class)
public void shutdownExecutionFail() {
SingleThreadScheduler sts = new SingleThreadScheduler();
sts.shutdown();
sts.execute(DoNothingRunnable.instance());
}
@Test (expected = RejectedExecutionException.class)
public void shutdownNowExecutionFail() {
SingleThreadScheduler sts = new SingleThreadScheduler();
sts.shutdownNow();
sts.execute(DoNothingRunnable.instance());
}
private class SingleThreadSchedulerFactory implements AbstractPrioritySchedulerFactory {
private final List<SingleThreadScheduler> schedulers = new ArrayList<>(1);
@Override
public void shutdown() {
Iterator<SingleThreadScheduler> it = schedulers.iterator();
while (it.hasNext()) {
it.next().shutdown();
it.remove();
}
}
@Override
public SubmitterExecutor makeSubmitterExecutor(int poolSize, boolean prestartIfAvailable) {
return makeSchedulerService(poolSize, prestartIfAvailable);
}
@Override
public SubmitterScheduler makeSubmitterScheduler(int poolSize, boolean prestartIfAvailable) {
return makeSchedulerService(poolSize, prestartIfAvailable);
}
@Override
public SchedulerService makeSchedulerService(int poolSize, boolean prestartIfAvailable) {
SingleThreadScheduler result = makeAbstractPriorityScheduler(poolSize);
if (prestartIfAvailable) {
if (prestartIfAvailable) {
result.prestartExecutionThread(true);
}
}
return result;
}
@Override
public AbstractPriorityScheduler makeAbstractPriorityScheduler(int poolSize,
TaskPriority defaultPriority,
long maxWaitForLowPriority) {
SingleThreadScheduler sts = new SingleThreadScheduler(defaultPriority, maxWaitForLowPriority);
schedulers.add(sts);
return sts;
}
@Override
public SingleThreadScheduler makeAbstractPriorityScheduler(int poolSize) {
SingleThreadScheduler sts = new SingleThreadScheduler();
schedulers.add(sts);
return sts;
}
}
}