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()); } }
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.
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.