Skip to content

Commit 9f81539

Browse files
Michiel Jan Laurens de HoonMichiel Jan Laurens de Hoon
authored andcommitted
require that Tcl is built with thread support
1 parent d13ee0a commit 9f81539

File tree

2 files changed

+49
-90
lines changed

2 files changed

+49
-90
lines changed

Doc/library/tkinter.rst

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

22-
Tkinter supports a range of Tcl/Tk versions, built either with or
23-
without thread support. The official Python binary release bundles Tcl/Tk 8.6
22+
Tkinter supports a range of Tcl/Tk versions, which must be built with
23+
thread support. The official Python binary release bundles Tcl/Tk 8.6
2424
threaded. See the source code for the :mod:`_tkinter` module
2525
for more information about supported versions.
2626

@@ -534,16 +534,11 @@ interpreter will fail.
534534

535535
A number of special cases exist:
536536

537-
* Tcl/Tk libraries can be built so they are not thread-aware. In this case,
538-
:mod:`tkinter` calls the library from the originating Python thread, even
539-
if this is different than the thread that created the Tcl interpreter. A global
540-
lock ensures only one call occurs at a time.
541-
542537
* While :mod:`tkinter` allows you to create more than one instance of a :class:`Tk`
543538
object (with its own interpreter), all interpreters that are part of the same
544539
thread share a common event queue, which gets ugly fast. In practice, don't create
545540
more than one instance of :class:`Tk` at a time. Otherwise, it's best to create
546-
them in separate threads and ensure you're running a thread-aware Tcl/Tk build.
541+
them in separate threads.
547542

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

Modules/_tkinter.c

Lines changed: 46 additions & 82 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
@@ -172,16 +173,11 @@ _get_tcl_lib_path(void)
172173
}
173174
#endif /* MS_WINDOWS */
174175

175-
/* The threading situation is complicated. Tcl is not thread-safe, except
176-
when configured with --enable-threads.
176+
/* The threading situation is complicated.
177+
We require that Tcl is compiled for threads.
177178
178-
So we need to use a lock around all uses of Tcl. Previously, the
179-
Python interpreter lock was used for this. However, this causes
180-
problems when other Python threads need to run while Tcl is blocked
181-
waiting for events.
182-
183-
To solve this problem, a separate lock for Tcl is introduced.
184-
Holding it is incompatible with holding Python's interpreter lock.
179+
We introduce a lock specifically for Tcl; holding it is incompatible
180+
with holding Python's interpreter lock.
185181
The following four macros manipulate both locks together.
186182
187183
ENTER_TCL and LEAVE_TCL are brackets, just like
@@ -213,9 +209,8 @@ _get_tcl_lib_path(void)
213209
These locks expand to several statements and brackets; they should
214210
not be used in branches of if statements and the like.
215211
216-
If Tcl is threaded, this approach won't work anymore. The Tcl
217-
interpreter is only valid in the thread that created it, and all Tk
218-
activity must happen in this thread, also. That means that the
212+
The Tcl interpreter is only valid in the thread that created it, and
213+
all Tk activity must happen in this thread, also. That means that the
219214
mainloop must be invoked in the thread that created the
220215
interpreter. Invoking commands from other threads is possible;
221216
_tkinter will queue an event for the interpreter thread, which will
@@ -225,49 +220,52 @@ _get_tcl_lib_path(void)
225220
the command invocation will block.
226221
227222
In addition, for a threaded Tcl, a single global tcl_tstate won't
228-
be sufficient anymore, since multiple Tcl interpreters may
229-
simultaneously dispatch in different threads. So we use the Tcl TLS
230-
API.
223+
be sufficient, since multiple Tcl interpreters may simultaneously
224+
dispatch in different threads. So we use the Tcl TLS API.
231225
232226
*/
233227

234-
static PyThread_type_lock tcl_lock = 0;
228+
static int
229+
_check_tcl_threaded(void)
230+
{
231+
Tcl_Interp* interp;
232+
Tcl_Obj* threaded;
233+
interp = Tcl_CreateInterp();
234+
threaded = Tcl_GetVar2Ex(interp,
235+
"tcl_platform",
236+
"threaded",
237+
TCL_GLOBAL_ONLY);
238+
Tcl_DeleteInterp(interp);
239+
if (threaded == NULL) return 0;
240+
else return 1;
241+
}
235242

236-
#ifdef TCL_THREADS
237243
static Tcl_ThreadDataKey state_key;
238244
typedef PyThreadState *ThreadSpecificData;
239245
#define tcl_tstate \
240246
(*(PyThreadState**)Tcl_GetThreadData(&state_key, sizeof(PyThreadState*)))
241-
#else
242-
static PyThreadState *tcl_tstate = NULL;
243-
#endif
244247

245248
#define ENTER_TCL \
246249
{ PyThreadState *tstate = PyThreadState_Get(); \
247250
Py_BEGIN_ALLOW_THREADS \
248-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); \
249251
tcl_tstate = tstate;
250252

251253
#define LEAVE_TCL \
252254
tcl_tstate = NULL; \
253-
if(tcl_lock)PyThread_release_lock(tcl_lock); \
254255
Py_END_ALLOW_THREADS}
255256

256257
#define ENTER_OVERLAP \
257258
Py_END_ALLOW_THREADS
258259

259260
#define LEAVE_OVERLAP_TCL \
260-
tcl_tstate = NULL; if(tcl_lock)PyThread_release_lock(tcl_lock); }
261+
tcl_tstate = NULL; }
261262

262263
#define ENTER_PYTHON \
263264
{ PyThreadState *tstate = tcl_tstate; tcl_tstate = NULL; \
264-
if(tcl_lock) \
265-
PyThread_release_lock(tcl_lock); \
266265
PyEval_RestoreThread((tstate)); }
267266

268267
#define LEAVE_PYTHON \
269268
{ PyThreadState *tstate = PyEval_SaveThread(); \
270-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); \
271269
tcl_tstate = tstate; }
272270

273271
#ifndef FREECAST
@@ -282,7 +280,6 @@ typedef struct {
282280
PyObject_HEAD
283281
Tcl_Interp *interp;
284282
int wantobjects;
285-
int threaded; /* True if tcl_platform[threaded] */
286283
Tcl_ThreadId thread_id;
287284
int dispatching;
288285
PyObject *trace;
@@ -307,7 +304,7 @@ typedef struct {
307304
static inline int
308305
check_tcl_appartment(TkappObject *app)
309306
{
310-
if (app->threaded && app->thread_id != Tcl_GetCurrentThread()) {
307+
if (app->thread_id != Tcl_GetCurrentThread()) {
311308
PyErr_SetString(PyExc_RuntimeError,
312309
"Calling Tcl from different apartment");
313310
return -1;
@@ -575,26 +572,10 @@ Tkapp_New(const char *screenName, const char *className,
575572

576573
v->interp = Tcl_CreateInterp();
577574
v->wantobjects = wantobjects;
578-
v->threaded = Tcl_GetVar2Ex(v->interp, "tcl_platform", "threaded",
579-
TCL_GLOBAL_ONLY) != NULL;
580575
v->thread_id = Tcl_GetCurrentThread();
581576
v->dispatching = 0;
582577
v->trace = NULL;
583578

584-
#ifndef TCL_THREADS
585-
if (v->threaded) {
586-
PyErr_SetString(PyExc_RuntimeError,
587-
"Tcl is threaded but _tkinter is not");
588-
Py_DECREF(v);
589-
return 0;
590-
}
591-
#endif
592-
if (v->threaded && tcl_lock) {
593-
/* If Tcl is threaded, we don't need the lock. */
594-
PyThread_free_lock(tcl_lock);
595-
tcl_lock = NULL;
596-
}
597-
598579
v->OldBooleanType = Tcl_GetObjType("boolean");
599580
{
600581
Tcl_Obj *value;
@@ -1442,13 +1423,11 @@ Tkapp_CallProc(Tcl_Event *evPtr, int flags)
14421423

14431424

14441425
/* This is the main entry point for calling a Tcl command.
1445-
It supports three cases, with regard to threading:
1446-
1. Tcl is not threaded: Must have the Tcl lock, then can invoke command in
1447-
the context of the calling thread.
1448-
2. Tcl is threaded, caller of the command is in the interpreter thread:
1426+
It supports two cases, with regard to threading:
1427+
2. Caller of the command is in the interpreter thread:
14491428
Execute the command in the calling thread. Since the Tcl lock will
14501429
not be used, we can merge that with case 1.
1451-
3. Tcl is threaded, caller is in a different thread: Must queue an event to
1430+
3. Caller is in a different thread: Must queue an event to
14521431
the interpreter thread. Allocation of Tcl objects needs to occur in the
14531432
interpreter thread, so we ship the PyObject* args to the target thread,
14541433
and perform processing there. */
@@ -1469,7 +1448,7 @@ Tkapp_Call(PyObject *selfptr, PyObject *args)
14691448
if (PyTuple_Check(item))
14701449
args = item;
14711450
}
1472-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
1451+
if (self->thread_id != Tcl_GetCurrentThread()) {
14731452
/* We cannot call the command directly. Instead, we must
14741453
marshal the parameters to the interpreter thread. */
14751454
Tkapp_CallEvent *ev;
@@ -1746,7 +1725,7 @@ static PyObject*
17461725
var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
17471726
{
17481727
TkappObject *self = TkappObject_CAST(selfptr);
1749-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
1728+
if (self->thread_id != Tcl_GetCurrentThread()) {
17501729
VarEvent *ev;
17511730
// init 'res' and 'exc' to make static analyzers happy
17521731
PyObject *res = NULL, *exc = NULL;
@@ -1780,7 +1759,7 @@ var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
17801759
}
17811760
return res;
17821761
}
1783-
/* Tcl is not threaded, or this is the interpreter thread. */
1762+
/* This is the interpreter thread. */
17841763
return func(self, args, flags);
17851764
}
17861765

@@ -2438,7 +2417,7 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
24382417
return NULL;
24392418
}
24402419

2441-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread() &&
2420+
if (self->thread_id != Tcl_GetCurrentThread() &&
24422421
!WaitForMainloop(self))
24432422
return NULL;
24442423

@@ -2450,7 +2429,7 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
24502429
Py_INCREF(self);
24512430
data->self = self;
24522431
data->func = Py_NewRef(func);
2453-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
2432+
if (self->thread_id != Tcl_GetCurrentThread()) {
24542433
err = 0; // init to make static analyzers happy
24552434

24562435
Tcl_Condition cond = NULL;
@@ -2507,7 +2486,7 @@ _tkinter_tkapp_deletecommand_impl(TkappObject *self, const char *name)
25072486

25082487
TRACE(self, ("((sss))", "rename", name, ""));
25092488

2510-
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
2489+
if (self->thread_id != Tcl_GetCurrentThread()) {
25112490
err = 0; // init to make static analyzers happy
25122491

25132492
Tcl_Condition cond = NULL;
@@ -2836,8 +2815,6 @@ static PyObject *
28362815
_tkinter_tkapp_mainloop_impl(TkappObject *self, int threshold)
28372816
/*[clinic end generated code: output=0ba8eabbe57841b0 input=036bcdcf03d5eca0]*/
28382817
{
2839-
PyThreadState *tstate = PyThreadState_Get();
2840-
28412818
CHECK_TCL_APPARTMENT(self);
28422819
self->dispatching = 1;
28432820

@@ -2848,23 +2825,10 @@ _tkinter_tkapp_mainloop_impl(TkappObject *self, int threshold)
28482825
{
28492826
int result;
28502827

2851-
if (self->threaded) {
2852-
/* Allow other Python threads to run. */
2853-
ENTER_TCL
2854-
result = Tcl_DoOneEvent(0);
2855-
LEAVE_TCL
2856-
}
2857-
else {
2858-
Py_BEGIN_ALLOW_THREADS
2859-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
2860-
tcl_tstate = tstate;
2861-
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
2862-
tcl_tstate = NULL;
2863-
if(tcl_lock)PyThread_release_lock(tcl_lock);
2864-
if (result == 0)
2865-
Sleep(Tkinter_busywaitinterval);
2866-
Py_END_ALLOW_THREADS
2867-
}
2828+
/* Allow other Python threads to run. */
2829+
ENTER_TCL
2830+
result = Tcl_DoOneEvent(0);
2831+
LEAVE_TCL
28682832

28692833
if (PyErr_CheckSignals() != 0) {
28702834
self->dispatching = 0;
@@ -3361,13 +3325,11 @@ EventHook(void)
33613325
}
33623326
#endif
33633327
Py_BEGIN_ALLOW_THREADS
3364-
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
33653328
tcl_tstate = event_tstate;
33663329

33673330
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
33683331

33693332
tcl_tstate = NULL;
3370-
if(tcl_lock)PyThread_release_lock(tcl_lock);
33713333
if (result == 0)
33723334
Sleep(Tkinter_busywaitinterval);
33733335
Py_END_ALLOW_THREADS
@@ -3452,9 +3414,11 @@ PyInit__tkinter(void)
34523414
{
34533415
PyObject *m, *uexe, *cexe;
34543416

3455-
tcl_lock = PyThread_allocate_lock();
3456-
if (tcl_lock == NULL)
3457-
return NULL;
3417+
if (_check_tcl_threaded() == 0) {
3418+
PyErr_SetString(PyExc_ImportError,
3419+
"Tcl must be compiled with thread support");
3420+
return 0;
3421+
}
34583422

34593423
m = PyModule_Create(&_tkintermodule);
34603424
if (m == NULL)

0 commit comments

Comments
 (0)