ilya1725
ilya1725

Reputation: 4938

How to interrupt Tcl_eval

I have a small shell application that embeds Tcl 8.4 to execute some set of Tcl code. The Tcl interpreter is initialized using Tcl_CreateInterp. Everything is very simple:

  1. user types Tcl command
  2. the command gets passed to Tcl_Eval for evaluation
  3. repeat

Q: Is there any way to interrupt a very long Tcl_Eval command? I can process a 'Ctrl+C' signal, but how to interrupt Tcl_Eval?

Upvotes: 2

Views: 1855

Answers (2)

Bimo
Bimo

Reputation: 6597

Another way to solve this problem would be to fork your tcl interpreter into a separate process and driving the stdin and stdout of the tcl interpreter from your main process. Then, in the main process, you can intercept Ctrl-C and use it to kill the process of your forked tcl interpreter and to refork a new tcl interpreter.

With this solution the tcl interpreter will never lock up on your main program. However, its really annoying to add c-function extension if they need them to run in the main process because you need to use inter-process communication to invoke functions.

I have a similar problem I was trying to solve, where I start the TCL interpret in a worker thread. Except, there's really no clean way to kill a worker thread because it leave allocated memory in an uncleaned up state, leading to memory leaks. So really the only way to fix this problem is to use a process model instead or to just keep quitting and restarting your application. Given the amount of time it takes to go with process solution I just decided to stick with threads and fix the problem one of these days to get the ctrl-c to work in a separate process, rather than leaking memory everytime i kill a thread. and potential destabilizing and crashing my program.

UPDATE:

My conclusion is that Tcl Arrays are not normal variables and you can't use Tcl_GetVar2Ex to read "tmp" variable after Eval and tmp doesn't show up under "info globals". So to get around this I decided to directly call the Tcl-Library API rather than Eval shortcut to build a dictionary object to return.

Tcl_Obj* dict_obj = Tcl_NewDictObj ();
if (!dict_obj) {
   return TCL_ERROR;
}

Tcl_DictObjPut (
    interp, 
    dict_obj,
    Tcl_NewStringObj ("key1",     -1),
    Tcl_NewStringObj ("value1",   -1)
);

Tcl_DictObjPut (
    interp, 
    dict_obj,
    Tcl_NewStringObj ("key2",     -1),
    Tcl_NewStringObj ("value2",   -1)
);

Tcl_SetObjResult(interp, dict_obj);

Upvotes: 0

Donal Fellows
Donal Fellows

Reputation: 137587

Tcl doesn't set signal handlers by default (except for SIGPIPE, which you probably don't care about at all) so you need to use an extension to the language to get the functionality you desire. By far the simplest way to do this is to use the signal command from the TclX package (or from the Expect package, but that's rather more intrusive in other ways):

package require Tclx

# Make Ctrl+C generate an error
signal error SIGINT

Just evaluate a script containing those in the same interpreter before using Tcl_Eval() to start running the code you want to be able to interrupt; a Ctrl+C will cause that Tcl_Eval() to return TCL_ERROR. (There are other things you can do — such as running an arbitrary Tcl command which can trap back into your C code — but that's the simplest.)

If you're on Windows, the TWAPI package can do something equivalent apparently.


Here's a demonstration of it in action in an interactive session!

bash$ tclsh8.6
% package require Tclx
8.4
% signal error SIGINT
% puts [list [catch {
    while 1 {incr i}
} a b] $a $b $errorInfo $errorCode]
^C1 {can't read "i": no such variableSIGINT signal received} {-code 1 -level 0 -errorstack {INNER push1} -errorcode {POSIX SIG SIGINT} -errorinfo {can't read "i": no such variableSIGINT signal received
    while executing
"incr i"} -errorline 2} {can't read "i": no such variableSIGINT signal received
    while executing
"incr i"} {POSIX SIG SIGINT}
% 

Note also that this can leave the interpreter in a somewhat-odd state; the error message is a little bit odd (and in fact that would be a bug, but I'm not sure what in). It's probably more elegant to do it like this (in 8.6):

% try {
    while 1 {incr i}
} trap {POSIX SIG SIGINT} -> {
    puts "interrupt"
}
^Cinterrupt
% 

Upvotes: 1

Related Questions