JDK-4010086 : Thread.stop(Throwable obj) can be caught, but thead apparently marked as dead
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.0.2
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_2.5
  • CPU: sparc
  • Submitted: 1996-10-18
  • Updated: 1997-10-23
  • Resolved: 1997-10-23
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
1.1 1.1Fixed
Related Reports
Relates :  
Description
Compile and run the enclosed file as a Java application.
I have annotated the application trace output with lines marked ">> ".

The spec says that this "is an unusual action to take".  If you consider
the example code wrong-headed, the threads implementation is still strange.
It unwinds the computation partway and lets the thread continue to run outside
the "catch" clause.  Then it terminates without executing the "finally" clause,
which should run according to the language specification.

3 clam% java ActiviTest
Action thread ready for request.
>> Press the "compute" button.
Application issuing request.
Application request issued.
Application showing killer dialog.
Action thread got request ActiviTest@ee700210
Sleeping.
Application notified, end of ActiviTest@ee700210
Application popping down monitor window.
Action thread completed request.
Action thread ready for request.
>> Press the "compute" button again.
Application issuing request.
Application request issued.
Application showing killer dialog.
Action thread got request ActiviTest@ee7003e0
Sleeping.
>> Press the "Abort" button.
Killer dialog requesting abort of ActiviTest@ee7003e0
Stop returned to caller.
Application notified, end of ActiviTest@ee7003e0
Application popping down monitor window.
>> At this point in the output there is a delay as the sleep completes
>> its full specified time.
Application notified, end of ActiviTest@ee7003e0
>> Notice that the action thread continues running and waits
>> for the next request.
Action thread caught abort.
Action thread ready for request.
>> Press the "compute" button again.
Application issuing request.
>> The event handler acquires the lock on the action thread's queue
>> then checks to see if it is alive.  I added this check because
>> the action thread was not responding.
Action thread is dead.
>> Note that the action thread never indicates that it is dying
>> even though there is a "finally" clause in its main loop and
>> the language specification states that "finally" clauses execute
>> when threads die.

Starting new action thread.



////////////////////////////////////////////////////////////////////////////
//
// %W% %G% %U%
//
// Copyright %G% Sun Microsystems, Inc. All Rights Reserved
//
////////////////////////////////////////////////////////////////////////////

// package COM.sun.share.ejava.test;
import java.util.*;
import java.awt.*;
import java.io.*;



/** A sample activity that takes some time.
 */
public class ActiviTest extends Activity {


public void act() {
    System.err.println("Sleeping.");
    try {
	Thread.sleep(5000);
    } catch(InterruptedException e) {
	System.err.println("InterrunptedException.");
    }
    /*
    System.err.print("Read> ");
    try {
	System.out.println("Read: " + new DataInputStream(System.in).readLine());
    } catch(IOException e) {
	System.err.println("I/O Exception.");
    }
    */
}


public static void main(String[] args) {
    TestFrame f = new TestFrame();
    f.pack();
    f.show();
}

}



////////////////////////////////////////////////////////////////////////////

class TestFrame extends Frame implements Observer {

private Activity act;
Dialog killer;


public TestFrame() {
    setTitle("Java Killer App");
    Panel panel = new Panel();
    add("Center", panel);
    panel.add(new Button("Press to compute."));
}


public boolean action(Event e, Object what) {
    act = new ActiviTest();
    act.setObserver(this);
    killer = new Killer(this, act);
    System.err.println("Application issuing request.");
    act.request();
    System.err.println("Application request issued.");
    System.err.println("Application showing killer dialog.");
    killer.show();
    return true;
}


public void update(Observable obs, Object arg) {
    System.err.println("Application notified, end of " + arg);
    if (arg==act) {
	act = null;
	System.err.println("Application popping down monitor window.");
	killer.hide();
    }
}

}



////////////////////////////////////////////////////////////////////////////

class Killer extends Dialog {

private Activity act;
public Label message;


public Killer(Frame f, Activity a) {
    super(f, "Java App Killer", false);		// Should be "true".
    act = a;
    add("South", new Button("Abort"));
    message = new Label("Press to Terminate");
    add("Center", message);
    pack();
}


public boolean action(Event e, Object what) {
    if (!act.isActive()) {
	System.err.println("Killer dialog: not active " + act);
	// Hide this window right away; don't wait for
	// the activity to get unstuck.
	hide();
    }
    System.err.println("Killer dialog requesting abort of " + act);
    act.abort();
    return true;
}


}


////////////////////////////////////////////////////////////////////////////

/** This class provides a concept of an activity with a beginning,
  a method that carries out the activity, and an end.  The activity can be
  aborted by request, and this class generates and handles the associated
  exception.  An Activity has active and inactive states.  The class provides
  synchronization to consistently manage the state transitions.  If an Observer
  is designated, this class notifies the Observer at the point where
  the activity terminates normally or is aborted.  Activities can be queued
  for execution, and this base class provides a default background thread dedicated
  to performing Activities.
  */
abstract class Activity {

private static BasicActionThread defaultThread = new BasicActionThread();
private boolean active;
private Thread thread;
private Observer observer;


public Activity() {}

/** Establishes an Observer to be notified of termination.
 */
public void setObserver(Observer o) {
    observer = o;
}


/** Tells whether the Activity is still in progress, i.e. is not terminated.
 */
public boolean isActive() { return active; }


/** This method invokes the activity in the current thread, calling
  begin(), then act(), then end() within a "finally" clause.  This
  method returns immediately in case of an abort, allowing only end()
  and any onAbort code to run.
 */
public void perform() {
    try {
	// System.err.println("Beginning.");
	begin();
	// System.err.println("Doing act.");
	act();
	// System.err.println("Finished act.");
    } finally {
	// System.err.println("Doing end.");
	end();
    }
}


/** Override this method to do the work of the activity.
  Typically, perform() gets called because the application code called
  request().  In this case, expect act() to not run in the thread that
  called request().
  */
protected abstract void act();


/** Any action desired in the action thread in case of an Abort.
  Does nothing in the base class.
 */
protected void onAbort() {}


/** Called in the action thread to enter the active state.  A subclass
  can add actions to immediately precede entry to the active state.
  */
protected synchronized void begin() {
    if (active) throw new RuntimeException("Activity is already active.");
    thread = Thread.currentThread();
    active = true;
}


/** Called exactly once as the Activity is ending, whether
  normally or by being stopped (aborted).  Calls any Observer's update
  method, passing null as the Observable and this Activity as the
  second argument.  Possibly could be called twice if abort is called
  in the activity thread.
  */
protected synchronized void end() {
    boolean was = active;
    active = false;
    if (observer!=null && was) {
	observer.update(null, this);
    }
}


/** Call this to immediately terminate the activity.  If the activity
  has not yet terminated, stops it, causing end() to run in the action
  thread.
  */
public synchronized void abort() {
    if (active) {
	thread.stop(new AbortActivity());
	System.err.println("Stop returned to caller.");
	if (observer!=null) observer.update(null, this);
	if (thread instanceof BasicActionThread)
	    ((BasicActionThread)thread).isStuck = true;
    }
}


/** This method causes the perform() method to eventually be invoked
  in an action thread.  The base class provides a single action thread
  and no queueing of requests.  If the action thread is performing an
  activity, calls to the base class request() method block until the
  activity terminates.
  */
public void request() {
    synchronized (defaultThread) {
	if (!defaultThread.isAlive() || defaultThread.isStuck) {
	    if (defaultThread.isStuck) System.err.println("Action thread is stuck.");
	    else System.err.println("Action thread is dead.");
	    defaultThread = new BasicActionThread();
	    System.err.println("\nStarting new action thread.\n");
	    defaultThread.start();
	}
	defaultThread.putRequest(this);
    }
}


static {
    defaultThread.start();
}

}


////////////////////////////////////////////////////////////////////////////


/** A basic kind of thread that blocks requestors until it is ready
  to begin the next Activity.
  */
class BasicActionThread extends Thread {

private Activity act;

// This should be true between the time the thread has
// been asked to abort and the time it is ready to process another request.
public boolean isStuck = false;



public void run() {
    try {
	while (!isStuck) {
	    try {
		System.err.println("Action thread ready for request.");
		Activity a = getRequest();
		System.err.println("Action thread got request " + a);
		a.perform();
		System.err.println("Action thread completed request.");
		// ?? isStuck = false;
	    } catch(AbortActivity e) {
		System.err.println("Action thread caught abort.");
		isStuck = false;
	    }
		
	}
    } finally { System.err.println("Action thread dying."); }
}


public synchronized Activity getRequest() {
    while (act==null) { justWait(); }
    Activity a = act;
    act = null;
    notify();
    return a;
}


public synchronized void putRequest(Activity a) {
    while (act!=null) {  justWait(); }
    act = a;
    notify();
}


public void justWait() {
    try {
	wait();
    } catch(InterruptedException e) {
	System.err.println("Interrupted.");
    }
}

}



////////////////////////////////////////////////////////////////////////////

/** Internal Exception class.
 */
class AbortActivity extends RuntimeException {}



Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: generic FIXED IN: 1.1 INTEGRATED IN: 1.1
14-06-2004

WORK AROUND The example contains a workaround, which is to check whetherthe worker thread has died.
11-06-2004

EVALUATION It's completely bogus that java_lang_Thread_stop0() was setting tid->stillborn. That probably dates back to the time where that function was only used to kill threads, and we were trying to prevent killed threads from being restarted. That would be wrong anyhow, as ThreadDeath should be catchable and ignorable, but with Thread.stop(Throwable) it is completely bogus. tid->stillborn is instead set in ThreadRT0 when the thread fully returns. At least that is my understanding of what this bug says. I can't actually get it to compile from the report, but that seems to be what it's getting at. timothy.lindholm@Eng 1996-11-25
25-11-1996