Set Interrupted API


About:

Jacl includes a feature that makes it possible to interrupt execution of an interpreter. When interrupted, a Jacl interp will cleanup each frame on the Tcl call stack and dispose of the interrupted interpreter. Typically, a developer would invoke the Interp.setInterrupted() method from a thread other than the one processing events. It is also possible to invoke the setInterrupted() method directly from Tcl code using the java package. The examples that follow show both usages.

Assume that the following Tcl commands have been evaluated:


package require java

proc p1 {} {
    p2
}

proc p2 {} {
    p3
}

proc p3 {} {
    [java::getinterp] setInterrupted
}

Just before the setInterrupted method is invoked in the proc p3, the Tcl stack would look like this:


Frame: p3
Frame: p2
Frame: p1
Global

When invoked, the setInterrupted method will set a flag in the Interp object to indicate that execution should be interrupted. This flag is then checked at the end of the command evaluation operation and a TclInterruptedException is raised. This TclInterruptedException is a Java exception that extends the unchecked RuntimeException class.


java.lang.RuntimeException
    -> tcl.lang.TclInterruptedException

When the TclInterruptedException is raised, stack frames for the p3, p2, and p1 commands will be cleaned up in order. The TclInterruptedException should then be caught in the outermost event processing loop. This is the main loop that is processing Tcl events using Notifier.doOneEvent(). See the EventLoop documentation for information about basic event loop processing. The EventLoop documentation describes a basic event loop, a simplified version might look like:


import tcl.lang.*;

public
class EventProcessingThread extends Thread {
    Interp interp;

    public void run() {
        // Allocate Interp
    
        while (true) {
            interp.getNotifier().doOneEvent(TCL.ALL_EVENTS);
        }
    }
}

Unfortunately, the basic event loop does not properly support handling of a TclInterruptedException. The basic event loop does not work properly when a TclInterruptedException is raised in a thread that is processing events for multiple interpreters. When one interpreter is interrupted, events for other interpreter(s) in the thread should continue to be processed. Because the basic event loop does not catch a TclInterruptedException, the default Java behavior is to terminate the thread. This problem can be fixed by placing a try around the event processing call and using the Notifier.hasActiveInterps() API to detect when all interps in the thread have been disposed of. The following example shows an event processing loop that supports interruption.


import tcl.lang.*;

public
class EventProcessingThread extends Thread {
    Interp interp;

    public void run() {
        // Allocate Interp

        Notifier notifier = interp.getNotifier();

        while (notifier.hasActiveInterps()) {
            try {
                notifier.doOneEvent(TCL.ALL_EVENTS);
            } catch (TclInterruptedException tie) {
                tie.disposeInterruptedInterp();
            }
        }
    }
}

A developer need not write out all the logic described above as the Notifier class provides a utility method named processTclEvents. This utility method implements the while loop functionality seen above, so a developer need only write the following:


import tcl.lang.*;

public
class EventProcessingThread extends Thread {
    Interp interp;

    public void run() {
        // Allocate Interp

        Notifier.processTclEvents(interp.getNotifier());
    }
}

Interrupting From Another Thread:

The interpreter interrupt feature is particularly useful when implementing services that may need to be terminated when a condition is met. For example, one might want to evaluate a script but define a timer that will interrupt the interpreter if the script takes longer than 60 seconds to run. The timer could be implemented as a separate Java Thread that would go to sleep and then invoke the setInterrupted API.

It is perfectly legal to invoke the setInterrupted API from a thread other than the one processing events. Thread safety issues are handled inside the setInterrupted method, so no explicit synchronization is needed in user code. Also note that the setInterrupted method will do nothing and return if the interp in question was already deleted or interrupted, so the caller need not worry about checking these conditions before invoking this method from a timeout thread.

Catching TclInterruptedException:

The only place user code should catch a TclInterruptedException is after invoking doOneEvent in the main event processing loop. A TclInterruptedException can't be caught using Tcl's catch command. It is also not possible to catch a TclInterruptedException using the java::try command. If a TclInterruptedException could be caught via java::try then it could be ignored, and that could result in unpredictable behavior. Even so, a finally block passed to a java::try command will still be executed when a TclInterruptedException is raised. The following example demonstrates this behavior:

package require java

proc p1 {} {
    [java::getinterp] setInterrupt
}

proc p2 {} {
    java::try {
        p1
    } catch {Exception e} {
        # This block will not be run since it is
        # not possible to catch a TclInterruptedException
        # using java::try

        puts "catch run in p2"
    } finally {
        # This finally block will be run

        puts "finally run in p2"
    }
}

When the p2 command is invoked it will invoke p1 and then the interpreter will be interrupted. When the stack frame for the p2 command is cleaned up the finally block is evaluated and "finally run in p2" is printed to the console. Note that the catch block is not run even though java.lang.Exception is a superclass of tcl.lang.TclInterruptedException.

Users should take care to keep the finally block for a java::try command as simple as possible and use it only to cleanup resources before disposing of the stack frame. The finally block should not invoke commands like update, vwait, or other potentially slow operations since this could delay interpreter interruption.

Users should take care not to accidentally catch and ignore a TclInterruptedException in Java code. Improper exception handling in Java code is a common problem. Poorly written code like the following would break the interpreter interrupt feature in Jacl.

public
class BrokenCmd implements Command {

    public void cmdProc(Interp interp, TclObject[] objv)
	throws TclException
    {
        try {
            interp.eval("cmd");
        } catch (Exception e) {
            // Ignore exception
        }
    }

}

In most cases, the caller would only want to catch exceptions that are a subclass of TclException. Using TclException instead of Exception is a better coding practice and will not break the interrupt feature. The same logic applies to catching Throwable, and RuntimeException.

Copyright © 1997-1998 Sun Microsystems, Inc.