Support threads-enabled Tcl installations.

This commit is contained in:
Martin v. Löwis 2002-12-12 17:07:58 +00:00
parent d9a6ad3beb
commit b5bfb9f38c
1 changed files with 526 additions and 119 deletions

View File

@ -9,17 +9,13 @@ Copyright (C) 1994 Steen Lumholt.
/* TCL/TK VERSION INFO:
Only Tcl/Tk 8.0 and later are supported. Older versions are not
supported. (Use Python 1.5.2 if you cannot upgrade your Tcl/Tk
Only Tcl/Tk 8.2 and later are supported. Older versions are not
supported. (Use Python 2.2 if you cannot upgrade your Tcl/Tk
libraries.)
*/
/* XXX Further speed-up ideas, involving Tcl 8.0 features:
- In Tcl_Call(), create Tcl objects from the arguments, possibly using
intelligent mappings between Python objects and Tcl objects (e.g. ints,
floats and Tcl window pointers could be handled specially).
- Register a new Tcl type, "Python callable", which can be called more
efficiently and passed to Tcl_EvalObj() directly (if this is possible).
@ -48,6 +44,11 @@ Copyright (C) 1994 Steen Lumholt.
anymore, this should go. */
#define USE_COMPAT_CONST
/* If Tcl is compiled for threads, we must also define TCL_THREAD. We define
it always; if Tcl is not threaded, the thread functions in
Tcl are empty. */
#define TCL_THREADS
#ifdef TK_FRAMEWORK
#include <Tcl/tcl.h>
#include <Tk/tk.h>
@ -111,9 +112,8 @@ Copyright (C) 1994 Steen Lumholt.
#ifdef WITH_THREAD
/* The threading situation is complicated. Tcl is not thread-safe, except for
Tcl 8.1, which will probably remain in alpha status for another 6 months
(and the README says that Tk will probably remain thread-unsafe forever).
/* The threading situation is complicated. Tcl is not thread-safe, except
when configured with --enable-threads.
So we need to use a lock around all uses of Tcl. Previously, the Python
interpreter lock was used for this. However, this causes problems when
other Python threads need to run while Tcl is blocked waiting for events.
@ -148,31 +148,58 @@ Copyright (C) 1994 Steen Lumholt.
These locks expand to several statements and brackets; they should not be
used in branches of if statements and the like.
If Tcl is threaded, this approach won't work anymore. The Tcl interpreter is
only valid in the thread that created it, and all Tk activity must happen in this
thread, also. That means that the mainloop must be invoked in the thread that
created the interpreter. Invoking commands from other threads is possible;
_tkinter will queue an event for the interpreter thread, which will then
execute the command and pass back the result. If the main thread is not in the
mainloop, and invoking commands causes an exception; if the main loop is running
but not processing events, the command invocation will block.
In addition, for a threaded Tcl, a single global tcl_tstate won't be sufficient
anymore, since multiple Tcl interpreters may simultaneously dispatch in different
threads. So we use the Tcl TLS API.
*/
static PyThread_type_lock tcl_lock = 0;
#ifdef TCL_THREADS
static Tcl_ThreadDataKey state_key;
typedef PyThreadState *ThreadSpecificData;
#define tcl_tstate (*(PyThreadState**)Tcl_GetThreadData(&state_key, sizeof(PyThreadState*)))
#else
static PyThreadState *tcl_tstate = NULL;
#endif
#define ENTER_TCL \
{ PyThreadState *tstate = PyThreadState_Get(); Py_BEGIN_ALLOW_THREADS \
PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate;
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate;
#define LEAVE_TCL \
tcl_tstate = NULL; PyThread_release_lock(tcl_lock); Py_END_ALLOW_THREADS}
tcl_tstate = NULL; if(tcl_lock)PyThread_release_lock(tcl_lock); Py_END_ALLOW_THREADS}
#define ENTER_OVERLAP \
Py_END_ALLOW_THREADS
#define LEAVE_OVERLAP_TCL \
tcl_tstate = NULL; PyThread_release_lock(tcl_lock); }
tcl_tstate = NULL; if(tcl_lock)PyThread_release_lock(tcl_lock); }
#define ENTER_PYTHON \
{ PyThreadState *tstate = tcl_tstate; tcl_tstate = NULL; \
PyThread_release_lock(tcl_lock); PyEval_RestoreThread((tstate)); }
if(tcl_lock)PyThread_release_lock(tcl_lock); PyEval_RestoreThread((tstate)); }
#define LEAVE_PYTHON \
{ PyThreadState *tstate = PyEval_SaveThread(); \
PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate; }
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate; }
#define CHECK_TCL_APPARTMENT \
if (((TkappObject *)self)->threaded && \
((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { \
PyErr_SetString(PyExc_RuntimeError, "Calling Tcl from different appartment"); \
return 0; \
}
#else
@ -182,6 +209,7 @@ static PyThreadState *tcl_tstate = NULL;
#define LEAVE_OVERLAP_TCL
#define ENTER_PYTHON
#define LEAVE_PYTHON
#define CHECK_TCL_APPARTMENT
#endif
@ -220,6 +248,9 @@ typedef struct {
PyObject_HEAD
Tcl_Interp *interp;
int wantobjects;
int threaded; /* True if tcl_platform[threaded] */
Tcl_ThreadId thread_id;
int dispatching;
/* We cannot include tclInt.h, as this is internal.
So we cache interesting types here. */
Tcl_ObjType *BooleanType;
@ -279,6 +310,7 @@ Sleep(int milli)
#endif /* MS_WINDOWS */
#endif /* WITH_THREAD */
static char *
AsString(PyObject *value, PyObject *tmp)
@ -541,6 +573,23 @@ Tkapp_New(char *screenName, char *baseName, char *className,
v->interp = Tcl_CreateInterp();
v->wantobjects = wantobjects;
v->threaded = Tcl_GetVar2Ex(v->interp, "tcl_platform", "threaded",
TCL_GLOBAL_ONLY) != NULL;
v->thread_id = Tcl_GetCurrentThread();
v->dispatching = 0;
#ifndef TCL_THREADS
if (v->threaded) {
PyErr_SetString(PyExc_RuntimeError, "Tcl is threaded but _tkinter is not");
Py_DECREF(v);
return 0;
}
#endif
if (v->threaded && tcl_lock) {
/* If Tcl is threaded, we don't need the lock. */
PyThread_free_lock(tcl_lock);
tcl_lock = NULL;
}
v->BooleanType = Tcl_GetObjType("boolean");
v->ByteArrayType = Tcl_GetObjType("bytearray");
@ -591,6 +640,19 @@ Tkapp_New(char *screenName, char *baseName, char *className,
}
static void
Tkapp_ThreadSend(TkappObject *self, Tcl_Event *ev,
Tcl_Condition *cond, Tcl_Mutex *mutex)
{
Py_BEGIN_ALLOW_THREADS;
Tcl_MutexLock(mutex);
Tcl_ThreadQueueEvent(self->thread_id, ev, TCL_QUEUE_TAIL);
Tcl_ThreadAlert(self->thread_id);
Tcl_ConditionWait(cond, mutex, NULL);
Tcl_MutexUnlock(mutex);
Py_END_ALLOW_THREADS
}
/** Tcl Eval **/
@ -848,7 +910,7 @@ FromObj(PyObject* tkapp, Tcl_Obj *value)
}
if (value->typePtr == app->ProcBodyType) {
/* fall through: return tcl object. */
/* fall through: return tcl object. */
}
if (value->typePtr == app->StringType) {
@ -883,19 +945,38 @@ FromObj(PyObject* tkapp, Tcl_Obj *value)
return newPyTclObject(value);
}
static PyObject *
Tkapp_Call(PyObject *self, PyObject *args)
/* This mutex synchronizes inter-thread command calls. */
TCL_DECLARE_MUTEX(call_mutex)
typedef struct Tkapp_CallEvent {
Tcl_Event ev; /* Must be first */
TkappObject *self;
PyObject *args;
int flags;
PyObject **res;
PyObject **exc_type, **exc_value, **exc_tb;
Tcl_Condition done;
} Tkapp_CallEvent;
void
Tkapp_CallDeallocArgs(Tcl_Obj** objv, Tcl_Obj** objStore, int objc)
{
Tcl_Obj *objStore[ARGSZ];
Tcl_Obj **objv = NULL;
int objc = 0, i;
PyObject *res = NULL;
Tcl_Interp *interp = Tkapp_Interp(self);
/* Could add TCL_EVAL_GLOBAL if wrapped by GlobalCall... */
int flags = TCL_EVAL_DIRECT;
int i;
for (i = 0; i < objc; i++)
Tcl_DecrRefCount(objv[i]);
if (objv != objStore)
ckfree(FREECAST objv);
}
objv = objStore;
/* Convert Python objects to Tcl objects. This must happen in the
interpreter thread, which may or may not be the calling thread. */
static Tcl_Obj**
Tkapp_CallArgs(PyObject *args, Tcl_Obj** objStore, int *pobjc)
{
Tcl_Obj **objv = objStore;
int objc, i;
if (args == NULL)
/* do nothing */;
@ -934,24 +1015,29 @@ Tkapp_Call(PyObject *self, PyObject *args)
Tcl_IncrRefCount(objv[i]);
}
}
*pobjc = objc;
return objv;
finally:
Tkapp_CallDeallocArgs(objv, objStore, objc);
return NULL;
}
ENTER_TCL
/* Convert the results of a command call into a Python objects. */
i = Tcl_EvalObjv(interp, objc, objv, flags);
ENTER_OVERLAP
if (i == TCL_ERROR)
Tkinter_Error(self);
else if(((TkappObject*)self)->wantobjects) {
Tcl_Obj *value = Tcl_GetObjResult(interp);
static PyObject*
Tkapp_CallResult(TkappObject *self)
{
PyObject *res = NULL;
if(self->wantobjects) {
Tcl_Obj *value = Tcl_GetObjResult(self->interp);
/* Not sure whether the IncrRef is necessary, but something
may overwrite the interpreter result while we are
converting it. */
Tcl_IncrRefCount(value);
res = FromObj(self, value);
res = FromObj((PyObject*)self, value);
Tcl_DecrRefCount(value);
} else {
const char *s = Tcl_GetStringResult(interp);
const char *s = Tcl_GetStringResult(self->interp);
const char *p = s;
/* If the result contains any bytes with the top bit set,
@ -970,8 +1056,8 @@ Tkapp_Call(PyObject *self, PyObject *args)
p = strchr(p, '\0');
res = PyUnicode_DecodeUTF8(s, (int)(p-s), "strict");
if (res == NULL) {
PyErr_Clear();
res = PyString_FromStringAndSize(s, (int)(p-s));
PyErr_Clear();
res = PyString_FromStringAndSize(s, (int)(p-s));
}
}
#else
@ -979,14 +1065,124 @@ Tkapp_Call(PyObject *self, PyObject *args)
res = PyString_FromStringAndSize(s, (int)(p-s));
#endif
}
return res;
}
LEAVE_OVERLAP_TCL
/* Tkapp_CallProc is the event procedure that is executed in the context of
the Tcl interpreter thread. Initially, it holds the Tcl lock, and doesn't
hold the Python lock. */
finally:
for (i = 0; i < objc; i++)
Tcl_DecrRefCount(objv[i]);
if (objv != objStore)
ckfree(FREECAST objv);
static int
Tkapp_CallProc(Tkapp_CallEvent *e, int flags)
{
Tcl_Obj *objStore[ARGSZ];
Tcl_Obj **objv;
int objc;
int i;
ENTER_PYTHON
objv = Tkapp_CallArgs(e->args, objStore, &objc);
if (!objv) {
PyErr_Fetch(e->exc_type, e->exc_value, e->exc_tb);
*(e->res) = NULL;
}
LEAVE_PYTHON
if (!objv)
goto done;
i = Tcl_EvalObjv(e->self->interp, objc, objv, e->flags);
ENTER_PYTHON
if (i == TCL_ERROR) {
*(e->res) = NULL;
*(e->exc_type) = NULL;
*(e->exc_tb) = NULL;
*(e->exc_value) = PyObject_CallFunction(
Tkinter_TclError, "s",
Tcl_GetStringResult(e->self->interp));
}
else {
*(e->res) = Tkapp_CallResult(e->self);
}
LEAVE_PYTHON
done:
/* Wake up calling thread. */
Tcl_MutexLock(&call_mutex);
Tcl_ConditionNotify(&e->done);
Tcl_MutexUnlock(&call_mutex);
return 1;
}
/* This is the main entry point for calling a Tcl command.
It supports three cases, with regard to threading:
1. Tcl is not threaded: Must have the Tcl lock, then can invoke command in
the context of the calling thread.
2. Tcl is threaded, caller of the command is in the interpreter thread:
Execute the command in the calling thread. Since the Tcl lock will
not be used, we can merge that with case 1.
3. Tcl is threaded, caller is in a different thread: Must queue an event to
the interpreter thread. Allocation of Tcl objects needs to occur in the
interpreter thread, so we ship the PyObject* args to the target thread,
and perform processing there. */
static PyObject *
Tkapp_Call(PyObject *_self, PyObject *args)
{
Tcl_Obj *objStore[ARGSZ];
Tcl_Obj **objv = NULL;
int objc, i;
PyObject *res = NULL;
TkappObject *self = (TkappObject*)_self;
/* Could add TCL_EVAL_GLOBAL if wrapped by GlobalCall... */
int flags = TCL_EVAL_DIRECT;
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
/* We cannot call the command directly. Instead, we must
marshal the parameters to the interpreter thread. */
Tkapp_CallEvent *ev;
PyObject *exc_type, *exc_value, *exc_tb;
if (!self->dispatching) {
PyErr_SetString(PyExc_RuntimeError,
"main thread is not in main loop");
return NULL;
}
ev = (Tkapp_CallEvent*)ckalloc(sizeof(Tkapp_CallEvent));
ev->ev.proc = (Tcl_EventProc*)Tkapp_CallProc;
ev->self = self;
ev->args = args;
ev->res = &res;
ev->exc_type = &exc_type;
ev->exc_value = &exc_value;
ev->exc_tb = &exc_tb;
ev->done = (Tcl_Condition)0;
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->done, &call_mutex);
if (res == NULL) {
if (exc_type)
PyErr_Restore(exc_type, exc_value, exc_tb);
else
PyErr_SetObject(Tkinter_TclError, exc_value);
}
}
else {
objv = Tkapp_CallArgs(args, objStore, &objc);
if (!objv)
return NULL;
ENTER_TCL
i = Tcl_EvalObjv(self->interp, objc, objv, flags);
ENTER_OVERLAP
if (i == TCL_ERROR)
Tkinter_Error(_self);
else
res = Tkapp_CallResult(self);
LEAVE_OVERLAP_TCL
Tkapp_CallDeallocArgs(objv, objStore, objc);
}
return res;
}
@ -1003,6 +1199,8 @@ Tkapp_GlobalCall(PyObject *self, PyObject *args)
char *cmd;
PyObject *res = NULL;
CHECK_TCL_APPARTMENT;
cmd = Merge(args);
if (cmd) {
int err;
@ -1030,6 +1228,8 @@ Tkapp_Eval(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:eval", &script))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
err = Tcl_Eval(Tkapp_Interp(self), script);
ENTER_OVERLAP
@ -1051,6 +1251,8 @@ Tkapp_GlobalEval(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:globaleval", &script))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
err = Tcl_GlobalEval(Tkapp_Interp(self), script);
ENTER_OVERLAP
@ -1072,6 +1274,8 @@ Tkapp_EvalFile(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:evalfile", &fileName))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
err = Tcl_EvalFile(Tkapp_Interp(self), fileName);
ENTER_OVERLAP
@ -1094,6 +1298,8 @@ Tkapp_Record(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s", &script))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
err = Tcl_RecordAndEval(Tkapp_Interp(self), script, TCL_NO_EVAL);
ENTER_OVERLAP
@ -1112,6 +1318,8 @@ Tkapp_AddErrorInfo(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:adderrorinfo", &msg))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
Tcl_AddErrorInfo(Tkapp_Interp(self), msg);
LEAVE_TCL
@ -1124,11 +1332,144 @@ Tkapp_AddErrorInfo(PyObject *self, PyObject *args)
/** Tcl Variable **/
TCL_DECLARE_MUTEX(var_mutex)
typedef const char* (*EventFunc1)(Tcl_Interp*, const char*, int);
typedef const char* (*EventFunc2)(Tcl_Interp*, const char*, const char*, int);
typedef const char* (*EventFunc3)(Tcl_Interp*, const char*, const char*, const char*, int);
typedef struct VarEvent {
Tcl_Event ev; /* must be first */
TkappObject *self;
char* arg1;
char* arg2;
char* arg3;
int flags;
EventFunc1 func1;
EventFunc2 func2;
EventFunc3 func3;
PyObject **res;
PyObject **exc;
Tcl_Condition cond;
int coderesult;
} VarEvent;
static const char*
var_perform(VarEvent *ev)
{
if (!ev->arg2 && !ev->arg2)
return ev->func1(ev->self->interp, ev->arg1, ev->flags);
if (!ev->arg3)
return ev->func2(ev->self->interp, ev->arg1,
ev->arg2, ev->flags);
return ev->func3(ev->self->interp, ev->arg1, ev->arg2,
ev->arg3, ev->flags);
}
static void
var_fill_result(VarEvent *ev, const char* res)
{
if (ev->coderesult) {
if ((int)res != TCL_ERROR) {
Py_INCREF(Py_None);
*(ev->res) = Py_None;
return;
}
}
else if (res) {
*(ev->res) = PyString_FromString(res);
return;
}
*(ev->res) = NULL;
*(ev->exc) = PyObject_CallFunction(
Tkinter_TclError, "s",
Tcl_GetStringResult(ev->self->interp));
}
static int
var_proc(VarEvent* ev, int flags)
{
const char *result = var_perform(ev);
ENTER_PYTHON
var_fill_result(ev, result);
Tcl_MutexLock(&var_mutex);
Tcl_ConditionNotify(&ev->cond);
Tcl_MutexUnlock(&var_mutex);
LEAVE_PYTHON
return 1;
}
static PyObject*
var_invoke(PyObject *_self, char* arg1, char* arg2, char* arg3, int flags,
EventFunc1 func1, EventFunc2 func2, EventFunc3 func3,
int coderesult)
{
VarEvent _ev;
TkappObject *self = (TkappObject*)_self;
VarEvent *ev = self->threaded ?
(VarEvent*)ckalloc(sizeof(VarEvent)) : &_ev;
PyObject *res, *exc;
ev->self = self;
ev->arg1 = arg1;
ev->arg2 = arg2;
ev->arg3 = arg3;
ev->flags = flags;
ev->func1 = func1;
ev->func2 = func2;
ev->func3 = func3;
ev->coderesult = coderesult;
ev->res = &res;
ev->exc = &exc;
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
/* The current thread is not the interpreter thread. Marshal
the call to the interpreter thread, then wait for
completion. */
if (!self->dispatching) {
PyErr_SetString(PyExc_RuntimeError,
"main thread is not in main loop");
return NULL;
}
ev->cond = NULL;
ev->ev.proc = (Tcl_EventProc*)var_proc;
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->cond, &var_mutex);
}
else {
/* Tcl is not threaded, or this is the interpreter thread. To
perform the call, we must hold the TCL lock. To receive the
results, we must also hold the Python lock. */
const char *result;
ENTER_TCL
result = var_perform(ev);
ENTER_OVERLAP
var_fill_result(ev, result);
LEAVE_OVERLAP_TCL
}
if (!res) {
PyErr_SetObject(Tkinter_TclError, exc);
return NULL;
}
return res;
}
static PyObject*
var_invoke2(PyObject *_self, char* arg1, char* arg2, char* arg3, int flags,
int (*func1)(Tcl_Interp*, const char*, int),
int (*func2)(Tcl_Interp*, const char*, const char*, int),
int (*func3)(Tcl_Interp*, const char*, const char*, const char*, int))
{
return var_invoke(_self, arg1, arg2, arg3, flags,
(EventFunc1)func1, (EventFunc2)func2,
(EventFunc3)func3, 1);
}
static PyObject *
SetVar(PyObject *self, PyObject *args, int flags)
{
char *name1, *name2, *s;
const char *ok;
PyObject *res;
PyObject *newValue;
PyObject *tmp;
@ -1141,9 +1482,8 @@ SetVar(PyObject *self, PyObject *args, int flags)
s = AsString(newValue, tmp);
if (s == NULL)
return NULL;
ENTER_TCL
ok = Tcl_SetVar(Tkapp_Interp(self), name1, s, flags);
LEAVE_TCL
res = var_invoke(self, name1, s, NULL, flags,
NULL, Tcl_SetVar, NULL, 0);
}
else {
PyErr_Clear();
@ -1152,10 +1492,8 @@ SetVar(PyObject *self, PyObject *args, int flags)
s = AsString(newValue, tmp);
if (s == NULL)
return NULL;
ENTER_TCL
ok = Tcl_SetVar2(Tkapp_Interp(self), name1, name2,
s, flags);
LEAVE_TCL
res = var_invoke(self, name1, name2, s, flags,
NULL, NULL, Tcl_SetVar2, 0);
}
else {
Py_DECREF(tmp);
@ -1164,9 +1502,10 @@ SetVar(PyObject *self, PyObject *args, int flags)
}
Py_DECREF(tmp);
if (!ok)
return Tkinter_Error(self);
if (!res)
return NULL;
Py_DECREF(res);
Py_INCREF(Py_None);
return Py_None;
}
@ -1189,24 +1528,13 @@ static PyObject *
GetVar(PyObject *self, PyObject *args, int flags)
{
char *name1, *name2=NULL;
const char *s;
PyObject *res = NULL;
if (!PyArg_ParseTuple(args, "s|s:getvar", &name1, &name2))
return NULL;
ENTER_TCL
if (name2 == NULL)
s = Tcl_GetVar(Tkapp_Interp(self), name1, flags);
else
s = Tcl_GetVar2(Tkapp_Interp(self), name1, name2, flags);
ENTER_OVERLAP
if (s == NULL)
res = Tkinter_Error(self);
else
res = PyString_FromString(s);
LEAVE_OVERLAP_TCL
res = var_invoke(self, name1, name2, NULL, flags,
Tcl_GetVar, Tcl_GetVar2, NULL, 0);
return res;
}
@ -1229,25 +1557,12 @@ UnsetVar(PyObject *self, PyObject *args, int flags)
{
char *name1, *name2=NULL;
PyObject *res = NULL;
int code;
if (!PyArg_ParseTuple(args, "s|s:unsetvar", &name1, &name2))
return NULL;
ENTER_TCL
if (name2 == NULL)
code = Tcl_UnsetVar(Tkapp_Interp(self), name1, flags);
else
code = Tcl_UnsetVar2(Tkapp_Interp(self), name1, name2, flags);
ENTER_OVERLAP
if (code == TCL_ERROR)
res = Tkinter_Error(self);
else {
Py_INCREF(Py_None);
res = Py_None;
}
LEAVE_OVERLAP_TCL
res = var_invoke2(self, name1, name2, NULL, flags,
Tcl_UnsetVar, Tcl_UnsetVar2, NULL);
return res;
}
@ -1336,6 +1651,9 @@ Tkapp_ExprString(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:exprstring", &s))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
retval = Tcl_ExprString(Tkapp_Interp(self), s);
ENTER_OVERLAP
@ -1357,6 +1675,9 @@ Tkapp_ExprLong(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:exprlong", &s))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
retval = Tcl_ExprLong(Tkapp_Interp(self), s, &v);
ENTER_OVERLAP
@ -1378,6 +1699,7 @@ Tkapp_ExprDouble(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:exprdouble", &s))
return NULL;
CHECK_TCL_APPARTMENT;
PyFPE_START_PROTECT("Tkapp_ExprDouble", return 0)
ENTER_TCL
retval = Tcl_ExprDouble(Tkapp_Interp(self), s, &v);
@ -1401,6 +1723,7 @@ Tkapp_ExprBoolean(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s:exprboolean", &s))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
retval = Tcl_ExprBoolean(Tkapp_Interp(self), s, &v);
ENTER_OVERLAP
@ -1575,13 +1898,42 @@ PythonCmdDelete(ClientData clientData)
static PyObject *
Tkapp_CreateCommand(PyObject *self, PyObject *args)
TCL_DECLARE_MUTEX(command_mutex)
typedef struct CommandEvent{
Tcl_Event ev;
Tcl_Interp* interp;
char *name;
int create;
int *status;
ClientData *data;
Tcl_Condition done;
} CommandEvent;
static int
Tkapp_CommandProc(CommandEvent *ev, int flags)
{
if (ev->create)
*ev->status = Tcl_CreateCommand(
ev->interp, ev->name, PythonCmd,
ev->data, PythonCmdDelete) == NULL;
else
*ev->status = Tcl_DeleteCommand(ev->interp, ev->name);
Tcl_MutexLock(&command_mutex);
Tcl_ConditionNotify(&ev->done);
Tcl_MutexUnlock(&command_mutex);
return 1;
}
static PyObject *
Tkapp_CreateCommand(PyObject *_self, PyObject *args)
{
TkappObject *self = (TkappObject*)_self;
PythonCmd_ClientData *data;
char *cmdName;
PyObject *func;
Tcl_Command err;
int err;
if (!PyArg_ParseTuple(args, "sO:createcommand", &cmdName, &func))
return NULL;
@ -1590,19 +1942,40 @@ Tkapp_CreateCommand(PyObject *self, PyObject *args)
return NULL;
}
if (self->threaded && self->thread_id != Tcl_GetCurrentThread() &&
!self->dispatching) {
PyErr_SetString(PyExc_RuntimeError,
"main thread is not in main loop");
return NULL;
}
data = PyMem_NEW(PythonCmd_ClientData, 1);
if (!data)
return NULL;
Py_XINCREF(self);
Py_XINCREF(func);
data->self = self;
data->self = _self;
data->func = func;
ENTER_TCL
err = Tcl_CreateCommand(Tkapp_Interp(self), cmdName, PythonCmd,
(ClientData)data, PythonCmdDelete);
LEAVE_TCL
if (err == NULL) {
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
CommandEvent *ev = (CommandEvent*)ckalloc(sizeof(CommandEvent));
ev->ev.proc = (Tcl_EventProc*)Tkapp_CommandProc;
ev->interp = self->interp;
ev->create = 1;
ev->name = cmdName;
ev->data = (ClientData)data;
ev->status = &err;
ev->done = NULL;
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->done, &command_mutex);
}
else {
ENTER_TCL
err = Tcl_CreateCommand(
Tkapp_Interp(self), cmdName, PythonCmd,
(ClientData)data, PythonCmdDelete) == NULL;
LEAVE_TCL
}
if (err) {
PyErr_SetString(Tkinter_TclError, "can't create Tcl command");
PyMem_DEL(data);
return NULL;
@ -1615,16 +1988,31 @@ Tkapp_CreateCommand(PyObject *self, PyObject *args)
static PyObject *
Tkapp_DeleteCommand(PyObject *self, PyObject *args)
Tkapp_DeleteCommand(PyObject *_self, PyObject *args)
{
TkappObject *self = (TkappObject*)_self;
char *cmdName;
int err;
if (!PyArg_ParseTuple(args, "s:deletecommand", &cmdName))
return NULL;
ENTER_TCL
err = Tcl_DeleteCommand(Tkapp_Interp(self), cmdName);
LEAVE_TCL
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
CommandEvent *ev;
ev = (CommandEvent*)ckalloc(sizeof(CommandEvent));
ev->ev.proc = (Tcl_EventProc*)Tkapp_CommandProc;
ev->interp = self->interp;
ev->create = 0;
ev->name = cmdName;
ev->status = &err;
ev->done = NULL;
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->done,
&command_mutex);
}
else {
ENTER_TCL
err = Tcl_DeleteCommand(self->interp, cmdName);
LEAVE_TCL
}
if (err == -1) {
PyErr_SetString(Tkinter_TclError, "can't delete Tcl command");
return NULL;
@ -1715,6 +2103,7 @@ Tkapp_CreateFileHandler(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "OiO:createfilehandler",
&file, &mask, &func))
return NULL;
CHECK_TCL_APPARTMENT;
tfile = PyObject_AsFileDescriptor(file);
if (tfile < 0)
return NULL;
@ -1743,6 +2132,7 @@ Tkapp_DeleteFileHandler(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "O:deletefilehandler", &file))
return NULL;
CHECK_TCL_APPARTMENT;
tfile = PyObject_AsFileDescriptor(file);
if (tfile < 0)
return NULL;
@ -1918,9 +2308,10 @@ Tkapp_CreateTimerHandler(PyObject *self, PyObject *args)
/** Event Loop **/
static PyObject *
Tkapp_MainLoop(PyObject *self, PyObject *args)
Tkapp_MainLoop(PyObject *_self, PyObject *args)
{
int threshold = 0;
TkappObject *self = (TkappObject*)_self;
#ifdef WITH_THREAD
PyThreadState *tstate = PyThreadState_Get();
#endif
@ -1928,7 +2319,10 @@ Tkapp_MainLoop(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "|i:mainloop", &threshold))
return NULL;
CHECK_TCL_APPARTMENT;
quitMainLoop = 0;
self->dispatching = 1;
while (Tk_GetNumMainWindows() > threshold &&
!quitMainLoop &&
!errorInCmd)
@ -1936,24 +2330,35 @@ Tkapp_MainLoop(PyObject *self, PyObject *args)
int result;
#ifdef WITH_THREAD
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(tcl_lock, 1);
tcl_tstate = tstate;
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
tcl_tstate = NULL;
PyThread_release_lock(tcl_lock);
if (result == 0)
Sleep(20);
Py_END_ALLOW_THREADS
if (self->threaded) {
/* Allow other Python threads to run. */
ENTER_TCL
result = Tcl_DoOneEvent(0);
LEAVE_TCL
}
else {
Py_BEGIN_ALLOW_THREADS
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
tcl_tstate = tstate;
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
tcl_tstate = NULL;
if(tcl_lock)PyThread_release_lock(tcl_lock);
if (result == 0)
Sleep(20);
Py_END_ALLOW_THREADS
}
#else
result = Tcl_DoOneEvent(0);
#endif
if (PyErr_CheckSignals() != 0)
if (PyErr_CheckSignals() != 0) {
self->dispatching = 0;
return NULL;
}
if (result < 0)
break;
}
self->dispatching = 0;
quitMainLoop = 0;
if (errorInCmd) {
@ -1974,6 +2379,7 @@ Tkapp_DoOneEvent(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "|i:dooneevent", &flags))
return NULL;
CHECK_TCL_APPARTMENT;
ENTER_TCL
rv = Tcl_DoOneEvent(flags);
@ -2068,6 +2474,7 @@ static PyMethodDef Tkapp_methods[] =
static void
Tkapp_Dealloc(PyObject *self)
{
//CHECK_TCL_APPARTMENT;
ENTER_TCL
Tcl_DeleteInterp(Tkapp_Interp(self));
LEAVE_TCL
@ -2292,13 +2699,13 @@ EventHook(void)
#endif
#if defined(WITH_THREAD) || defined(MS_WINDOWS)
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(tcl_lock, 1);
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
tcl_tstate = event_tstate;
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
tcl_tstate = NULL;
PyThread_release_lock(tcl_lock);
if(tcl_lock)PyThread_release_lock(tcl_lock);
if (result == 0)
Sleep(20);
Py_END_ALLOW_THREADS