Subject: Re: How to stop running interpreters in other threads (AsyncHandlers) - DN [1]


Paul Duffin <pduffin@mailserver.hursley.ibm.com> - 21 Oct 1999 - comp.lang.tcl

 Byron Servies wrote:
 >
 > Hi there!
 >
 > I have a multi-threaded application with embedded Tcl interpreters.  One
 > of the options is to create a Tcl interpreter in a new thread and
 > evaluate a file.  This is for long-running scripts, or ones that should
 > never exit (like the TclHTTP server).  Everything seems to work fine
 > until I clean up these threads.
 >
 > My problem is that when I go to clean up these long-running threads,
 > Tcl_DeleteInterp() fails because the interpreter is still evaluating a
 > file.  It fails badly, too, because it calls 'panic', which eventually
 > calls Tcl_Exit and 'exit'.
 >
 > How do I delete an interpreter which has an active eval?
 >

 With difficulty.

 If the thread is processing events through its event loop you could send
 the thread a thread exit command.

 If it is inside some C code then you probably cannot.

 If it is executing Tcl code then there is a possible solution built
 around the Tcl Async mechanism which allows you to execute code
 *between* Tcl commands / events.

 The big problem with the Tcl Async mechanism is that it is global and
 not thread specific so you cannot aim your request at a specific
 thread / interpreter which would make things easier. However, we can
 still probably do something.

 What you need to do in C code is as follows.

     Create an async handler which contains either the id of the
     thread that you want to kill, or the address of the interpreter
     that you want to kill, and also the address where a status
     result is stored.

 shoot:
     Set the status to a special NOT_TRIGGERED value.
     Mark the async handler as ready.

 sleep1:
     Go to sleep for a while.
     If status has not changed then
         goto sleep1

     if status is NOT_CORRECT_TARGET then
         goto shoot

     cleanup and exit.

 If you have multiple threads that you want to delete at once then add
 all the identifiers into the AsyncHandler and do a search when it is
 invoked as that will be much more efficient.

 If you want to kill all threads then it is much easier because any time
 it gets invoked it can kill the thread.

 It would probably be wise to add a counter in to exit the loops if it
 doesn't work for some reason, e.g. you are trying to kill a thread
 which is not executing Tcl code, or for some reasons due to the
 scheduler another thread always handles the async handlers.

 When the async handler is invoked you need to check that the
 thread / interpreter which is executing you is the correct one, and that
 the interpreter is not NULL. If it is not then set the status to
 NOT_CORRECT_TARGET and return. Otherwise, you need to blitz the
 interpreter that you are running in. You can do a variety of things.

 If the target you want to kill is a thread then you could call
 Tcl_ExitThread which should kill the thread / delete the interpreter /
 call (thread) exit handlers etc.

 If the target is an interpreter then the following should work unless
 there are some catches in the stack, or it is a wish application.

     Set the interpreter result to an error message.
     Set the error code to something specific.
     Return a TCL_ERROR code.

 If the above will not work then you can be really nasty and do the
 following.

     Delete every namespace (apart from global).
     Delete all global variables and procedures.

 If the above does not cause a crash then it will stop the interpreter.

 Proposal: Improved Async Handlers
 =================================

 This would be much easier if each interpreter / thread had its own queue
 of async handlers which other interpreters / threads could add to. This
 could be the basis of a general inter-interpreter / inter-thread
 communication channel.

 Extensions to Async mechanism to allow the handler to be invoked in
 a specific interpreter / thread.

     Create a AsyncHandler queue structure which contains
     mutex (optional).
     pointer to head.
     pointer to tail.
     ready flag.
     invoking flag.
     Basically this is the same information as is used by the global
     async handler queue.

     Move the global queue of AsyncHandlers into its own list.

     Add a thread id and an interpreter pointer to the AsyncHandler
     structure. These are initialised to NULL, unless the handler is
     targeted at a specific interpreter / thread in which case the
     relevant structure member is set and the other is initialised to
     NULL.

     Also add a pointer to the AsyncHandler queue to the AsyncHandler
     structure.

     Add a queue of AsyncHandlers to the the Interp structure.

     Associate with each thread a structure of information (Tcl_Thread)
     containing thread id, next / prev pointers for chaining together and
     a queue of AsyncHandlers. These Tcl_Thread structures should be
     chained together in a global list so threads can add AsyncHandlers
     to each others the queues.

     Define two new functions
     Tcl_AsyncHandler
     Tcl_InterpAsyncCreate (Tcl_Interp *interp,
                    Tcl_AsyncProc *proc,
                    ClientData clientData);

     and
     Tcl_AsyncHandler
     Tcl_ThreadAsyncCreate (Tcl_ThreadId threadID,
                    Tcl_AsyncProc *proc,
                    ClientData clientData);

     These functions create a Tcl_AsyncHandler just like Tcl_AsyncCreate
     but they place it on the interpreter queue, or the thread queue
     respectively.

     If the threadID is NULL then the current thread is assumed.

     Tcl_AsyncMark is modified to use the AsyncHandler queue pointer to
     access the queue mutex and the ready flag.

     A new function is needed.
         int Tcl_AsyncReadyEx (Tcl_Interp *interp);
     This returns the logical or of the global ready flag, the thread
     ready flag, and the intepreter ready flag (if an interpreter is
     provided).

     Tcl_AsyncInvoke needs to be modified to only execute those async
     handlers which match the current thread, current interpreter (if
     any) and the global ones.

 Once this is in place stopping other threads executing is simply a
 matter of queueing an AsyncHandler on that thread's queue which calls
 Tcl_ExitThread when invoked.

 Stopping a running interpreter simply involves queueing an AsyncHandler
 on that interpreter's queue which blitzes the interpreter state.

 Sending an event to another thread can be done by queuing an
 AsyncHandler on the thread's queue which when invoked calls
 Tcl_QueueEvent. (Already possible with the special
 Tcl_ThreadQueueEvent).

 An inter-thread pipe can be easily built which sends an event to the
 reader when something is written. This could probably be built on top
 of a memory channel, or the memory channel could be extended to support
 this functionality.

 --
 Paul Duffin
 DT/6000 Development    Email: pduffin@hursley.ibm.com
 IBM UK Laboratories Ltd., Hursley Park nr. Winchester
 Internal: 7-246880    International: +44 1962-816880

Last modified
1999-11-17

(195.108.246.50)

Note: you are looking at
the snapshot of an old wiki
- much of this information
is likely to be very outdated