JDK-6639183 : Scheduling large negative delay hangs entire ScheduledExecutor
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 6u3
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2007-12-07
  • Updated: 2010-12-03
  • Resolved: 2008-01-09
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other JDK 6
5.0u16-revFixed 6u10 b10Fixed
Related Reports
Relates :  
type: bug
product: j2se
sub-cat: java.util.*
release: Java 6
O/S: Windows XP

Synopsis: Scheduling large negative delay hangs entire ScheduledExecutor

Full O/S version: Microsoft Windows XP [Version 5.1.2600]
java -version: 


java version "1.5.0_12"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_12-b04)
Java HotSpot(TM) Client VM (build 1.5.0_12-b04, mixed mode)


java version "1.6.0_03"
Java(TM) SE Runtime Environment (build 1.6.0_03-b05)
Java HotSpot(TM) Client VM (build 1.6.0_03-b05, mixed mode)


Scheduling something with a very large negative delay (using TimeUnit.MILLISECONDS) blocks the scheduled executor from running anything.  Other than perhaps relative ordering, negative delay (no matter how large) is effectively the same as zero delay and the scheduled item ought to be eligible to run immediately.

Frequency: always

Regression: no

Steps to reproduce:

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ScheduleBug {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);

        exec.schedule(new Op("one"), -1000, TimeUnit.MILLISECONDS);
        exec.schedule(new Op("two"), -100, TimeUnit.MILLISECONDS);
        exec.schedule(new Op("three"), -10, TimeUnit.MILLISECONDS);
        exec.schedule(new Op("four"), 0, TimeUnit.MILLISECONDS);
        exec.schedule(new Op("five"), 1000, TimeUnit.MILLISECONDS);
        exec.schedule(new Op("six"), 5000, TimeUnit.MILLISECONDS);
        exec.schedule(new Runnable() {
            public void run() { System.exit(0); }
        }, 10000, TimeUnit.MILLISECONDS);

    static class Op implements Runnable {
        private final String m_message;

        public Op(String message)
        {   m_message = message;    }

        public void run() {

produces the following output as expected (after 10-15s):

However, change the first schedule line from
        exec.schedule(new Op("one"), -1000, TimeUnit.MILLISECONDS);
        exec.schedule(new Op("one"), Long.MIN_VALUE, TimeUnit.MILLISECONDS);

and the program will hang for hours with no output at all.


1) should be exact same output.
2) Even if underflow were documented / specified behavior, scheduling op "one" far in the future should NOT prevent other ops from running right away

severity: some progress possible

EVALUATION Note that the fix for 6725789 subsumes/replaces the simple fix suggested here.

EVALUATION David Holmes writes, "The reason I think it hangs (it's a tricky one to puzzle out) is because the bad Delayed item gets correctly queued at the head of the delay queue, but because of the way the initial delay is stored and the way getDelayed() works, you get an underflow that turns the delay into a large positive value - hence no Delayed entries get processed because this is the one with the next delay and it claims not to be ready." which makes sense to me.

EVALUATION Looking closer.... Changing the Runnable to a Callable pool.schedule( new Callable<Void>() { public Void call() { System.out.println("Age of the dinosaurs"); return null; }}, Long.MIN_VALUE, NANOSECONDS); makes the bug go away. Which strongly hints that this easy change make in 7-b08 should be backported: --- /tmp/geta27573 2007-12-07 14:31:24.283954000 -0800 +++ ScheduledThreadPoolExecutor.java 2007-12-07 14:30:45.394875000 -0800 @@ -355,14 +355,15 @@ } public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); + if (delay < 0) delay = 0; long triggerTime = now() + unit.toNanos(delay); RunnableScheduledFuture<?> t = decorateTask(command, new ScheduledFutureTask<Boolean>(command, null, triggerTime)); delayedExecute(t); return t; } It appears that this problem is due to large negative values underflowing. This problem may appear elsewhere in java.util.concurrent.

EVALUATION This is reproducible with jdk 7-b07 and 6u5, but no longer reproducible in jdk 7-b08 which had a large complex rewrite of much of ThreadPoolExecutor and ScheduledThreadPoolExecutor Here's a smaller test case demonstrating the problem: import java.util.concurrent.ScheduledThreadPoolExecutor; import static java.util.concurrent.TimeUnit.*; public class Bug { public static void main(String[] args) throws Throwable { ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1); pool.schedule(new Runnable() { public void run() { System.out.println("Age of the dinosaurs"); }}, Long.MIN_VALUE, NANOSECONDS); pool.schedule(new Runnable() { public void run() { System.out.println("Yesterday, all my troubles ..."); }}, -1L, DAYS); pool.shutdown(); pool.awaitTermination(10L, SECONDS); } } Here's the list of CRs fixed in jdk 7-b08 6450200: ThreadPoolExecutor idling core threads don't terminate when core pool size reduced 6450205: ThreadPoolExecutor does not replace throwing threads 6450207: ThreadPoolExecutor doesn't count throwing tasks as "completed" 6450211: ThreadPoolExecutor.afterExecute sees RuntimeExceptions, but not Errors 6454289: ScheduledThreadPoolExecutor spins while waiting for delayed tasks after shutdown 6458339: ThreadPoolExecutor very slow to shut down for large poolSize 6458662: ThreadPoolExecutor poolSize might shrink below corePoolSize after timeout 6459119: Explain how afterExecute can access a submitted job's Throwable I don't know what exactly changed to cause this bug to go away.

WORK AROUND manually change negative values to zero before scheduling