TIP:            285
Title:          Script Cancellation with [interp cancel] and Tcl_CancelEval
Version:        $Revision: 1.11 $
Author:         Joe Mistachkin <joe@mistachkin.com>
Author:         Dawson Cowals <dawson@dawsoncowals.com>
State:          Final
Type:           Project
Vote:           Done
Created:        04-Jun-2006
Post-History:   
Keywords:       eval,cancel,unwind,terminate,runaway,async,thread,safe
Tcl-Version:    8.6

~ Abstract

This TIP introduces the ability to quickly and safely cancel a script within a
specified interpreter from any thread in the process.

~ Key Use-Case Scenario

When using Tcl inside a context such as a web-browser (e.g. as a plugin), it
is often necessary for the execution of a particular script to be terminated
cleanly without waiting for the script to get to a point where it is able to
respond to events. For example, if the user encounters a page that contains a
script that starts a long-running execution but then decides to navigate away
from the page, it's important to stop the script as soon as possible.

But we do not want to stop the script by means of just terminating the thread,
as this can result in various resources being still allocated, as browsers are
long-running applications. Some of the problems (e.g. memory waste) can be
worked around by running the script in a separate process, but there are other
resources that aren't cleaned up that way, such as locked files or shared
memory blocks, and it is always better to give the Tcl interpreter itself an
opportunity to clean up after itself. Furthermore, there are other possible
applications (such as using Tcl to implement COM objects on Windows) where the
separate-process method will not work so well.

Instead, what is needed is a way to programmatically make a script stop its
execution even when that script is otherwise determined to continue. This is
different from a resource limit in that the cancellation is not caused by the
exceeding of a predetermined value, but rather by some external event that is
possibly even not processed initially by Tcl at all.

~ Technical Rationale

Currently, once the evaluation of a script has been initiated it will do one
of the following:

 * run to completion,

 * run until it encounters an uncaught error,

 * run until it exceeds a pre-determined limit as specified in [143], or

 * run indefinitely.

In each of the cases above, neither the host application nor an interactive
user have any recourse to terminate the script prior to it running its course.

There are many situations for which it is absolutely necessary to be able to
cancel a running script without its cooperation and without setting an
arbitrary limit on the amount of time it can run ([143]). This is especially
true in a multi-threaded application embedding environment, or where a user
interface is present.

 1. In the case where the completion time for a script is unknown,
    non-existent, or non-deterministic a user may want or need to terminate
    the script prematurely.

 2. When evaluating an untrusted - or marginally trusted - script in either a
    safe or standard interpreter, there is a risk that the script might never
    terminate. In such a situation it is not reasonable to forcibly terminate
    the thread or the entire process.

 > 1. Forcibly terminating a thread prevents Tcl and Tk from cleaning up their
      thread-specific memory and resources.

 > 2. The host application may suffer similar memory and resource leaks as
      well as other serious side-effects that may corrupt data, prevent other
      threads from properly synchronizing, or leave the process in an unknown
      and unrecoverable state.

 > 3. For an interactive host application valuable work may be lost without
      providing an opportunity to save pending modifications. Even in the
      absence of modifications the host application might have been holding
      locks that left unreleased would prevent other processes and users from
      using important resources.

The basic building blocks needed for any scripting language to seamlessly
integrate with an enterprise-ready host application are:

 * Engine Initialization

 * Evaluation

 * Extensibility

 * Cancellation

 * Engine Finalization

Tcl now provides full support for all of the above except script cancellation.
[143] allows for scripts to be prematurely terminated after reaching resource
limits that were pre-arranged by the host application. However, this only
handles terminating scripts based on a narrow set of deterministic criteria.
Full support would require the ability to immediately and unconditionally
terminate the evaluation of a script without adversely affecting the execution
environment of the host application. In addition the following issues must be
addressed:

 * Scripts being evaluated in nested slave interpreters.

 * Interaction with third-party extensions.

 * Safely usable by arbitrary threads.

Several other mainstream scripting engines (e.g., JavaScript, Microsoft Active
Scripting, etc.) currently provide this capability to cancel the evaluation of
a script. This TIP proposes an implementation that would bring this necessary
aspect of application integration to Tcl. This must be implemented in the
core, because access to and modification of internal Tcl functions and data
structures is required.

~ Specification

A new '''interp cancel''' script command will be added, as follows:

 > '''interp cancel''' ?'''-unwind'''? ?'''--'''? ?''path''? ?''result''?

This command cancels the script being evaluated in the interpreter.

~~ Arguments

 > '''-unwind'''

This argument is optional. Without ''-unwind'', the evaluation stack for the
interpreter is unwound until an enclosing '''catch''' command is found or
there are no further invocations of the interpreter left on the call-stack.
With ''-unwind'', the evaluation stack for the interpreter is unwound without
regard to any intervening '''catch''' command until there are no further
invocations of the interpreter left on the call-stack.

 > '''--'''

This argument is optional, and marks the end of options. The argument
following this one will be treated as being the ''path'' argument even if it
starts with a "-".

 > ''path''

This argument is optional. If not supplied, the current interpreter is
assumed; otherwise, the interpreter specified by ''path'' is used.

 > ''result''

This argument is optional. If not supplied, a default error message is left in
the result of the interpreter; otherwise, the result specified by ''result''
is used.

~~ Behavior

When a script is canceled, the following occur:

 * The ''CANCELED'' flag, and possibly the ''TCL_CANCEL_UNWIND'' flag, are set
   in the interpreter to mark the evaluation in progress as having been
   canceled.

 * The currently executing command/script in the interpreter is made to return
   with code ''TCL_ERROR''. (This is superior to using a novel return code, as
   third-party extensions are usually far better at handling error cases!)

 * The '''catch''' command will only catch errors if the interpreter
   containing it does not have the ''TCL_CANCEL_UNWIND'' flag set.

 * Additional trips through the internal loops of the '''after''',
   '''vwait''', '''update''' and '''tkwait''' commands will not proceed with
   the ''CANCELED'' or ''TCL_CANCEL_UNWIND'' flags set.  (Extensions can find
   this information out by using ''Tcl_Canceled''; see below.)

 * Once the execution unwinds out of the interpreter, so that no further
   invocations of the interpreter are left on the call-stack, both of the
   script cancellation related flags are reset.

 * If there are no invocations of the interpreter on the call-stack when
   ''Tcl_CancelEval'' or '''interp cancel''' are called, then the next script to be
   evaluated will be preemptively canceled.

~~ Notes

 * Going forward, all "long running commands" in the Tcl/Tk core should make
   every effort to comply with the script cancellation functionality by
   calling ''Tcl_Canceled'' at times when it is appropriate to abort
   processing.

 * Extensions can optionally check if they should abort processing by calling
   ''Tcl_Canceled''.

~ C API

~~ Constants

 TCL_CANCEL_UNWIND: New eval-flag bit that applies to ''Tcl_CancelEval'' and
   ''Tcl_Canceled''.

 > When used in ''flags'' for ''Tcl_CancelEval'', the evaluation stack for
   ''interp'' is unwound without regard to any intervening '''catch''' command
   until there are no further invocations of ''interp'' left on the call
   stack. When not set, the evaluation stack for the interpreter is unwound
   until an enclosing '''catch''' command is found or there are no further
   invocations of ''interp'' left on the call-stack.

 > When used in ''flags'' for ''Tcl_Canceled'', checks if the script being
   evaluated has been canceled using the ''TCL_CANCEL_UNWIND'' flag (i.e., the
   evaluation stack for ''interp'' is being completely unwound).

 TCL_LEAVE_ERR_MSG: Existing variable-related flag bit that applies to
   ''Tcl_Canceled'' only.

 > When used in ''flags'' for ''Tcl_Canceled'', an error message will be left
   in the result of ''interp'' if the script being evaluated has been
   canceled.

~~ Functions

 > int '''Tcl_CancelEval'''(Tcl_Interp *''interp'', Tcl_Obj *''resultObjPtr'',
   ClientData ''clientData'', int ''flags'')

The '''Tcl_CancelEval''' function initiates cancellation of the script being
evaluated in ''interp''. It returns a standard Tcl result. If ''resultObjPtr''
is NULL, a default error message will be left in the result of ''interp''
indicating that the script was canceled or unwound. If ''resultObjPtr'' is not
NULL, it will be used verbatim to supply the result of ''interp''. The
''clientData'' is reserved for future use and must be zero. This function may
be called from any thread in the process, regardless of which thread created
''interp''.

 > int '''Tcl_Canceled'''(Tcl_Interp *''interp'', int ''flags'')

The '''Tcl_Canceled''' function checks whether the script being evaluated in
''interp'' has been canceled.  Returns a standard Tcl result (i.e.,
''TCL_ERROR'' if the script being evaluated has been canceled). This function
should only be called from the thread which created ''interp''; otherwise, its
behavior is undefined.

~ Reference Implementation

A reference implementation of this TIP is available
[http://sf.net/tracker/?func=detail&aid=1499394&group_id=10894&atid=310894].

~ Copyright

This document has been placed in the public domain.

