JDK-6378434 : (thread) IllegalThreadStateException invoking Thread.start after getting OOME (jdk5.0)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 5.0
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: linux
  • CPU: x86
  • Submitted: 2006-01-30
  • Updated: 2011-02-16
  • Resolved: 2007-08-28
Related Reports
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_04-b05)
Java HotSpot(TM) Client VM (build 1.5.0_04-b05, mixed mode, sharing)


ADDITIONAL OS VERSION INFORMATION :
Linux jks-desktop 2.6.12-1.1376_FC3 #1 Fri Aug 26 23:27:26 EDT 2005 i686 athlon i386 GNU/Linux


A DESCRIPTION OF THE PROBLEM :
If a Thread.start() throws an OutOfMemoryError because a native system thread couldn't be created, trying to call start() again results in a IllegalThreadStateException, even though the the thread never started.  I was not able to reduce the problem in jdk 1.4.2, so this seems to be a regressions.


REPRODUCIBILITY :
This bug can be reproduced often.

---------- BEGIN SOURCE ----------
public class ThreadSpinner {

    /** Tries to start a thread until successfull. */
    private static void start_thread(final Thread orig_t){
        Thread t = orig_t;
        boolean sent_off = false;
        int times_tried = 0;
        while (true) try{
            times_tried++;
            t.start();
            sent_off = true;
            break;
        } catch(IllegalThreadStateException e){
            e.printStackTrace();
            System.exit(1);
        }catch(OutOfMemoryError e){
            if (times_tried > 1)
                System.err.println("already tried "+times_tried+" times");
            t = new Thread(orig_t.getThreadGroup(), orig_t, orig_t.getName());
            try{ Thread.sleep(3000); }
            catch(InterruptedException e2){}
        }
    }

    public static void main(String[] args){
        ThreadGroup tg = new ThreadGroup("spun");

        final Object waiter = new Object();
        final long hour = 60 * 60 * 1000;

        int i = 0;

        while(true){
            i++;
            Runnable r = new Runnable(){
                    public void run(){
                        //try{ Thread.sleep(hour); } catch(Exception e){};
                        try{ waiter.wait(hour); } catch(Exception e){};
                    }};


            Thread t = new Thread(tg, r,"thread "+i,1000000);
            start_thread(t);
        }
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use a Runnable, so a new Thread can be created each time through the loop.

Comments
EVALUATION As the previous comment states, with Java SE 6 if Thread.start does not complete successfully then a subsequent attempt to start the thread is permitted. This is still the case in Java SE 7 and CR 6379235 will take care of any unwanted side effects (in ThreadGroup) of this.
28-08-2007

SUGGESTED FIX Except that the current code *does* now use threadStatus (a side effect of another change), but that didn't change the behavior. This suggests the JVM has already changed the thread state at the point of the OOM. So this may require fixes to both the Thread code (to catch/rethrow the exception *after* undoing the group add) and to somehow get the thread status back to "NEW", probably via some mechanism to do with classes_management.
23-04-2007

SUGGESTED FIX A fix for SE 5 would be substituting use of Thread.threadStatus for "started" in combination with the changes for CR 6379235 (not that this is suggesting that a fix should be applied to 5).
31-01-2006

EVALUATION This defect is specific to Java SE version 5 and is fixed in "Mustang" (aka version 6 in development) With Java SE 1.4.2, thread state is hidden from users and a Thread instance' relation to its ThreadGroup instance is established at a different time than with Java SE 5 (and Mustang). With 1.4.2 a Thread instance is added to it's ThreadGroup instance as a side effect of Thread instantiation and the Thread.start method does nothing except use a call into the VM to get it started. So a failure in Thread.start may not have advanced the VM's internal accounting of the thread's state, allowing the start method to be invoked again safely. But if the VM judges the thread to be started (even if it explodes before executing one bytecode) then 1.4.2 will throw IllegalStateException and this will include cases where it's obvious the thread is not running. With Java SE 5 Thread.start is responsible for adding the thread to its ThreadGroup instance and the JVM call is then used for startup as with 1.4.2. A private boolean field was added to Thread that is set early in the Thread.start method but stays set in the case that the start fails. This prevents a second invocation of start even though the Thread is clearly not started (i.e. it's state can be checked and will still be NEW). So instantiation of a new Thread is the only workaround with 5, as the submitted test case demonstrates. With Mustang, retrying Thread.start is only prevented if the thread state has moved past "NEW" (e.g. could now be in "RUNNABLE" or some other state). This effectively restores the 1.4.2 behavior. However this CR has exposed a weakness (thank you for reporting this bug!). If the VM throws an exception like OOM, ThreadGroup is left in a bad state such that invoking start again would result in a duplication of Thread references and peculiar behavior (e.g. ThreadGroup.activeCount would report a too high value). So the current code trades the ability to avoid repeating instantiation of a Thread to recover for a chance (albeit very small) of broken ThreadGroup accounting. This (ThreadGroup hazard) should be fixed as CR 6379235. If it is start's use of a ThreadGroup method that throws an exception then start can be reused safely (i.e. the TheadGroup state is still consistent). "All of the above Mustang details are subject to change." As recovery from transient OOM seems straight forward the priority of this CR will remain low.
31-01-2006