28

Future.get(timeout) does not reliably throw the TimeoutException after the given timeout. Is this normal behavior or can I do something to make this more reliable? This test fails on my machine. However if I sleep for 3000 instead of 2000, it will pass.

public class FutureTimeoutTest {
@Test
public void test() throws
    ExecutionException,
    InterruptedException {

    ExecutorService exec = Executors.newSingleThreadExecutor();
    final Callable call = new Callable() {
        @Override
        public Object call() throws Exception {
             try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            return 0;
        }
    };
    final Future future = exec.submit(call);
    try {
        future.get(1000, TimeUnit.MILLISECONDS);
        fail("expected TimeoutException");
    } catch (TimeoutException ignore) {
    }
}

}

Andrew Raphael
  • 507
  • 2
  • 7
  • 9
  • Interesting. Does it vary by platform? It could be a bug in the JVM or the OS. – JOTN Dec 03 '10 at 23:54
  • I'm not sure this would really count as a bug, per se. There's bound to be some non-determinism in how much time will have passed between starting that other thread and even starting to wait for the Future to complete. Yes, 1 second seems like a bit much, but I don't see anything in the docs that actually make hard guarantees about deadlines. – Laurence Gonsalves Dec 04 '10 at 00:04
  • I have only tried it on Windows XP. I agree that there could be non-determinism, however there is not much happening in this example. My real-world case has multiple threads and there is blocking within JNI on network responses. I can understand some variability in the timeout in that case but here? – Andrew Raphael Dec 04 '10 at 00:20
  • What JVM/JDK/IDE/TestFramework are you using? This runs fine in Sun JDK6 + jUnit4 + Netbeans. – Affe Dec 04 '10 at 00:31
  • @Seh points out that it could be a thread contention issue: what happens if you change the test to run in a single thread using a `FutureTask`? – Cameron Skinner Dec 04 '10 at 00:52
  • This runs fine on my machine too, Sun JDK6, Windows Server 2008 R2. – codeplay Dec 04 '10 at 01:39

3 Answers3

15

There is no reason to expect the test to pass. Given that you submit the task for execution and then wait on its completion, any amount of time could pass before your wait on Future#get() begins, allowing the task plenty of time to exhaust the sleep duration and complete.

In your case, we can assume that the thread running within the Executor gets focus while your main thread running through test() is on hold, despite being in a runnable state. As for the observed difference between stalling the submitted task for two and three seconds, I expect you could find situations where even three seconds is insufficient, depending on what other processes are busy doing on your computer.

seh
  • 14,999
  • 2
  • 48
  • 58
11

@seh is right.

You are expecting what is commonly called "real-time" behavior from Java. This cannot be achieved reliably unless you use real-time libraries in a real-time capable Java distribution running on a real-time operating system.

Just to illustrate, the Java thread implementation in modern JVMs like HotSpot relies on the host operating system's native thread scheduler to decide what threads to run when. Unless the thread scheduler is specifically aware of real-time deadlines and stuff, it is likely to take a "whole of system" view when deciding what threads to run when. If the system is loaded, any particular thread may not get scheduled to run for seconds ... or longer ... after the conditions that prevented it running (e.g. waiting for a timer event) have passed.

Then there is the problem that the Java GC may cause all other threads to block.

If you really need real-time behavior from Java, it is available. For example:

However, you should expect to change your applications to use different APIs to give you real-time behavior.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
9

I have to say, I think the other two answers currently have an unnecessarily low opinion of the Java concurrency classes. They will not give you millisecond accuracy (what "real" real-time applications expect) but they do quite well usually. I've written large scale commercial services using Futures and Executors and they normally worked within 10 milliseconds of the expected times, even under load.

I've run this test both on MacOS 10.6 with Java 1.6 and WinXP w/ Java 1.6.0_22 and both of them work as expected.

I modified the code as follows to test the accuracy:

    long time1 = System.nanoTime();

    System.out.println("Submitting");
    final Future<Object> future = exec.submit(call);
    try {
        future.get(1000, TimeUnit.MILLISECONDS);

        long time2 = System.nanoTime();
        System.out.println("No timeout after " + 
                             (time2-time1)/1000000000.0 + " seconds");

        fail("expected TimeoutException");
    } catch (TimeoutException ignore) {
        long time2 = System.nanoTime();
        System.out.println("Timed out after " +
                             (time2-time1)/1000000000.0 + " seconds");
    }
    finally {
        exec.shutdown();
    }

In XP this prints "timed out after 1.002598934 seconds" and in MacOS X it prints "timed out after 1.003158 seconds".

If the original poster would describe their OS and JDK version, perhaps we could determine if this is a particular bug.

andrewmu
  • 14,276
  • 4
  • 39
  • 37
  • try running that on a loaded system and see if it is as accurate that. On a good day it works, on a bad day it doesn't, so don't code your applications (or unit tests) to *rely* on it. – Stephen C Dec 04 '10 at 02:58
  • @Stephen-C - Of course I wouldn't depend on always getting perfect timings in a production application, but I have actually used code like this for time-critical services in a very large dot-com and for at least 99.9% of cases it functioned as required (we had a ~50 ms margin of safety). – andrewmu Dec 04 '10 at 10:04
  • 1
    I tried it on Mac OSX 10.6.5 with Java 1.6 and it works. At the office I was using Windows XP Sun Java 1.6 - not sure of the minor version but it is fairly up to date. That was failing on Friday. I did notice that my real-world code would have this unreliable timeout most days but I did have a day when the timeout would occur within a few ms. of the time specified in the Future.get(). – Andrew Raphael Dec 05 '10 at 21:34
  • Thank you all for the good discussion. My system can handle the variability in the timeout (I think). It is an implementation of the circuit breaker pattern. As long as the timeout does occur eventually, the circuit breaker is at least adding value. I will wait until this code gets into the performance test environment to see what happens there. It would be nice to see it behave like @andrewmu's systems... – Andrew Raphael Dec 05 '10 at 21:53
  • For reference, the systems I mentioned were running on Redhat Linux (probably 2.4 kernel) with Java 1.5. It seems unlikely (though not impossible) that it would have regressed much in Java 1.6 – andrewmu Dec 06 '10 at 22:34