United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6639183 Scheduling large negative delay hangs entire ScheduledExecutor
JDK-6639183 : Scheduling large negative delay hangs entire ScheduledExecutor

Details
Type:
Bug
Submit Date:
2007-12-07
Status:
Resolved
Updated Date:
2010-12-03
Project Name:
JDK
Resolved Date:
2008-01-09
Component:
core-libs
OS:
windows_xp
Sub-Component:
java.util.concurrent
CPU:
x86
Priority:
P2
Resolution:
Fixed
Affected Versions:
6u3
Fixed Versions:
6u10 (b10)

Related Reports
Backport:
Backport:
Relates:

Sub Tasks

Description
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: 

both

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)

and

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)


Description:

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() {
            System.out.println(m_message);
            System.out.flush();
        }
    }
}

produces the following output as expected (after 10-15s):
one
two
three
four
five
six


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

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

problems:

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

                                    

Comments
WORK AROUND

manually change negative values to zero before scheduling
                                     
2007-12-07
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.
                                     
2007-12-07
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.
                                     
2007-12-07
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.
                                     
2007-12-09
EVALUATION

Note that the fix for 6725789 subsumes/replaces the simple fix suggested here.
                                     
2008-08-20



Hardware and Software, Engineered to Work Together