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
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
