/*
* This file is part of ADDIS (Aggregate Data Drug Information System).
* ADDIS is distributed from http://drugis.org/.
* Copyright (C) 2009 Gert van Valkenhoef, Tommi Tervonen.
* Copyright (C) 2010 Gert van Valkenhoef, Tommi Tervonen,
* Tijs Zwinkels, Maarten Jacobs, Hanno Koeslag, Florin Schimbinschi,
* Ahmad Kamal, Daniel Reid.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.drugis.common.threading;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
public class ThreadHandlerIT {
private int d_numCores;
public static class SuspendableTestThread extends AbstractSuspendable {
private final int d_ms;
boolean d_done;
public SuspendableTestThread(final int ms) {
d_ms = ms;
}
public synchronized boolean getDone() {
return d_done;
}
public void run() {
if (d_done)
throw new IllegalStateException("Thread already done.");
try {
Thread.sleep(d_ms);
waitIfSuspended();
} catch (InterruptedException e) {
} catch (AbortedException t) {
}
d_done = true;
}
// FIXME: Breaks the contract of equals by being not transitive.
@Override
public boolean equals(final Object o) {
if (o instanceof SuspendableThreadWrapper)
return ((SuspendableThreadWrapper) o).getRunnable().equals(this);
else if (o instanceof SuspendableTestThread)
return super.equals(o);
return false;
}
};
class NonSuspendableTestThread implements Runnable {
private final int d_ms;
boolean d_done;
public NonSuspendableTestThread(final int ms) {
d_ms = ms;
}
public synchronized boolean getDone() {
return d_done;
}
@Override
public String toString() {
return ""+d_ms;
}
public void run() {
if (d_done)
throw new IllegalStateException("Thread already done.");
try {
Thread.sleep(d_ms);
} catch (InterruptedException e) {
}
d_done = true;
}
};
@Before
public void setUp() {
ThreadHandler.getInstance().clear();
d_numCores = Runtime.getRuntime().availableProcessors();
}
@Test
public void testQueueingOrder() {
LinkedList<SimpleTask> ToDo1 = new LinkedList<SimpleTask>();
LinkedList<SimpleTask> ToDo2 = new LinkedList<SimpleTask>();
for (int i = 0; i < d_numCores; ++i) {
ToDo1.add(new SimpleSuspendableTask(new SuspendableTestThread(400)));
if (i < d_numCores - 1) {
ToDo2.add(new SimpleSuspendableTask(new SuspendableTestThread(400)));
}
}
ThreadHandler th = ThreadHandler.getInstance();
th.scheduleTasks(ToDo1); // fills the available cores
th.scheduleTasks(ToDo2); // should push back every task except ToDo1[0].
sleepLongEnough();
List<SimpleTask> expected = new ArrayList<SimpleTask>(ToDo2);
expected.add(ToDo1.get(0));
assertEquals(expected, th.getRunningTasks());
}
@Test
public void testReprioritise() {
LinkedList<SimpleTask> ToDo1 = new LinkedList<SimpleTask>();
final int NUMMODELS = d_numCores + 2;
for(int i=0; i < NUMMODELS; ++i) {
ToDo1.add(new SimpleSuspendableTask(new SuspendableTestThread((i+4) * 400)));
}
ThreadHandler th = ThreadHandler.getInstance();
th.scheduleTasks(ToDo1);
List<SimpleTask> nCoresHeadList = ToDo1.subList(0, d_numCores);
List<SimpleTask> nCoresHeadListComplement = ToDo1.subList(d_numCores, d_numCores + (NUMMODELS - d_numCores));
sleepLongEnough();
assertTrue(th.getRunningTasks().containsAll(nCoresHeadList));
assertTrue(th.getQueuedTaskList().containsAll(ToDo1));
// Note: NOP; rescheduling already-running tasks should not change anything
th.scheduleTasks(nCoresHeadList);
sleepLongEnough();
assertTrue(th.getRunningTasks().containsAll(nCoresHeadList));
assertTrue(th.getQueuedTaskList().containsAll(ToDo1));
// reprioritise scheduled tasks by re-adding them; should displace running tasks
th.scheduleTasks(nCoresHeadListComplement);
sleepLongEnough();
assertTrue(th.getRunningTasks().containsAll(nCoresHeadListComplement));
assertTrue(th.getRunningTasks().containsAll(nCoresHeadList.subList(0, d_numCores - 2)));
assertTrue(th.getQueuedTaskList().containsAll(ToDo1));
}
@Test
public void testReprioritiseDontTouchNonSuspendable() {
LinkedList<SimpleTask> ToDo1 = new LinkedList<SimpleTask>();
LinkedList<SimpleTask> ToDo2 = new LinkedList<SimpleTask>();
for(int i=0; i < d_numCores - 1; ++i) {
ToDo1.add(new SimpleSuspendableTask(new SuspendableTestThread(600)));
ToDo2.add(new SimpleSuspendableTask(new SuspendableTestThread(600)));
}
ToDo1.add(new SimpleSuspendableTask(new NonSuspendableTestThread(600)));
ToDo2.add(new SimpleSuspendableTask(new SuspendableTestThread(400)));
ThreadHandler th = ThreadHandler.getInstance();
th.scheduleTasks(ToDo1);
sleepLongEnough();
assertEquals(th.getRunningTasks(), ToDo1);
LinkedList<SimpleTask> expected = new LinkedList<SimpleTask>(ToDo2.subList(0, ToDo2.size() - 1));
expected.addFirst(ToDo1.getLast());
th.scheduleTasks(ToDo2);
sleepLongEnough();
assertEquals(expected, th.getRunningTasks());
}
@Test
public void testHighLoad() {
final int NUMTHREADS = 100;
LinkedList<SimpleTask> runnables = new LinkedList<SimpleTask>();
ThreadHandler th = ThreadHandler.getInstance();
ArrayList<SimpleTask> threadList = new ArrayList<SimpleTask>(NUMTHREADS);
for (int i=0; i<NUMTHREADS; ++i) {
SimpleTask mod = new SimpleSuspendableTask(new SuspendableTestThread((int) (Math.random() * 100)));
threadList.add(mod);
runnables.add(mod);
if ((Math.random() > 0.75) || (i == (NUMTHREADS-1))) {
th.scheduleTasks(runnables);
runnables.clear();
}
sleep((int) (Math.random() * 50));
assertTrue(Runtime.getRuntime().availableProcessors() >= th.d_runningTasks.size());
}
assertTrue(Runtime.getRuntime().availableProcessors() >= th.d_runningTasks.size());
waitTillDone();
for (SimpleTask mod : threadList) {
assertTrue(mod.isFinished());
}
}
@Test
public void testTakeSuspendableSlot() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
th.scheduleTask(new SimpleSuspendableTask(new SuspendableTestThread(300)));
for (int i=0; i<d_numCores -1; ++i)
th.scheduleTask(new SimpleSuspendableTask(new NonSuspendableTestThread(300)));
SimpleTask newTask = new SimpleSuspendableTask(new SuspendableTestThread(300));
th.scheduleTask(newTask);
sleepLongEnough();
assertTrue(th.getRunningTasks().contains(newTask));
waitTillDone();
for (int i=0; i<d_numCores -1; ++i)
th.scheduleTask(new SimpleSuspendableTask(new NonSuspendableTestThread(300)));
th.scheduleTask(new SimpleSuspendableTask(new SuspendableTestThread(300)));
newTask = new SimpleSuspendableTask(new SuspendableTestThread(300));
th.scheduleTask(newTask);
sleepLongEnough();
assertTrue(th.getRunningTasks().contains(newTask));
waitTillDone();
}
@Test
public void testRemoveWaitingSimpleTask() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
List<SimpleTask> runthreads = new ArrayList<SimpleTask>();
SimpleTask t = new SimpleSuspendableTask(new SuspendableTestThread(100));
for (int i=0; i<d_numCores + 1; ++i) {
runthreads.add(new SimpleSuspendableTask(new SuspendableTestThread(100)));
}
runthreads.add(t);// 2 threads in waiting list.
th.scheduleTasks(runthreads);
assertEquals(d_numCores + 2, th.getQueuedTasks());
th.abortTask(t);
assertEquals(d_numCores + 1, th.getQueuedTasks());
waitTillDone();
assertFalse(t.isFinished());
}
@Test
public void testClearRemovesWaitingTasks() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
List<SimpleTask> runthreads = new ArrayList<SimpleTask>();
for (int i=0; i<d_numCores + 2; ++i) // 2 threads in waiting list.
runthreads.add(new SimpleSuspendableTask(new SuspendableTestThread(100)));
th.scheduleTasks(runthreads);
assertEquals(d_numCores + 2, th.getQueuedTasks());
th.clear();
assertEquals(0, th.getQueuedTasks());
}
@Test
public void testRemoveSuspendableRunningTask() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
SimpleTask task = new SimpleSuspendableTask(new SuspendableTestThread(10000));
th.scheduleTask(task);
sleepLongEnough();
assertEquals(1, th.getRunningThreads());
th.abortTask(task);
sleepLongEnough();
assertEquals(0, th.getRunningThreads());
assertFalse(task.isFinished());
assertTrue(task.isAborted());
assertFalse(task.isSuspended());
}
@Test
public void testClearRemovesSuspendableRunningTasks() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
List<SimpleTask> runthreads = new ArrayList<SimpleTask>();
for (int i=0; i<d_numCores + 2; ++i) // 2 threads in waiting list.
runthreads.add(new SimpleSuspendableTask(new SuspendableTestThread(300)));
th.scheduleTasks(runthreads);
sleepLongEnough();
assertEquals(d_numCores, th.getRunningThreads());
th.clear();
sleepLongEnough();
assertEquals(0, th.getRunningThreads());
}
@Test
public void testRemoveCantRemoveUnsuspendableRunningTask() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
SimpleSuspendableTask t = new SimpleSuspendableTask(new NonSuspendableTestThread(1000));
th.scheduleTask(t);
sleepLongEnough();
assertEquals(1, th.getRunningThreads());
assertFalse(th.abortTask(t));
sleepLongEnough();
assertEquals(1, th.getRunningThreads());
}
@Test
public void testClearCantRemoveUnsuspendableRunningTasks() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
List<SimpleTask> runthreads = new ArrayList<SimpleTask>();
for (int i=0; i<d_numCores; ++i) // numcore threads running.
runthreads.add(new SimpleSuspendableTask(new NonSuspendableTestThread(600)));
th.scheduleTasks(runthreads);
sleepLongEnough();
assertEquals(d_numCores, th.getRunningThreads());
th.clear();
sleepLongEnough();
assertEquals(d_numCores, th.getRunningThreads());
}
private void sleepLongEnough() {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
}
}
class SomeCompositeTask implements CompositeTask {
boolean d_started = false;
SimpleTask d_first = new SimpleSuspendableTask(new SuspendableTestThread(600));
SimpleTask d_middle1 = new SimpleSuspendableTask(new SuspendableTestThread(600));
SimpleTask d_middle2 = new SimpleSuspendableTask(new SuspendableTestThread(600));
SimpleTask d_last = new SimpleSuspendableTask(new SuspendableTestThread(600));
public List<SimpleTask> getNextTasks() {
if (!d_started) {
throw new RuntimeException("Can't get tasks if not started");
} else if (!d_first.isFinished()) {
return Collections.singletonList(d_first);
} else if (!d_middle1.isFinished() || !d_middle2.isFinished()) {
List<SimpleTask> list = new ArrayList<SimpleTask>();
if (!d_middle1.isFinished()) list.add(d_middle1);
if (!d_middle2.isFinished()) list.add(d_middle2);
return list ;
} else if (!d_last.isFinished()) {
return Collections.singletonList(d_last);
} else {
return Collections.<SimpleTask>emptyList();
}
}
public void start() {
d_started = true;
}
public boolean isStarted() {
return d_started;
}
public boolean isFinished() {
return d_last.isFinished();
}
public void addTaskListener(final TaskListener l) {}
public void removeTaskListener(final TaskListener l) {}
public boolean isFailed() { return false; }
public Throwable getFailureCause() { return null; }
public boolean isAborted() { return false; }
}
@Test
public void testScheduleCompositeTask() {
waitTillDone();
ThreadHandler threadHandler = ThreadHandler.getInstance();
SomeCompositeTask task = new SomeCompositeTask();
threadHandler.scheduleTask(task);
sleepLongEnough();
assertEquals(Collections.singletonList(task.d_first), threadHandler.getRunningTasks());
assertEquals(Collections.singletonList(task), threadHandler.getQueuedTaskList());
// wait for first task to finish and subsequent tasks to be enqueued
waitTillDone(task.d_first);
sleepLongEnough();
List<Task> expected = new ArrayList<Task>();
expected.add(task.d_middle1);
if (d_numCores >= 2) {
expected.add(task.d_middle2);
}
assertEquals(expected, threadHandler.getRunningTasks());
// wait for last task to be finished and its finishing to be detected
waitTillDone(task.d_last);
sleepLongEnough();
assertTrue(task.d_last.isFinished());
assertTrue(task.isFinished());
assertEquals(Collections.<Task>emptyList(), threadHandler.getQueuedTaskList());
}
@Test
public void testRemoveCompositeTask() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
SomeCompositeTask task = new SomeCompositeTask();
th.scheduleTask(task);
// wait for first task to finish and subsequent tasks to be enqueued
waitTillDone(task.d_first);
sleepLongEnough();
th.abortTask(task);
sleepLongEnough();
assertTrue(task.d_first.isFinished());
assertTrue(task.d_middle1.isAborted());
if (d_numCores > 1) {
assertTrue(task.d_middle2.isAborted());
}
assertEquals(0, th.getRunningTasks().size());
assertEquals(0, th.getQueuedTaskList().size());
}
@Test
public void testRemoveWaitingCompositeTask() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
List<Task> tasks = new ArrayList<Task>();
for (int i = 0; i < d_numCores; ++i) {
tasks.add(new SimpleSuspendableTask(new SuspendableTestThread(300)));
}
SomeCompositeTask composite = new SomeCompositeTask();
tasks.add(composite);
th.scheduleTasks(tasks);
sleepLongEnough();
th.abortTask(composite);
sleepLongEnough();
}
@Test
public void testRemoveHalfWaitingCompositeTask() {
waitTillDone();
ThreadHandler th = ThreadHandler.getInstance();
List<Task> tasks = new ArrayList<Task>();
for (int i = 0; i < d_numCores - 1; ++i) {
tasks.add(new SimpleSuspendableTask(new SuspendableTestThread(1000)));
}
SomeCompositeTask composite = new SomeCompositeTask();
tasks.add(composite);
th.scheduleTasks(tasks);
waitTillDone(composite.d_first);
sleepLongEnough();
th.abortTask(composite);
sleepLongEnough();
}
private void waitTillDone(final SimpleTask task) {
try {
TaskUtil.waitUntilReady(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testScheduleCompositeTaskWithDuplicateTask() {
ThreadHandler threadHandler = ThreadHandler.getInstance();
SomeCompositeTask task = new SomeCompositeTask();
SimpleTask duplicate = task.d_first;
threadHandler.scheduleTask(duplicate);
threadHandler.scheduleTask(task);
sleepLongEnough();
List<Task> expected = new ArrayList<Task>();
expected.add(task);
expected.add(duplicate);
assertEquals(Collections.singletonList(duplicate), threadHandler.getRunningTasks());
assertEquals(expected, threadHandler.getQueuedTaskList());
}
@Test
public void testScheduleCompositeTaskWithDuplicateTaskVV() {
ThreadHandler threadHandler = ThreadHandler.getInstance();
SomeCompositeTask task = new SomeCompositeTask();
SimpleTask duplicate = task.d_first;
threadHandler.scheduleTask(task);
threadHandler.scheduleTask(duplicate);
sleepLongEnough();
List<Task> expected = new ArrayList<Task>();
expected.add(duplicate);
expected.add(task);
assertEquals(Collections.singletonList(duplicate), threadHandler.getRunningTasks());
assertEquals(expected, threadHandler.getQueuedTaskList());
}
@Test
public void testRemoveCompositeTaskWithDuplicateTask() {
ThreadHandler threadHandler = ThreadHandler.getInstance();
SomeCompositeTask task = new SomeCompositeTask();
SimpleTask duplicate = task.d_first;
threadHandler.scheduleTask(duplicate);
threadHandler.scheduleTask(task);
sleepLongEnough();
List<Task> expected = new ArrayList<Task>();
expected.add(task);
expected.add(duplicate);
assertEquals(Collections.singletonList(duplicate), threadHandler.getRunningTasks());
assertEquals(expected, threadHandler.getQueuedTaskList());
threadHandler.abortTask(task);
sleepLongEnough();
assertFalse(duplicate.isFinished());
expected.remove(task);
assertEquals(expected, threadHandler.getQueuedTaskList());
assertEquals(Collections.singletonList(duplicate), threadHandler.getRunningTasks());
}
public static void waitTillDone() {
ThreadHandler th = ThreadHandler.getInstance();
while ((th.d_runningTasks.size() > 0) || (th.d_scheduledTasks.size() > 0)) {
sleep(100);
}
}
private static void sleep(final int ms ) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}