2

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 (in evaluate()) should be a daemon thread?
  • Is CountDownLatch just used for blocking the getResult() until thread is running? Does it really work (I thought nothing can avert a context switch between callable.awaitStarted() and getResult())? 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.

Morrissss
  • 324
  • 2
  • 8

1 Answers1

4

Why thread (in evaluate()) should be a daemon thread?

If there is anything wrong with the test it allows JVM to exit properly. See also What is Daemon thread in Java?

Is CountDownLatch just used for blocking the getResult() until thread is running? Does it really work (I thought nothing can avert a context switch between callable.awaitStarted() and getResult())? Is there any "simpler" way to do this?

Nothing can avert context switch, but if the thread is already started and running it will get eventually some CPU attention and originalStatement.evaluate() will be executed. The reason why to do it this way is that there might not be underlying operating system thread available and then the test execution of the test might fail although the test itself is correct. There are also other ways how to do that e.g. Semaphore, but CountDownLatch is pretty efficient and cheap primitive to do that.

Community
  • 1
  • 1
JiriS
  • 6,870
  • 5
  • 31
  • 40
  • Thanks a lot for your advice, now I understand Daemon thread. For the second question, I've edited my post with some more explanation, hope you could answer that. Thanks again. – Morrissss Jun 04 '15 at 09:04
  • You're partially right with "If there are plenty of other threads, there can be unnegligible time interval between them." in a way that it might take some time to switch contexts, but in practice if don't have too many threads in Runnable state and most the threads have the same priority it should be negligible time interval. Also I would guess that the `timeout` is not meant as a tool for time measurement, but rather as a safety for misbehaving threads. – JiriS Jun 04 '15 at 09:28
  • `FailOnTimeout` is used to test whether a test method can finish within `timeout`. So `timeout` is used for time measurement. I think `callable.awaitStarted()` cannot prevent the test method from starting, and `task.get()` cannot prevent threads other than "CallableStatement thread" being awakened before it. So `CountDownLatch` doesn't work better than simply using `FutureTask` and wishing it would be awakened as soon as possible. – Morrissss Jun 04 '15 at 09:49
  • 1
    Should have written **precise** time measurements. My guess is that there won't be under normal circumstances more than one (or a few) milliseconds (or maybe rather hundreds of nanoseconds) difference at most, but haven't tried that. In general the difference is much much smaller than would be a difference without using `CountDownLatch` as thread initialization might take a *while*. – JiriS Jun 04 '15 at 10:00
  • 1
    I was involved in writing and reviewing changes to `FailOnTimeout` and @JiriS is correct. In some environments, thread startup time can be significant and variable. Waiting for the latch allows `FailOnTimeout` to not "start the clock" until the wrapped `Statement` has started. Yes, that means that occasionally your test might pass if the runtime was just over the specified limit, but we think that's acceptable. – NamshubWriter Jun 06 '15 at 21:57