Skip to content

Commit 0ec39ff

Browse files
[3.12] 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). (cherry-picked from commit f5198b0)
1 parent e19af2c commit 0ec39ff

File tree

8 files changed

+151
-68
lines changed

8 files changed

+151
-68
lines changed

Include/cpython/pystate.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ struct _ts {
143143
/* padding to align to 4 bytes */
144144
unsigned int :24;
145145
} _status;
146+
#ifdef Py_BUILD_CORE
147+
# define _PyThreadState_WHENCE_NOTSET -1
148+
# define _PyThreadState_WHENCE_UNKNOWN 0
149+
# define _PyThreadState_WHENCE_INTERP 1
150+
# define _PyThreadState_WHENCE_THREADING 2
151+
# define _PyThreadState_WHENCE_GILSTATE 3
152+
# define _PyThreadState_WHENCE_EXEC 4
153+
#endif
154+
int _whence;
146155

147156
int py_recursion_remaining;
148157
int py_recursion_limit;

Include/internal/pycore_pystate.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ _Py_IsMainInterpreterFinalizing(PyInterpreterState *interp)
4444
PyAPI_FUNC(int) _PyInterpreterState_SetRunningMain(PyInterpreterState *);
4545
PyAPI_FUNC(void) _PyInterpreterState_SetNotRunningMain(PyInterpreterState *);
4646
PyAPI_FUNC(int) _PyInterpreterState_IsRunningMain(PyInterpreterState *);
47+
PyAPI_FUNC(int) _PyInterpreterState_FailIfRunningMain(PyInterpreterState *);
4748

4849

4950
static inline const PyConfig *
@@ -132,7 +133,9 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) {
132133

133134
// PyThreadState functions
134135

135-
PyAPI_FUNC(PyThreadState *) _PyThreadState_New(PyInterpreterState *interp);
136+
PyAPI_FUNC(PyThreadState *) _PyThreadState_New(
137+
PyInterpreterState *interp,
138+
int whence);
136139
PyAPI_FUNC(void) _PyThreadState_Bind(PyThreadState *tstate);
137140
// We keep this around exclusively for stable ABI compatibility.
138141
PyAPI_FUNC(void) _PyThreadState_Init(

Include/internal/pycore_runtime_init.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ extern PyTypeObject _PyExc_MemoryError;
127127

128128
#define _PyThreadState_INIT \
129129
{ \
130+
._whence = _PyThreadState_WHENCE_NOTSET, \
130131
.py_recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
131132
.context_ver = 1, \
132133
}

Lib/threading.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,8 +1585,11 @@ def _shutdown():
15851585
# The main thread isn't finished yet, so its thread state lock can't
15861586
# have been released.
15871587
assert tlock is not None
1588-
assert tlock.locked()
1589-
tlock.release()
1588+
if tlock.locked():
1589+
# It should have been released already by
1590+
# _PyInterpreterState_SetNotRunningMain(), but there may be
1591+
# embedders that aren't calling that yet.
1592+
tlock.release()
15901593
_main_thread._stop()
15911594
else:
15921595
# bpo-1596321: _shutdown() must be called in the main thread.

Modules/_threadmodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1203,7 +1203,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
12031203
if (boot == NULL) {
12041204
return PyErr_NoMemory();
12051205
}
1206-
boot->tstate = _PyThreadState_New(interp);
1206+
boot->tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_THREADING);
12071207
if (boot->tstate == NULL) {
12081208
PyMem_RawFree(boot);
12091209
if (!PyErr_Occurred()) {

Modules/_xxsubinterpretersmodule.c

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ _sharedns_apply(_sharedns *shared, PyObject *ns)
250250
// of the exception in the calling interpreter.
251251

252252
typedef struct _sharedexception {
253+
PyInterpreterState *interp;
254+
#define ERR_NOT_SET 0
255+
#define ERR_NO_MEMORY 1
256+
#define ERR_ALREADY_RUNNING 2
257+
int code;
253258
const char *name;
254259
const char *msg;
255260
} _sharedexception;
@@ -271,14 +276,26 @@ _sharedexception_clear(_sharedexception *exc)
271276
}
272277

273278
static const char *
274-
_sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
279+
_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
275280
{
281+
if (sharedexc->interp == NULL) {
282+
sharedexc->interp = PyInterpreterState_Get();
283+
}
284+
285+
if (code != ERR_NOT_SET) {
286+
assert(exc == NULL);
287+
assert(code > 0);
288+
sharedexc->code = code;
289+
return NULL;
290+
}
291+
276292
assert(exc != NULL);
277293
const char *failure = NULL;
278294

279295
PyObject *nameobj = PyUnicode_FromFormat("%S", Py_TYPE(exc));
280296
if (nameobj == NULL) {
281297
failure = "unable to format exception type name";
298+
code = ERR_NO_MEMORY;
282299
goto error;
283300
}
284301
sharedexc->name = _copy_raw_string(nameobj);
@@ -289,13 +306,15 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
289306
} else {
290307
failure = "unable to encode and copy exception type name";
291308
}
309+
code = ERR_NO_MEMORY;
292310
goto error;
293311
}
294312

295313
if (exc != NULL) {
296314
PyObject *msgobj = PyUnicode_FromFormat("%S", exc);
297315
if (msgobj == NULL) {
298316
failure = "unable to format exception message";
317+
code = ERR_NO_MEMORY;
299318
goto error;
300319
}
301320
sharedexc->msg = _copy_raw_string(msgobj);
@@ -306,6 +325,7 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
306325
} else {
307326
failure = "unable to encode and copy exception message";
308327
}
328+
code = ERR_NO_MEMORY;
309329
goto error;
310330
}
311331
}
@@ -316,14 +336,18 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc)
316336
assert(failure != NULL);
317337
PyErr_Clear();
318338
_sharedexception_clear(sharedexc);
319-
*sharedexc = no_exception;
339+
*sharedexc = (_sharedexception){
340+
.interp = sharedexc->interp,
341+
.code = code,
342+
};
320343
return failure;
321344
}
322345

323346
static void
324347
_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
325348
{
326349
if (exc->name != NULL) {
350+
assert(exc->code == ERR_NOT_SET);
327351
if (exc->msg != NULL) {
328352
PyErr_Format(wrapperclass, "%s: %s", exc->name, exc->msg);
329353
}
@@ -332,9 +356,19 @@ _sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
332356
}
333357
}
334358
else if (exc->msg != NULL) {
359+
assert(exc->code == ERR_NOT_SET);
335360
PyErr_SetString(wrapperclass, exc->msg);
336361
}
362+
else if (exc->code == ERR_NO_MEMORY) {
363+
PyErr_NoMemory();
364+
}
365+
else if (exc->code == ERR_ALREADY_RUNNING) {
366+
assert(exc->interp != NULL);
367+
assert(_PyInterpreterState_IsRunningMain(exc->interp));
368+
_PyInterpreterState_FailIfRunningMain(exc->interp);
369+
}
337370
else {
371+
assert(exc->code == ERR_NOT_SET);
338372
PyErr_SetNone(wrapperclass);
339373
}
340374
}
@@ -370,9 +404,16 @@ static int
370404
_run_script(PyInterpreterState *interp, const char *codestr,
371405
_sharedns *shared, _sharedexception *sharedexc)
372406
{
407+
int errcode = ERR_NOT_SET;
408+
373409
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
374-
// We skip going through the shared exception.
375-
return -1;
410+
assert(PyErr_Occurred());
411+
// In the case where we didn't switch interpreters, it would
412+
// be more efficient to leave the exception in place and return
413+
// immediately. However, life is simpler if we don't.
414+
PyErr_Clear();
415+
errcode = ERR_ALREADY_RUNNING;
416+
goto error;
376417
}
377418

378419
PyObject *excval = NULL;
@@ -411,16 +452,17 @@ _run_script(PyInterpreterState *interp, const char *codestr,
411452

412453
error:
413454
excval = PyErr_GetRaisedException();
414-
const char *failure = _sharedexception_bind(excval, sharedexc);
455+
const char *failure = _sharedexception_bind(excval, errcode, sharedexc);
415456
if (failure != NULL) {
416457
fprintf(stderr,
417458
"RunFailedError: script raised an uncaught exception (%s)",
418459
failure);
419-
PyErr_Clear();
420460
}
421461
Py_XDECREF(excval);
462+
if (errcode != ERR_ALREADY_RUNNING) {
463+
_PyInterpreterState_SetNotRunningMain(interp);
464+
}
422465
assert(!PyErr_Occurred());
423-
_PyInterpreterState_SetNotRunningMain(interp);
424466
return -1;
425467
}
426468

@@ -429,6 +471,7 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp,
429471
const char *codestr, PyObject *shareables)
430472
{
431473
module_state *state = get_module_state(mod);
474+
assert(state != NULL);
432475

433476
_sharedns *shared = _get_shared_ns(shareables);
434477
if (shared == NULL && PyErr_Occurred()) {
@@ -437,50 +480,30 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp,
437480

438481
// Switch to interpreter.
439482
PyThreadState *save_tstate = NULL;
483+
PyThreadState *tstate = NULL;
440484
if (interp != PyInterpreterState_Get()) {
441-
// XXX gh-109860: Using the "head" thread isn't strictly correct.
442-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
443-
assert(tstate != NULL);
444-
// Hack (until gh-109860): The interpreter's initial thread state
445-
// is least likely to break.
446-
while(tstate->next != NULL) {
447-
tstate = tstate->next;
448-
}
449-
// We must do this check before switching interpreters, so any
450-
// exception gets raised in the right one.
451-
// XXX gh-109860: Drop this redundant check once we stop
452-
// re-using tstates that might already be in use.
453-
if (_PyInterpreterState_IsRunningMain(interp)) {
454-
PyErr_SetString(PyExc_RuntimeError,
455-
"interpreter already running");
456-
if (shared != NULL) {
457-
_sharedns_free(shared);
458-
}
459-
return -1;
460-
}
485+
tstate = PyThreadState_New(interp);
486+
tstate->_whence = _PyThreadState_WHENCE_EXEC;
461487
// XXX Possible GILState issues?
462488
save_tstate = PyThreadState_Swap(tstate);
463489
}
464490

465491
// Run the script.
466-
_sharedexception exc = {NULL, NULL};
492+
_sharedexception exc = (_sharedexception){ .interp = interp };
467493
int result = _run_script(interp, codestr, shared, &exc);
468494

469495
// Switch back.
470496
if (save_tstate != NULL) {
497+
PyThreadState_Clear(tstate);
471498
PyThreadState_Swap(save_tstate);
499+
PyThreadState_Delete(tstate);
472500
}
473501

474502
// Propagate any exception out to the caller.
475-
if (exc.name != NULL) {
476-
assert(state != NULL);
503+
if (result < 0) {
504+
assert(!PyErr_Occurred());
477505
_sharedexception_apply(&exc, state->RunFailedError);
478-
}
479-
else if (result != 0) {
480-
if (!PyErr_Occurred()) {
481-
// We were unable to allocate a shared exception.
482-
PyErr_NoMemory();
483-
}
506+
assert(PyErr_Occurred());
484507
}
485508

486509
if (shared != NULL) {
@@ -510,6 +533,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
510533
const PyInterpreterConfig config = isolated
511534
? (PyInterpreterConfig)_PyInterpreterConfig_INIT
512535
: (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT;
536+
513537
// XXX Possible GILState issues?
514538
PyThreadState *tstate = NULL;
515539
PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config);
@@ -525,6 +549,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
525549
return NULL;
526550
}
527551
assert(tstate != NULL);
552+
528553
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
529554
PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
530555
if (idobj == NULL) {
@@ -534,6 +559,10 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
534559
PyThreadState_Swap(save_tstate);
535560
return NULL;
536561
}
562+
563+
PyThreadState_Clear(tstate);
564+
PyThreadState_Delete(tstate);
565+
537566
_PyInterpreterState_RequireIDRef(interp, 1);
538567
return idobj;
539568
}
@@ -581,14 +610,8 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
581610
}
582611

583612
// Destroy the interpreter.
584-
// XXX gh-109860: Using the "head" thread isn't strictly correct.
585-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
586-
assert(tstate != NULL);
587-
// Hack (until gh-109860): The interpreter's initial thread state
588-
// is least likely to break.
589-
while(tstate->next != NULL) {
590-
tstate = tstate->next;
591-
}
613+
PyThreadState *tstate = PyThreadState_New(interp);
614+
tstate->_whence = _PyThreadState_WHENCE_INTERP;
592615
// XXX Possible GILState issues?
593616
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
594617
Py_EndInterpreter(tstate);

Python/pylifecycle.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,8 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
650650
return status;
651651
}
652652

653-
PyThreadState *tstate = _PyThreadState_New(interp);
653+
PyThreadState *tstate = _PyThreadState_New(interp,
654+
_PyThreadState_WHENCE_INTERP);
654655
if (tstate == NULL) {
655656
return _PyStatus_ERR("can't make first thread");
656657
}
@@ -2025,7 +2026,8 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
20252026
return _PyStatus_OK();
20262027
}
20272028

2028-
PyThreadState *tstate = _PyThreadState_New(interp);
2029+
PyThreadState *tstate = _PyThreadState_New(interp,
2030+
_PyThreadState_WHENCE_INTERP);
20292031
if (tstate == NULL) {
20302032
PyInterpreterState_Delete(interp);
20312033
*tstate_p = NULL;

0 commit comments

Comments
 (0)