@@ -44,6 +44,7 @@ get_thread_state(PyObject *module)
4444
4545typedef struct {
4646 PyObject_HEAD
47+ struct llist_node node ; // linked list node (see _pythread_runtime_state)
4748 PyThread_ident_t ident ;
4849 PyThread_handle_t handle ;
4950 char joinable ;
@@ -59,13 +60,26 @@ new_thread_handle(thread_module_state* state)
5960 self -> ident = 0 ;
6061 self -> handle = 0 ;
6162 self -> joinable = 0 ;
63+
64+ HEAD_LOCK (& _PyRuntime );
65+ llist_insert_tail (& _PyRuntime .threads .handles , & self -> node );
66+ HEAD_UNLOCK (& _PyRuntime );
67+
6268 return self ;
6369}
6470
6571static void
6672ThreadHandle_dealloc (ThreadHandleObject * self )
6773{
6874 PyObject * tp = (PyObject * ) Py_TYPE (self );
75+
76+ // Remove ourself from the global list of handles
77+ HEAD_LOCK (& _PyRuntime );
78+ if (self -> node .next != NULL ) {
79+ llist_remove (& self -> node );
80+ }
81+ HEAD_UNLOCK (& _PyRuntime );
82+
6983 if (self -> joinable ) {
7084 int ret = PyThread_detach_thread (self -> handle );
7185 if (ret ) {
@@ -77,6 +91,28 @@ ThreadHandle_dealloc(ThreadHandleObject *self)
7791 Py_DECREF (tp );
7892}
7993
94+ void
95+ _PyThread_AfterFork (struct _pythread_runtime_state * state )
96+ {
97+ // gh-115035: We mark ThreadHandles as not joinable early in the child's
98+ // after-fork handler. We do this before calling any Python code to ensure
99+ // that it happens before any ThreadHandles are deallocated, such as by a
100+ // GC cycle.
101+ PyThread_ident_t current = PyThread_get_thread_ident_ex ();
102+
103+ struct llist_node * node ;
104+ llist_for_each_safe (node , & state -> handles ) {
105+ ThreadHandleObject * hobj = llist_data (node , ThreadHandleObject , node );
106+ if (hobj -> ident == current ) {
107+ continue ;
108+ }
109+
110+ // Disallow calls to detach() and join() as they could crash.
111+ hobj -> joinable = 0 ;
112+ llist_remove (node );
113+ }
114+ }
115+
80116static PyObject *
81117ThreadHandle_repr (ThreadHandleObject * self )
82118{
@@ -91,21 +127,6 @@ ThreadHandle_get_ident(ThreadHandleObject *self, void *ignored)
91127}
92128
93129
94- static PyObject *
95- ThreadHandle_after_fork_alive (ThreadHandleObject * self , void * ignored )
96- {
97- PyThread_update_thread_after_fork (& self -> ident , & self -> handle );
98- Py_RETURN_NONE ;
99- }
100-
101- static PyObject *
102- ThreadHandle_after_fork_dead (ThreadHandleObject * self , void * ignored )
103- {
104- // Disallow calls to detach() and join() as they could crash.
105- self -> joinable = 0 ;
106- Py_RETURN_NONE ;
107- }
108-
109130static PyObject *
110131ThreadHandle_detach (ThreadHandleObject * self , void * ignored )
111132{
@@ -157,8 +178,6 @@ static PyGetSetDef ThreadHandle_getsetlist[] = {
157178
158179static PyMethodDef ThreadHandle_methods [] =
159180{
160- {"after_fork_alive" , (PyCFunction )ThreadHandle_after_fork_alive , METH_NOARGS },
161- {"after_fork_dead" , (PyCFunction )ThreadHandle_after_fork_dead , METH_NOARGS },
162181 {"detach" , (PyCFunction )ThreadHandle_detach , METH_NOARGS },
163182 {"join" , (PyCFunction )ThreadHandle_join , METH_NOARGS },
164183 {0 , 0 }
0 commit comments