I just started to read code of JUnit 4.13 (https://github.com/junit-team/junit), and get a little confused about the implementation of org.junit.internal.runners.statements.FailOnTimeout
:
@Override
public void evaluate() throws Throwable {
CallableStatement callable = new CallableStatement();
FutureTask<Throwable> task = new FutureTask<Throwable>(callable);
ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup");
Thread thread = new Thread(threadGroup, task, "Time-limited test");
thread.setDaemon(true);
thread.start();
callable.awaitStarted();
Throwable throwable = getResult(task, thread);
if (throwable != null) {
throw throwable;
}
}
/**
* Wait for the test task, returning the exception thrown by the test if the
* test failed, an exception indicating a timeout if the test timed out, or
* {@code null} if the test passed.
*/
private Throwable getResult(FutureTask<Throwable> task, Thread thread) {
try {
if (timeout > 0) {
return task.get(timeout, timeUnit); // HERE limits the time
} else {
return task.get();
}
} catch (InterruptedException e) {
return e; // caller will re-throw; no need to call Thread.interrupt()
} catch (ExecutionException e) {
// test failed; have caller re-throw the exception thrown by the test
return e.getCause();
} catch (TimeoutException e) {
return createTimeoutException(thread);
}
}
where CallableStatement
is:
private class CallableStatement implements Callable<Throwable> {
private final CountDownLatch startLatch = new CountDownLatch(1);
public Throwable call() throws Exception {
try {
startLatch.countDown();
originalStatement.evaluate(); // HERE the test runs
} catch (Exception e) {
throw e;
} catch (Throwable e) {
return e;
}
return null;
}
public void awaitStarted() throws InterruptedException {
startLatch.await();
}
}
Here is my understanding of the code:
evaluate()
starts a new thread for the test method. callable.awaitStarted()
blocks evaluate()
until startLatch.countDown()
, and then getResult()
times the test method.
Here is my question:
- Why
thread
(inevaluate()
) should be a daemon thread? - Is
CountDownLatch
just used for blocking thegetResult()
untilthread
is running? Does it really work (I thought nothing can avert a context switch betweencallable.awaitStarted()
andgetResult()
)? Is there any "simpler" way to do this?
I'm not very familiar with concurrency. And I'd appreciate it a lot if someone can explain these or pointing out my errors. Thanks.
More explanation about the second question:
I would denote the two threads as "evaluate() thread" and "CallableStatement thread".
I think "evaluate() thread" is blocked when callable.awaitStarted()
is executed until startLatch.countDown()
is done, but the test method may start to run before the context switched back to "evaluate() thread". Right after "evaluate() thread" is awakened again, it calls FutureTask.get()
, which will block the "evaluate() thread", but cannot ensure that "CallableStatement thread" will be awakened right after that.
So, I think the moment the test method beginning has nothing to do with the moment task.get(timeout, timeUnit)
being called. If there are plenty of other threads, there can be unnegligible time interval between them.