Skip to content

Commit a24dc2e

Browse files
committed
pystate: keep track of attached vs. detached state
This adds a "status" field to each PyThreadState. The GC status will be useful for implementing stop-the-world garbage collection.
1 parent 17f2325 commit a24dc2e

File tree

7 files changed

+132
-15
lines changed

7 files changed

+132
-15
lines changed

Include/ceval.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ PyAPI_FUNC(PyObject *) PyEval_EvalCodeEx(PyObject *co,
1616
PyObject *const *kwds, int kwdc,
1717
PyObject *const *defs, int defc,
1818
PyObject *kwdefs, PyObject *closure);
19+
/* Interface to random parts in ceval.c */
1920

2021
/* PyEval_CallObjectWithKeywords(), PyEval_CallObject(), PyEval_CallFunction
2122
* and PyEval_CallMethod are deprecated. Since they are officially part of the

Include/cpython/pystate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ struct _ts {
114114
PyThreadState *next;
115115
PyInterpreterState *interp;
116116

117+
/* thread status (attached, detached, gc) */
118+
int status;
119+
117120
/* Has been initialized to a safe state.
118121
119122
In order to be effective, this must be set to 0 during or right
@@ -164,6 +167,8 @@ struct _ts {
164167
*/
165168
unsigned long native_thread_id;
166169

170+
uintptr_t fast_thread_id; /* Thread id used for object ownership */
171+
167172
int trash_delete_nesting;
168173
PyObject *trash_delete_later;
169174

Include/internal/pycore_ceval.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func);
152152

153153
extern int _Py_HandlePending(PyThreadState *tstate);
154154

155+
extern void _PyEval_TakeGIL(PyThreadState *tstate);
156+
extern void _PyEval_DropGIL(PyThreadState *tstate);
157+
155158

156159

157160
#ifdef __cplusplus

Include/internal/pycore_pystate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ extern "C" {
1010

1111
#include "pycore_runtime.h" /* PyRuntimeState */
1212

13+
enum _threadstatus {
14+
_Py_THREAD_DETACHED = 0,
15+
_Py_THREAD_ATTACHED = 1,
16+
_Py_THREAD_GC = 2
17+
};
1318

1419
/* Check if the current thread is the main thread.
1520
Use _Py_IsMainInterpreter() to check if it's the main interpreter. */

Include/object.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,35 @@ PyAPI_FUNC(void) _Py_NegativeRefcount(const char *filename, int lineno,
500500

501501
PyAPI_FUNC(void) _Py_Dealloc(PyObject *);
502502

503+
static inline uintptr_t
504+
_Py_ThreadId(void)
505+
{
506+
// copied from mimalloc-internal.h
507+
uintptr_t tid;
508+
#if defined(_MSC_VER) && defined(_M_X64)
509+
tid = __readgsqword(48);
510+
#elif defined(_MSC_VER) && defined(_M_IX86)
511+
tid = __readfsdword(24);
512+
#elif defined(_MSC_VER) && defined(_M_ARM64)
513+
tid = __getReg(18);
514+
#elif defined(__i386__)
515+
__asm__("movl %%gs:0, %0" : "=r" (tid)); // 32-bit always uses GS
516+
#elif defined(__MACH__) && defined(__x86_64__)
517+
__asm__("movq %%gs:0, %0" : "=r" (tid)); // x86_64 macOSX uses GS
518+
#elif defined(__x86_64__)
519+
__asm__("movq %%fs:0, %0" : "=r" (tid)); // x86_64 Linux, BSD uses FS
520+
#elif defined(__arm__)
521+
__asm__ ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tid));
522+
#elif defined(__aarch64__) && defined(__APPLE__)
523+
__asm__ ("mrs %0, tpidrro_el0" : "=r" (tid));
524+
#elif defined(__aarch64__)
525+
__asm__ ("mrs %0, tpidr_el0" : "=r" (tid));
526+
#else
527+
# error "define _Py_ThreadId for this platform"
528+
#endif
529+
return tid;
530+
}
531+
503532
/*
504533
These are provided as conveniences to Python runtime embedders, so that
505534
they can have object code that is not dependent on Python compilation flags.

Python/ceval_gil.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,3 +1004,16 @@ _Py_HandlePending(PyThreadState *tstate)
10041004
return 0;
10051005
}
10061006

1007+
void
1008+
_PyEval_TakeGIL(PyThreadState *tstate)
1009+
{
1010+
_PyThreadState_SET(tstate);
1011+
take_gil(tstate);
1012+
}
1013+
1014+
void
1015+
_PyEval_DropGIL(PyThreadState *tstate)
1016+
{
1017+
_PyThreadState_SET(NULL);
1018+
_PyEval_ReleaseLock(tstate);
1019+
}

Python/pystate.c

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "pycore_pystate.h" // _PyThreadState_GET()
1414
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
1515
#include "pycore_sysmodule.h"
16+
#include "pyatomic.h"
1617

1718
/* --------------------------------------------------------------------------
1819
CAUTION
@@ -240,6 +241,30 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
240241
static void _PyGILState_NoteThreadState(
241242
struct _gilstate_runtime_state *gilstate, PyThreadState* tstate);
242243

244+
int
245+
_PyThreadState_GetStatus(PyThreadState *tstate)
246+
{
247+
return _Py_atomic_load_int_relaxed(&tstate->status);
248+
}
249+
250+
static int
251+
_PyThreadState_Attach(PyThreadState *tstate)
252+
{
253+
if (_Py_atomic_compare_exchange_int(
254+
&tstate->status,
255+
_Py_THREAD_DETACHED,
256+
_Py_THREAD_ATTACHED)) {
257+
return 1;
258+
}
259+
return 0;
260+
}
261+
262+
static void
263+
_PyThreadState_Detach(PyThreadState *tstate)
264+
{
265+
_Py_atomic_store_int(&tstate->status, _Py_THREAD_DETACHED);
266+
}
267+
243268
PyStatus
244269
_PyInterpreterState_Enable(_PyRuntimeState *runtime)
245270
{
@@ -517,13 +542,14 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
517542
{
518543
_PyRuntimeState *runtime = interp->runtime;
519544
struct pyinterpreters *interpreters = &runtime->interpreters;
520-
zapthreads(interp, 0);
521-
522-
_PyEval_FiniState(&interp->ceval);
523545

524546
/* Delete current thread. After this, many C API calls become crashy. */
525547
_PyThreadState_Swap(&runtime->gilstate, NULL);
526548

549+
zapthreads(interp, 0);
550+
551+
_PyEval_FiniState(&interp->ceval);
552+
527553
HEAD_LOCK(runtime);
528554
PyInterpreterState **p;
529555
for (p = &interpreters->head; ; p = &(*p)->next) {
@@ -910,6 +936,7 @@ _PyThreadState_Init(PyThreadState *tstate)
910936
void
911937
_PyThreadState_SetCurrent(PyThreadState *tstate)
912938
{
939+
tstate->fast_thread_id = _Py_ThreadId();
913940
_PyGILState_NoteThreadState(&tstate->interp->runtime->gilstate, tstate);
914941
}
915942

@@ -1094,15 +1121,25 @@ PyThreadState_Clear(PyThreadState *tstate)
10941121
/* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */
10951122
static void
10961123
tstate_delete_common(PyThreadState *tstate,
1097-
struct _gilstate_runtime_state *gilstate)
1124+
struct _gilstate_runtime_state *gilstate,
1125+
int is_current)
10981126
{
1127+
assert(is_current ? tstate->status == _Py_THREAD_ATTACHED
1128+
: tstate->status != _Py_THREAD_ATTACHED);
1129+
10991130
_Py_EnsureTstateNotNULL(tstate);
11001131
PyInterpreterState *interp = tstate->interp;
11011132
if (interp == NULL) {
11021133
Py_FatalError("NULL interpreter");
11031134
}
1104-
_PyRuntimeState *runtime = interp->runtime;
11051135

1136+
if (gilstate->autoInterpreterState &&
1137+
PyThread_tss_get(&gilstate->autoTSSkey) == tstate)
1138+
{
1139+
PyThread_tss_set(&gilstate->autoTSSkey, NULL);
1140+
}
1141+
1142+
_PyRuntimeState *runtime = interp->runtime;
11061143
HEAD_LOCK(runtime);
11071144
if (tstate->prev) {
11081145
tstate->prev->next = tstate->next;
@@ -1115,10 +1152,8 @@ tstate_delete_common(PyThreadState *tstate,
11151152
}
11161153
HEAD_UNLOCK(runtime);
11171154

1118-
if (gilstate->autoInterpreterState &&
1119-
PyThread_tss_get(&gilstate->autoTSSkey) == tstate)
1120-
{
1121-
PyThread_tss_set(&gilstate->autoTSSkey, NULL);
1155+
if (is_current) {
1156+
_PyThreadState_SET(NULL);
11221157
}
11231158
_PyStackChunk *chunk = tstate->datastack_chunk;
11241159
tstate->datastack_chunk = NULL;
@@ -1138,7 +1173,7 @@ _PyThreadState_Delete(PyThreadState *tstate, int check_current)
11381173
_Py_FatalErrorFormat(__func__, "tstate %p is still current", tstate);
11391174
}
11401175
}
1141-
tstate_delete_common(tstate, gilstate);
1176+
tstate_delete_common(tstate, gilstate, 0);
11421177
free_threadstate(tstate);
11431178
}
11441179

@@ -1155,7 +1190,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
11551190
{
11561191
_Py_EnsureTstateNotNULL(tstate);
11571192
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
1158-
tstate_delete_common(tstate, gilstate);
1193+
tstate_delete_common(tstate, gilstate, 1);
11591194
_PyRuntimeGILState_SetThreadState(gilstate, NULL);
11601195
_PyEval_ReleaseLock(tstate);
11611196
free_threadstate(tstate);
@@ -1230,9 +1265,36 @@ PyThreadState_Get(void)
12301265
PyThreadState *
12311266
_PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *newts)
12321267
{
1233-
PyThreadState *oldts = _PyRuntimeGILState_GetThreadState(gilstate);
1268+
PyThreadState *oldts = _Py_current_tstate;
1269+
1270+
#if defined(Py_DEBUG)
1271+
// The new thread-state should correspond to the current native thread
1272+
// XXX: breaks subinterpreter tests
1273+
if (newts && newts->fast_thread_id != _Py_ThreadId()) {
1274+
Py_FatalError("Invalid thread state for this thread");
1275+
}
1276+
#endif
1277+
1278+
if (oldts != NULL) {
1279+
int status = _Py_atomic_load_int(&oldts->status);
1280+
assert(status == _Py_THREAD_ATTACHED || status == _Py_THREAD_GC);
1281+
1282+
if (status == _Py_THREAD_ATTACHED) {
1283+
_PyThreadState_Detach(oldts);
1284+
}
1285+
}
1286+
1287+
_Py_current_tstate = newts;
1288+
1289+
if (newts) {
1290+
int attached = _PyThreadState_Attach(newts);
1291+
if (!attached) {
1292+
// _PyThreadState_GC_Park(newts);
1293+
}
1294+
1295+
assert(_Py_atomic_load_int(&newts->status) == _Py_THREAD_ATTACHED);
1296+
}
12341297

1235-
_PyRuntimeGILState_SetThreadState(gilstate, newts);
12361298
/* It should not be possible for more than one thread state
12371299
to be used for a thread. Check this the best we can in debug
12381300
builds.
@@ -1243,8 +1305,7 @@ _PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *new
12431305
to it, we need to ensure errno doesn't change.
12441306
*/
12451307
int err = errno;
1246-
PyThreadState *check = _PyGILState_GetThisThreadState(gilstate);
1247-
if (check && check->interp == newts->interp && check != newts)
1308+
if (oldts && oldts->interp == newts->interp && oldts != newts)
12481309
Py_FatalError("Invalid thread state for this thread");
12491310
errno = err;
12501311
}

0 commit comments

Comments
 (0)