Skip to content

Commit f5198b0

Browse files
gh-109860: Use a New Thread State When Switching Interpreters, When Necessary (gh-110245)
In a few places we switch to another interpreter without knowing if it has a thread state associated with the current thread. For the main interpreter there wasn't much of a problem, but for subinterpreters we were *mostly* okay re-using the tstate created with the interpreter (located via PyInterpreterState_ThreadHead()). There was a good chance that tstate wasn't actually in use by another thread. However, there are no guarantees of that. Furthermore, re-using an already used tstate is currently fragile. To address this, now we create a new thread state in each of those places and use it. One consequence of this change is that PyInterpreterState_ThreadHead() may not return NULL (though that won't happen for the main interpreter).
1 parent 4227bfa commit f5198b0

File tree

8 files changed

+151
-68
lines changed

8 files changed

+151
-68
lines changed

Include/cpython/pystate.h

+9
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ struct _ts {
9292
/* padding to align to 4 bytes */
9393
unsigned int :24;
9494
} _status;
95+
#ifdef Py_BUILD_CORE
96+
# define _PyThreadState_WHENCE_NOTSET -1
97+
# define _PyThreadState_WHENCE_UNKNOWN 0
98+
# define _PyThreadState_WHENCE_INTERP 1
99+
# define _PyThreadState_WHENCE_THREADING 2
100+
# define _PyThreadState_WHENCE_GILSTATE 3
101+
# define _PyThreadState_WHENCE_EXEC 4
102+
#endif
103+
int _whence;
95104

96105
int py_recursion_remaining;
97106
int py_recursion_limit;

Include/internal/pycore_pystate.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ _Py_IsMainInterpreterFinalizing(PyInterpreterState *interp)
4848
PyAPI_FUNC(int) _PyInterpreterState_SetRunningMain(PyInterpreterState *);
4949
PyAPI_FUNC(void) _PyInterpreterState_SetNotRunningMain(PyInterpreterState *);
5050
PyAPI_FUNC(int) _PyInterpreterState_IsRunningMain(PyInterpreterState *);
51+
PyAPI_FUNC(int) _PyInterpreterState_FailIfRunningMain(PyInterpreterState *);
5152

5253

5354
static inline const PyConfig *
@@ -139,7 +140,9 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) {
139140

140141
// PyThreadState functions
141142

142-
extern PyThreadState * _PyThreadState_New(PyInterpreterState *interp);
143+
extern PyThreadState * _PyThreadState_New(
144+
PyInterpreterState *interp,
145+
int whence);
143146
extern void _PyThreadState_Bind(PyThreadState *tstate);
144147
extern void _PyThreadState_DeleteExcept(PyThreadState *tstate);
145148

Include/internal/pycore_runtime_init.h

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ extern PyTypeObject _PyExc_MemoryError;
185185

186186
#define _PyThreadState_INIT \
187187
{ \
188+
._whence = _PyThreadState_WHENCE_NOTSET, \
188189
.py_recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
189190
.context_ver = 1, \
190191
}

Lib/threading.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1593,8 +1593,11 @@ def _shutdown():
15931593
# The main thread isn't finished yet, so its thread state lock can't
15941594
# have been released.
15951595
assert tlock is not None
1596-
assert tlock.locked()
1597-
tlock.release()
1596+
if tlock.locked():
1597+
# It should have been released already by
1598+
# _PyInterpreterState_SetNotRunningMain(), but there may be
1599+
# embedders that aren't calling that yet.
1600+
tlock.release()
15981601
_main_thread._stop()
15991602
else:
16001603
# bpo-1596321: _shutdown() must be called in the main thread.

Modules/_threadmodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
12051205
if (boot == NULL) {
12061206
return PyErr_NoMemory();
12071207
}
1208-
boot->tstate = _PyThreadState_New(interp);
1208+
boot->tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_THREADING);
12091209
if (boot->tstate == NULL) {
12101210
PyMem_RawFree(boot);
12111211
if (!PyErr_Occurred()) {

Modules/_xxsubinterpretersmodule.c

+67-44
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ _sharedns_apply(_sharedns *shared, PyObject *ns)
242242
// of the exception in the calling interpreter.
243243

244244
typedef struct _sharedexception {
245+
PyInterpreterState *interp;
246+
#define ERR_NOT_SET 0
247+
#define ERR_NO_MEMORY 1
248+
#define ERR_ALREADY_RUNNING 2
249+
int code;
245250
const char *name;
246251
const char *msg;
247252
} _sharedexception;
@@ -263,14 +268,26 @@ _sharedexception_clear(_sharedexception *exc)
263268
}
264269

265270
static const char *
266-
_sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
271+
_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
267272
{
273+
if (sharedexc->interp == NULL) {
274+
sharedexc->interp = PyInterpreterState_Get();
275+
}
276+
277+
if (code != ERR_NOT_SET) {
278+
assert(exc == NULL);
279+
assert(code > 0);
280+
sharedexc->code = code;
281+
return NULL;
282+
}
283+
268284
assert(exc != NULL);
269285
const char *failure = NULL;
270286

271287
PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name);
272288
if (nameobj == NULL) {
273289
failure = "unable to format exception type name";
290+
code = ERR_NO_MEMORY;
274291
goto error;
275292
}
276293
sharedexc->name = _copy_raw_string(nameobj);
@@ -281,13 +298,15 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
281298
} else {
282299
failure = "unable to encode and copy exception type name";
283300
}
301+
code = ERR_NO_MEMORY;
284302
goto error;
285303
}
286304

287305
if (exc != NULL) {
288306
PyObject *msgobj = PyUnicode_FromFormat("%S", exc);
289307
if (msgobj == NULL) {
290308
failure = "unable to format exception message";
309+
code = ERR_NO_MEMORY;
291310
goto error;
292311
}
293312
sharedexc->msg = _copy_raw_string(msgobj);
@@ -298,6 +317,7 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
298317
} else {
299318
failure = "unable to encode and copy exception message";
300319
}
320+
code = ERR_NO_MEMORY;
301321
goto error;
302322
}
303323
}
@@ -308,14 +328,18 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
308328
assert(failure != NULL);
309329
PyErr_Clear();
310330
_sharedexception_clear(sharedexc);
311-
*sharedexc = no_exception;
331+
*sharedexc = (_sharedexception){
332+
.interp = sharedexc->interp,
333+
.code = code,
334+
};
312335
return failure;
313336
}
314337

315338
static void
316339
_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
317340
{
318341
if (exc->name != NULL) {
342+
assert(exc->code == ERR_NOT_SET);
319343
if (exc->msg != NULL) {
320344
PyErr_Format(wrapperclass, "%s: %s", exc->name, exc->msg);
321345
}
@@ -324,9 +348,19 @@ _sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
324348
}
325349
}
326350
else if (exc->msg != NULL) {
351+
assert(exc->code == ERR_NOT_SET);
327352
PyErr_SetString(wrapperclass, exc->msg);
328353
}
354+
else if (exc->code == ERR_NO_MEMORY) {
355+
PyErr_NoMemory();
356+
}
357+
else if (exc->code == ERR_ALREADY_RUNNING) {
358+
assert(exc->interp != NULL);
359+
assert(_PyInterpreterState_IsRunningMain(exc->interp));
360+
_PyInterpreterState_FailIfRunningMain(exc->interp);
361+
}
329362
else {
363+
assert(exc->code == ERR_NOT_SET);
330364
PyErr_SetNone(wrapperclass);
331365
}
332366
}
@@ -362,9 +396,16 @@ static int
362396
_run_script(PyInterpreterState *interp, const char *codestr,
363397
_sharedns *shared, _sharedexception *sharedexc)
364398
{
399+
int errcode = ERR_NOT_SET;
400+
365401
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
366-
// We skip going through the shared exception.
367-
return -1;
402+
assert(PyErr_Occurred());
403+
// In the case where we didn't switch interpreters, it would
404+
// be more efficient to leave the exception in place and return
405+
// immediately. However, life is simpler if we don't.
406+
PyErr_Clear();
407+
errcode = ERR_ALREADY_RUNNING;
408+
goto error;
368409
}
369410

370411
PyObject *excval = NULL;
@@ -403,16 +444,17 @@ _run_script(PyInterpreterState *interp, const char *codestr,
403444

404445
error:
405446
excval = PyErr_GetRaisedException();
406-
const char *failure = _sharedexception_bind(excval, sharedexc);
447+
const char *failure = _sharedexception_bind(excval, errcode, sharedexc);
407448
if (failure != NULL) {
408449
fprintf(stderr,
409450
"RunFailedError: script raised an uncaught exception (%s)",
410451
failure);
411-
PyErr_Clear();
412452
}
413453
Py_XDECREF(excval);
454+
if (errcode != ERR_ALREADY_RUNNING) {
455+
_PyInterpreterState_SetNotRunningMain(interp);
456+
}
414457
assert(!PyErr_Occurred());
415-
_PyInterpreterState_SetNotRunningMain(interp);
416458
return -1;
417459
}
418460

@@ -421,6 +463,7 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp,
421463
const char *codestr, PyObject *shareables)
422464
{
423465
module_state *state = get_module_state(mod);
466+
assert(state != NULL);
424467

425468
_sharedns *shared = _get_shared_ns(shareables);
426469
if (shared == NULL && PyErr_Occurred()) {
@@ -429,50 +472,30 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp,
429472

430473
// Switch to interpreter.
431474
PyThreadState *save_tstate = NULL;
475+
PyThreadState *tstate = NULL;
432476
if (interp != PyInterpreterState_Get()) {
433-
// XXX gh-109860: Using the "head" thread isn't strictly correct.
434-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
435-
assert(tstate != NULL);
436-
// Hack (until gh-109860): The interpreter's initial thread state
437-
// is least likely to break.
438-
while(tstate->next != NULL) {
439-
tstate = tstate->next;
440-
}
441-
// We must do this check before switching interpreters, so any
442-
// exception gets raised in the right one.
443-
// XXX gh-109860: Drop this redundant check once we stop
444-
// re-using tstates that might already be in use.
445-
if (_PyInterpreterState_IsRunningMain(interp)) {
446-
PyErr_SetString(PyExc_RuntimeError,
447-
"interpreter already running");
448-
if (shared != NULL) {
449-
_sharedns_free(shared);
450-
}
451-
return -1;
452-
}
477+
tstate = PyThreadState_New(interp);
478+
tstate->_whence = _PyThreadState_WHENCE_EXEC;
453479
// XXX Possible GILState issues?
454480
save_tstate = PyThreadState_Swap(tstate);
455481
}
456482

457483
// Run the script.
458-
_sharedexception exc = {NULL, NULL};
484+
_sharedexception exc = (_sharedexception){ .interp = interp };
459485
int result = _run_script(interp, codestr, shared, &exc);
460486

461487
// Switch back.
462488
if (save_tstate != NULL) {
489+
PyThreadState_Clear(tstate);
463490
PyThreadState_Swap(save_tstate);
491+
PyThreadState_Delete(tstate);
464492
}
465493

466494
// Propagate any exception out to the caller.
467-
if (exc.name != NULL) {
468-
assert(state != NULL);
495+
if (result < 0) {
496+
assert(!PyErr_Occurred());
469497
_sharedexception_apply(&exc, state->RunFailedError);
470-
}
471-
else if (result != 0) {
472-
if (!PyErr_Occurred()) {
473-
// We were unable to allocate a shared exception.
474-
PyErr_NoMemory();
475-
}
498+
assert(PyErr_Occurred());
476499
}
477500

478501
if (shared != NULL) {
@@ -502,6 +525,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
502525
const PyInterpreterConfig config = isolated
503526
? (PyInterpreterConfig)_PyInterpreterConfig_INIT
504527
: (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT;
528+
505529
// XXX Possible GILState issues?
506530
PyThreadState *tstate = NULL;
507531
PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config);
@@ -517,6 +541,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
517541
return NULL;
518542
}
519543
assert(tstate != NULL);
544+
520545
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
521546
PyObject *idobj = PyInterpreterState_GetIDObject(interp);
522547
if (idobj == NULL) {
@@ -526,6 +551,10 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
526551
PyThreadState_Swap(save_tstate);
527552
return NULL;
528553
}
554+
555+
PyThreadState_Clear(tstate);
556+
PyThreadState_Delete(tstate);
557+
529558
_PyInterpreterState_RequireIDRef(interp, 1);
530559
return idobj;
531560
}
@@ -573,14 +602,8 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
573602
}
574603

575604
// Destroy the interpreter.
576-
// XXX gh-109860: Using the "head" thread isn't strictly correct.
577-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
578-
assert(tstate != NULL);
579-
// Hack (until gh-109860): The interpreter's initial thread state
580-
// is least likely to break.
581-
while(tstate->next != NULL) {
582-
tstate = tstate->next;
583-
}
605+
PyThreadState *tstate = PyThreadState_New(interp);
606+
tstate->_whence = _PyThreadState_WHENCE_INTERP;
584607
// XXX Possible GILState issues?
585608
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
586609
Py_EndInterpreter(tstate);

Python/pylifecycle.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,8 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
655655
return status;
656656
}
657657

658-
PyThreadState *tstate = _PyThreadState_New(interp);
658+
PyThreadState *tstate = _PyThreadState_New(interp,
659+
_PyThreadState_WHENCE_INTERP);
659660
if (tstate == NULL) {
660661
return _PyStatus_ERR("can't make first thread");
661662
}
@@ -2050,7 +2051,8 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
20502051
return _PyStatus_OK();
20512052
}
20522053

2053-
PyThreadState *tstate = _PyThreadState_New(interp);
2054+
PyThreadState *tstate = _PyThreadState_New(interp,
2055+
_PyThreadState_WHENCE_INTERP);
20542056
if (tstate == NULL) {
20552057
PyInterpreterState_Delete(interp);
20562058
*tstate_p = NULL;

0 commit comments

Comments
 (0)