Skip to content

Commit 53a3669

Browse files
Michiel Jan Laurens de HoonMichiel Jan Laurens de Hoon
authored andcommitted
require that Tcl is built with thread support
1 parent 16d0c89 commit 53a3669

2 files changed

Lines changed: 42 additions & 84 deletions

File tree

‎Doc/library/tkinter.rst‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ demonstrating a simple Tk interface, letting you know that :mod:`!tkinter` is
1717
properly installed on your system, and also showing what version of Tcl/Tk is
1818
installed, so you can read the Tcl/Tk documentation specific to that version.
1919

20-
Tkinter supports a range of Tcl/Tk versions, built either with or without
20+
Tkinter supports a range of Tcl/Tk versions, which must be built with
2121
thread support.
2222
Tcl/Tk 8.5.12 is the minimum supported version; the official Python binary
2323
release bundles Tcl/Tk 9.0.
@@ -476,18 +476,11 @@ interpreter will fail.
476476

477477
A number of special cases exist:
478478

479-
* Tcl/Tk libraries built without thread support are now rare: Tcl/Tk 9.0 (the
480-
bundled version) is always thread-aware, so this case only arises with some
481-
older 8.x builds. When the library is not thread-aware,
482-
:mod:`!tkinter` calls the library from the originating Python thread, even
483-
if this is different than the thread that created the Tcl interpreter. A global
484-
lock ensures only one call occurs at a time.
485-
486479
* While :mod:`!tkinter` allows you to create more than one instance of a :class:`Tk`
487480
object (with its own interpreter), all interpreters that are part of the same
488481
thread share a common event queue, which gets ugly fast. In practice, don't create
489482
more than one instance of :class:`Tk` at a time. Otherwise, it's best to create
490-
them in separate threads and ensure you're running a thread-aware Tcl/Tk build.
483+
them in separate threads.
491484

492485
* Blocking event handlers are not the only way to prevent the Tcl interpreter from
493486
reentering the event loop. It is even possible to run multiple nested event loops

‎Modules/_tkinter.c‎

Lines changed: 40 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ Copyright (C) 1994 Steen Lumholt.
4040
#define CHECK_SIZE(size, elemsize) \
4141
((size_t)(size) <= Py_MIN((size_t)INT_MAX, UINT_MAX / (size_t)(elemsize)))
4242

43-
/* If Tcl is compiled for threads, we must also define TCL_THREAD. We define
44-
it always; if Tcl is not threaded, the thread functions in
45-
Tcl are empty. */
43+
/* As we require that Tcl is compiled for threads, we must also define
44+
TCL_THREADS. We define it always; if Tcl is not threaded, the thread
45+
functions in Tcl are empty. We check if Tcl is actually compiled for
46+
threads when importing this module. */
4647
#define TCL_THREADS
4748

4849
#ifdef TK_FRAMEWORK
@@ -179,6 +180,7 @@ _get_tcl_lib_path(void)
179180
}
180181
#endif /* MS_WINDOWS */
181182

183+
<<<<<<< HEAD
182184
#if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9
183185
static void
184186
mount_tk_dll_zip(void)
@@ -233,13 +235,8 @@ Tkinter_TkInit(Tcl_Interp *interp)
233235
/* The threading situation is complicated. Tcl is not thread-safe, except
234236
when configured with --enable-threads.
235237
236-
So we need to use a lock around all uses of Tcl. Previously, the
237-
Python interpreter lock was used for this. However, this causes
238-
problems when other Python threads need to run while Tcl is blocked
239-
waiting for events.
240-
241-
To solve this problem, a separate lock for Tcl is introduced.
242-
Holding it is incompatible with holding Python's interpreter lock.
238+
We introduce a lock specifically for Tcl; holding it is incompatible
239+
with holding Python's interpreter lock.
243240
The following four macros manipulate both locks together.
244241
245242
ENTER_TCL and LEAVE_TCL are brackets, just like
@@ -271,9 +268,8 @@ Tkinter_TkInit(Tcl_Interp *interp)
271268
These locks expand to several statements and brackets; they should
272269
not be used in branches of if statements and the like.
273270
274-
If Tcl is threaded, this approach won't work anymore. The Tcl
275-
interpreter is only valid in the thread that created it, and all Tk
276-
activity must happen in this thread, also. That means that the
271+
The Tcl interpreter is only valid in the thread that created it, and
272+
all Tk activity must happen in this thread, also. That means that the
277273
mainloop must be invoked in the thread that created the
278274
interpreter. Invoking commands from other threads is possible;
279275
_tkinter will queue an event for the interpreter thread, which will
@@ -283,48 +279,51 @@ Tkinter_TkInit(Tcl_Interp *interp)
283279
the command invocation will block.
284280
285281
In addition, for a threaded Tcl, a single global tcl_tstate won't
286-
be sufficient anymore, since multiple Tcl interpreters may
287-
simultaneously dispatch in different threads. So we use the Tcl TLS
288-
API.
282+
be sufficient, since multiple Tcl interpreters may simultaneously
283+
dispatch in different threads. So we use the Tcl TLS API.
289284
290285
*/
291286

292-
static PyThread_type_lock tcl_lock = 0;
287+
static int
288+
_check_tcl_threaded(void)
289+
{
290+
Tcl_Interp* interp;
291+
Tcl_Obj* threaded;
292+
interp = Tcl_CreateInterp();
293+
threaded = Tcl_GetVar2Ex(interp,
294+
"tcl_platform",
295+
"threaded",
296+
TCL_GLOBAL_ONLY);
297+
Tcl_DeleteInterp(interp);
298+
if (threaded == NULL) return 0;
299+
else return 1;
300+
}
293301

294-
#ifdef TCL_THREADS
295302
static Tcl_ThreadDataKey state_key;
296303
#define tcl_tstate \
297304
(*(PyThreadState**)Tcl_GetThreadData(&state_key, sizeof(PyThreadState*)))
298-
#else
299-
static PyThreadState *tcl_tstate = NULL;
300-
#endif
301305

302306
#define ENTER_TCL \
303307
{ PyThreadState *tstate = PyThreadState_Get(); \
304308
Py_BEGIN_ALLOW_THREADS \
305-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); \
306309
tcl_tstate = tstate;
307310

308311
#define LEAVE_TCL \
309312
tcl_tstate = NULL; \
310-
if(tcl_lock)PyThread_release_lock(tcl_lock); \
311313
Py_END_ALLOW_THREADS}
312314

313315
#define ENTER_OVERLAP \
314316
Py_END_ALLOW_THREADS
315317

316318
#define LEAVE_OVERLAP_TCL \
317-
tcl_tstate = NULL; if(tcl_lock)PyThread_release_lock(tcl_lock); }
319+
tcl_tstate = NULL; }
318320

319321
#define ENTER_PYTHON \
320322
{ PyThreadState *tstate = tcl_tstate; tcl_tstate = NULL; \
321-
if(tcl_lock) \
322-
PyThread_release_lock(tcl_lock); \
323323
PyEval_RestoreThread((tstate)); }
324324

325325
#define LEAVE_PYTHON \
326326
{ PyThreadState *tstate = PyEval_SaveThread(); \
327-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); \
328327
tcl_tstate = tstate; }
329328

330329
#ifndef FREECAST
@@ -339,7 +338,6 @@ typedef struct {
339338
PyObject_HEAD
340339
Tcl_Interp *interp;
341340
int wantobjects;
342-
int threaded; /* True if tcl_platform[threaded] */
343341
Tcl_ThreadId thread_id;
344342
int dispatching;
345343
PyObject *trace;
@@ -373,7 +371,7 @@ typedef struct {
373371
static inline int
374372
check_tcl_appartment(TkappObject *app)
375373
{
376-
if (app->threaded && app->thread_id != Tcl_GetCurrentThread()) {
374+
if (app->thread_id != Tcl_GetCurrentThread()) {
377375
PyErr_SetString(PyExc_RuntimeError,
378376
"Calling Tcl from different apartment");
379377
return -1;
@@ -652,20 +650,6 @@ Tkapp_New(const char *screenName, const char *className,
652650
v->dispatching = 0;
653651
v->trace = NULL;
654652

655-
#ifndef TCL_THREADS
656-
if (v->threaded) {
657-
PyErr_SetString(PyExc_RuntimeError,
658-
"Tcl is threaded but _tkinter is not");
659-
Py_DECREF(v);
660-
return 0;
661-
}
662-
#endif
663-
if (v->threaded && tcl_lock) {
664-
/* If Tcl is threaded, we don't need the lock. */
665-
PyThread_free_lock(tcl_lock);
666-
tcl_lock = NULL;
667-
}
668-
669653
v->OldBooleanType = Tcl_GetObjType("boolean");
670654
{
671655
Tcl_Obj *value;
@@ -1659,13 +1643,11 @@ Tkapp_CallProc(Tcl_Event *evPtr, int flags)
16591643

16601644

16611645
/* This is the main entry point for calling a Tcl command.
1662-
It supports three cases, with regard to threading:
1663-
1. Tcl is not threaded: Must have the Tcl lock, then can invoke command in
1664-
the context of the calling thread.
1665-
2. Tcl is threaded, caller of the command is in the interpreter thread:
1646+
It supports two cases, with regard to threading:
1647+
2. Caller of the command is in the interpreter thread:
16661648
Execute the command in the calling thread. Since the Tcl lock will
16671649
not be used, we can merge that with case 1.
1668-
3. Tcl is threaded, caller is in a different thread: Must queue an event to
1650+
3. Caller is in a different thread: Must queue an event to
16691651
the interpreter thread. Allocation of Tcl objects needs to occur in the
16701652
interpreter thread, so we ship the PyObject* args to the target thread,
16711653
and perform processing there. */
@@ -1686,7 +1668,7 @@ Tkapp_Call(PyObject *selfptr, PyObject *args)
16861668
if (PyTuple_Check(item))
16871669
args = item;
16881670
}
1689-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
1671+
if (self->thread_id != Tcl_GetCurrentThread()) {
16901672
/* We cannot call the command directly. Instead, we must
16911673
marshal the parameters to the interpreter thread. */
16921674
Tkapp_CallEvent *ev;
@@ -1963,7 +1945,7 @@ static PyObject*
19631945
var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
19641946
{
19651947
TkappObject *self = TkappObject_CAST(selfptr);
1966-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
1948+
if (self->thread_id != Tcl_GetCurrentThread()) {
19671949
VarEvent *ev;
19681950
// init 'res' and 'exc' to make static analyzers happy
19691951
PyObject *res = NULL, *exc = NULL;
@@ -1997,7 +1979,7 @@ var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
19971979
}
19981980
return res;
19991981
}
2000-
/* Tcl is not threaded, or this is the interpreter thread. */
1982+
/* This is the interpreter thread. */
20011983
return func(self, args, flags);
20021984
}
20031985

@@ -2655,7 +2637,7 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
26552637
return NULL;
26562638
}
26572639

2658-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread() &&
2640+
if (self->thread_id != Tcl_GetCurrentThread() &&
26592641
!WaitForMainloop(self))
26602642
return NULL;
26612643

@@ -2669,7 +2651,7 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
26692651
(gh-80937). The command cannot outlive the interpreter. */
26702652
data->self = self;
26712653
data->func = Py_NewRef(func);
2672-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
2654+
if (self->thread_id != Tcl_GetCurrentThread()) {
26732655
err = 0; // init to make static analyzers happy
26742656

26752657
Tcl_Condition cond = NULL;
@@ -2727,7 +2709,7 @@ _tkinter_tkapp_deletecommand_impl(TkappObject *self, const char *name)
27272709

27282710
TRACE(self, ("((sss))", "rename", name, ""));
27292711

2730-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
2712+
if (self->thread_id != Tcl_GetCurrentThread()) {
27312713
err = 0; // init to make static analyzers happy
27322714

27332715
Tcl_Condition cond = NULL;
@@ -3082,8 +3064,6 @@ static PyObject *
30823064
_tkinter_tkapp_mainloop_impl(TkappObject *self, int threshold)
30833065
/*[clinic end generated code: output=0ba8eabbe57841b0 input=036bcdcf03d5eca0]*/
30843066
{
3085-
PyThreadState *tstate = PyThreadState_Get();
3086-
30873067
CHECK_TCL_APPARTMENT(self);
30883068
self->dispatching = 1;
30893069

@@ -3094,23 +3074,10 @@ _tkinter_tkapp_mainloop_impl(TkappObject *self, int threshold)
30943074
{
30953075
int result;
30963076

3097-
if (self->threaded) {
3098-
/* Allow other Python threads to run. */
3099-
ENTER_TCL
3100-
result = Tcl_DoOneEvent(0);
3101-
LEAVE_TCL
3102-
}
3103-
else {
3104-
Py_BEGIN_ALLOW_THREADS
3105-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
3106-
tcl_tstate = tstate;
3107-
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
3108-
tcl_tstate = NULL;
3109-
if(tcl_lock)PyThread_release_lock(tcl_lock);
3110-
if (result == 0)
3111-
Sleep(Tkinter_busywaitinterval);
3112-
Py_END_ALLOW_THREADS
3113-
}
3077+
/* Allow other Python threads to run. */
3078+
ENTER_TCL
3079+
result = Tcl_DoOneEvent(0);
3080+
LEAVE_TCL
31143081

31153082
if (PyErr_CheckSignals() != 0) {
31163083
self->dispatching = 0;
@@ -3686,13 +3653,11 @@ EventHook(void)
36863653
}
36873654
#endif
36883655
Py_BEGIN_ALLOW_THREADS
3689-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
36903656
tcl_tstate = event_tstate;
36913657

36923658
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
36933659

36943660
tcl_tstate = NULL;
3695-
if(tcl_lock)PyThread_release_lock(tcl_lock);
36963661
if (result == 0)
36973662
Sleep(Tkinter_busywaitinterval);
36983663
Py_END_ALLOW_THREADS

0 commit comments

Comments
 (0)