diff --git a/Include/internal/pycore_pythread.h b/Include/internal/pycore_pythread.h
index d2e7cc2a206ced..8bfb89cd25a38e 100644
--- a/Include/internal/pycore_pythread.h
+++ b/Include/internal/pycore_pythread.h
@@ -78,7 +78,7 @@ struct _pythread_runtime_state {
} stubs;
#endif
- // Linked list of ThreadHandleObjects
+ // Linked list of struct _PyThread_handle_data
struct llist_node handles;
};
@@ -153,6 +153,31 @@ PyAPI_FUNC(int) PyThread_join_thread(PyThread_handle_t);
*/
PyAPI_FUNC(int) PyThread_detach_thread(PyThread_handle_t);
+
+/******************/
+/* thread handles */
+/******************/
+
+// Handles transition from RUNNING to one of JOINED, DETACHED, or INVALID (post
+// fork).
+typedef enum {
+ THREAD_HANDLE_RUNNING = 1,
+ THREAD_HANDLE_JOINED = 2,
+ THREAD_HANDLE_DETACHED = 3,
+ THREAD_HANDLE_INVALID = 4,
+} _PyThreadHandleState;
+
+extern PyTypeObject _PyThreadHandle_Type;
+
+extern PyObject * _PyThreadHandle_NewObject(void);
+extern _PyEventRc * _PyThreadHandle_GetExitingEvent(PyObject *);
+extern void _PyThreadHandle_SetStarted(
+ PyObject *obj,
+ PyThread_handle_t handle,
+ PyThread_ident_t ident
+);
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 3cf4de08a0c842..fa436c47135742 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -524,6 +524,7 @@ OBJECT_OBJS= \
Objects/setobject.o \
Objects/sliceobject.o \
Objects/structseq.o \
+ Objects/threadhandleobject.o \
Objects/tupleobject.o \
Objects/typeobject.o \
Objects/typevarobject.o \
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index cc5396a035018f..1072cf649269d4 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -3,7 +3,7 @@
#include "Python.h"
#include "pycore_interp.h" // _PyInterpreterState.threads.count
-#include "pycore_lock.h"
+#include "pycore_lock.h" // _PyEventRc
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
#include "pycore_pylifecycle.h"
@@ -25,13 +25,16 @@
// Forward declarations
static struct PyModuleDef thread_module;
-// Module state
+
+/****************/
+/* Module state */
+/****************/
+
typedef struct {
PyTypeObject *excepthook_type;
PyTypeObject *lock_type;
PyTypeObject *local_type;
PyTypeObject *local_dummy_type;
- PyTypeObject *thread_handle_type;
} thread_module_state;
static inline thread_module_state*
@@ -42,232 +45,226 @@ get_thread_state(PyObject *module)
return (thread_module_state *)state;
}
-// _ThreadHandle type
-
-// Handles transition from RUNNING to one of JOINED, DETACHED, or INVALID (post
-// fork).
-typedef enum {
- THREAD_HANDLE_RUNNING = 1,
- THREAD_HANDLE_JOINED = 2,
- THREAD_HANDLE_DETACHED = 3,
- THREAD_HANDLE_INVALID = 4,
-} ThreadHandleState;
-
-// A handle around an OS thread.
-//
-// The OS thread is either joined or detached after the handle is destroyed.
-//
-// Joining the handle is idempotent; the underlying OS thread is joined or
-// detached only once. Concurrent join operations are serialized until it is
-// their turn to execute or an earlier operation completes successfully. Once a
-// join has completed successfully all future joins complete immediately.
-typedef struct {
- PyObject_HEAD
- struct llist_node node; // linked list node (see _pythread_runtime_state)
-
- // The `ident` and `handle` fields are immutable once the object is visible
- // to threads other than its creator, thus they do not need to be accessed
- // atomically.
- PyThread_ident_t ident;
- PyThread_handle_t handle;
- // Holds a value from the `ThreadHandleState` enum.
- int state;
+/********************/
+/* thread execution */
+/********************/
- // Set immediately before `thread_run` returns to indicate that the OS
- // thread is about to exit. This is used to avoid false positives when
- // detecting self-join attempts. See the comment in `ThreadHandle_join()`
- // for a more detailed explanation.
+// bootstate is used to "bootstrap" new threads. Any arguments needed by
+// `thread_run()`, which can only take a single argument due to platform
+// limitations, are contained in bootstate.
+struct bootstate {
+ PyThreadState *tstate;
+ PyObject *func;
+ PyObject *args;
+ PyObject *kwargs;
_PyEventRc *thread_is_exiting;
+};
- // Serializes calls to `join`.
- _PyOnceFlag once;
-} ThreadHandleObject;
-
-static inline int
-get_thread_handle_state(ThreadHandleObject *handle)
-{
- return _Py_atomic_load_int(&handle->state);
-}
-
-static inline void
-set_thread_handle_state(ThreadHandleObject *handle, ThreadHandleState state)
-{
- _Py_atomic_store_int(&handle->state, state);
-}
-
-static ThreadHandleObject*
-new_thread_handle(thread_module_state* state)
+static struct bootstate *
+thread_bootstate_new(PyObject *func, PyObject *args, PyObject *kwargs,
+ _PyEventRc *thread_is_exiting)
{
- _PyEventRc *event = _PyEventRc_New();
- if (event == NULL) {
- PyErr_NoMemory();
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "thread is not supported for isolated subinterpreters");
return NULL;
}
- ThreadHandleObject* self = PyObject_New(ThreadHandleObject, state->thread_handle_type);
- if (self == NULL) {
- _PyEventRc_Decref(event);
+ if (interp->finalizing) {
+ PyErr_SetString(PyExc_PythonFinalizationError,
+ "can't create new thread at interpreter shutdown");
return NULL;
}
- self->ident = 0;
- self->handle = 0;
- self->thread_is_exiting = event;
- self->once = (_PyOnceFlag){0};
- self->state = THREAD_HANDLE_INVALID;
-
- HEAD_LOCK(&_PyRuntime);
- llist_insert_tail(&_PyRuntime.threads.handles, &self->node);
- HEAD_UNLOCK(&_PyRuntime);
-
- return self;
-}
-static void
-ThreadHandle_dealloc(ThreadHandleObject *self)
-{
- PyObject *tp = (PyObject *) Py_TYPE(self);
-
- // Remove ourself from the global list of handles
- HEAD_LOCK(&_PyRuntime);
- if (self->node.next != NULL) {
- llist_remove(&self->node);
- }
- HEAD_UNLOCK(&_PyRuntime);
-
- // It's safe to access state non-atomically:
- // 1. This is the destructor; nothing else holds a reference.
- // 2. The refcount going to zero is a "synchronizes-with" event;
- // all changes from other threads are visible.
- if (self->state == THREAD_HANDLE_RUNNING) {
- // This is typically short so no need to release the GIL
- if (PyThread_detach_thread(self->handle)) {
- PyErr_SetString(ThreadError, "Failed detaching thread");
- PyErr_WriteUnraisable(tp);
- }
- else {
- self->state = THREAD_HANDLE_DETACHED;
+ PyThreadState *tstate = _PyThreadState_New(
+ interp, _PyThreadState_WHENCE_THREADING);
+ if (tstate == NULL) {
+ if (!PyErr_Occurred()) {
+ PyErr_NoMemory();
}
+ return NULL;
}
- _PyEventRc_Decref(self->thread_is_exiting);
- PyObject_Free(self);
- Py_DECREF(tp);
-}
-void
-_PyThread_AfterFork(struct _pythread_runtime_state *state)
-{
- // gh-115035: We mark ThreadHandles as not joinable early in the child's
- // after-fork handler. We do this before calling any Python code to ensure
- // that it happens before any ThreadHandles are deallocated, such as by a
- // GC cycle.
- PyThread_ident_t current = PyThread_get_thread_ident_ex();
-
- struct llist_node *node;
- llist_for_each_safe(node, &state->handles) {
- ThreadHandleObject *hobj = llist_data(node, ThreadHandleObject, node);
- if (hobj->ident == current) {
- continue;
+ if (args == NULL) {
+ args = PyTuple_New(0);
+ if (args == NULL) {
+ PyThreadState_Clear(tstate);
+ PyThreadState_Delete(tstate);
}
+ }
+ else {
+ Py_INCREF(args);
+ }
- // Disallow calls to join() as they could crash. We are the only
- // thread; it's safe to set this without an atomic.
- hobj->state = THREAD_HANDLE_INVALID;
- llist_remove(node);
+ // gh-109795: Use PyMem_RawMalloc() instead of PyMem_Malloc(),
+ // because it should be possible to call thread_bootstate_free()
+ // without holding the GIL.
+ struct bootstate *boot = PyMem_RawMalloc(sizeof(struct bootstate));
+ if (boot == NULL) {
+ PyErr_NoMemory();
+ Py_DECREF(args);
+ PyThreadState_Clear(tstate);
+ PyThreadState_Delete(tstate);
+ return NULL;
}
+ *boot = (struct bootstate){
+ .tstate = tstate,
+ .func = Py_NewRef(func),
+ .args = args,
+ .kwargs = Py_XNewRef(kwargs),
+ .thread_is_exiting = thread_is_exiting,
+ };
+ if (thread_is_exiting != NULL) {
+ _PyEventRc_Incref(thread_is_exiting);
+ }
+ return boot;
}
-static PyObject *
-ThreadHandle_repr(ThreadHandleObject *self)
+static void
+thread_bootstate_free(struct bootstate *boot)
{
- return PyUnicode_FromFormat("<%s object: ident=%" PY_FORMAT_THREAD_IDENT_T ">",
- Py_TYPE(self)->tp_name, self->ident);
+ Py_XDECREF(boot->func);
+ Py_XDECREF(boot->args);
+ Py_XDECREF(boot->kwargs);
+ if (boot->thread_is_exiting != NULL) {
+ _PyEventRc_Decref(boot->thread_is_exiting);
+ }
+ if (boot->tstate != NULL) {
+ PyThreadState_Clear(boot->tstate);
+ // XXX PyThreadState_Delete() too?
+ }
+ PyMem_RawFree(boot);
}
-static PyObject *
-ThreadHandle_get_ident(ThreadHandleObject *self, void *ignored)
+static void
+thread_bootstate_finalizing(struct bootstate *boot)
{
- return PyLong_FromUnsignedLongLong(self->ident);
+ // Don't call PyThreadState_Clear() nor _PyThreadState_DeleteCurrent().
+ // These functions are called on tstate indirectly by Py_Finalize()
+ // which calls _PyInterpreterState_Clear().
+ //
+ // Py_DECREF() cannot be called because the GIL is not held: leak
+ // references on purpose. Python is being finalized anyway.
+ boot->tstate = NULL;
+ boot->func = NULL;
+ boot->args = NULL;
+ boot->kwargs = NULL;
+ thread_bootstate_free(boot);
}
-static int
-join_thread(ThreadHandleObject *handle)
-{
- assert(get_thread_handle_state(handle) == THREAD_HANDLE_RUNNING);
-
- int err;
- Py_BEGIN_ALLOW_THREADS
- err = PyThread_join_thread(handle->handle);
- Py_END_ALLOW_THREADS
- if (err) {
- PyErr_SetString(ThreadError, "Failed joining thread");
- return -1;
- }
- set_thread_handle_state(handle, THREAD_HANDLE_JOINED);
- return 0;
-}
-static PyObject *
-ThreadHandle_join(ThreadHandleObject *self, void* ignored)
+static void
+thread_run(void *boot_raw)
{
- if (get_thread_handle_state(self) == THREAD_HANDLE_INVALID) {
- PyErr_SetString(PyExc_ValueError,
- "the handle is invalid and thus cannot be joined");
- return NULL;
- }
+ struct bootstate *boot = (struct bootstate *) boot_raw;
+ PyThreadState *tstate = boot->tstate;
+
+ // `thread_is_exiting` needs to be set after bootstate has been freed
+ _PyEventRc *thread_is_exiting = boot->thread_is_exiting;
+ boot->thread_is_exiting = NULL;
- // We want to perform this check outside of the `_PyOnceFlag` to prevent
- // deadlock in the scenario where another thread joins us and we then
- // attempt to join ourselves. However, it's not safe to check thread
- // identity once the handle's os thread has finished. We may end up reusing
- // the identity stored in the handle and erroneously think we are
- // attempting to join ourselves.
+ // gh-108987: If _thread.start_new_thread() is called before or while
+ // Python is being finalized, thread_run() can called *after*.
+ // _PyRuntimeState_SetFinalizing() is called. At this point, all Python
+ // threads must exit, except of the thread calling Py_Finalize() whch holds
+ // the GIL and must not exit.
//
- // To work around this, we set `thread_is_exiting` immediately before
- // `thread_run` returns. We can be sure that we are not attempting to join
- // ourselves if the handle's thread is about to exit.
- if (!_PyEvent_IsSet(&self->thread_is_exiting->event) &&
- self->ident == PyThread_get_thread_ident_ex()) {
- // PyThread_join_thread() would deadlock or error out.
- PyErr_SetString(ThreadError, "Cannot join current thread");
- return NULL;
+ // At this stage, tstate can be a dangling pointer (point to freed memory),
+ // it's ok to call _PyThreadState_MustExit() with a dangling pointer.
+ if (_PyThreadState_MustExit(tstate)) {
+ thread_bootstate_finalizing(boot);
+ goto exit;
}
- if (_PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)join_thread,
- self) == -1) {
- return NULL;
+ _PyThreadState_Bind(tstate);
+ PyEval_AcquireThread(tstate);
+ _Py_atomic_add_ssize(&tstate->interp->threads.count, 1);
+
+ PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs);
+ if (res == NULL) {
+ if (PyErr_ExceptionMatches(PyExc_SystemExit))
+ /* SystemExit is ignored silently */
+ PyErr_Clear();
+ else {
+ PyErr_FormatUnraisable(
+ "Exception ignored in thread started by %R", boot->func);
+ }
}
- assert(get_thread_handle_state(self) == THREAD_HANDLE_JOINED);
- Py_RETURN_NONE;
+ else {
+ Py_DECREF(res);
+ }
+
+ boot->tstate = NULL;
+ thread_bootstate_free(boot);
+
+ _Py_atomic_add_ssize(&tstate->interp->threads.count, -1);
+ PyThreadState_Clear(tstate);
+ _PyThreadState_DeleteCurrent(tstate);
+
+exit:
+ if (thread_is_exiting != NULL) {
+ _PyEvent_Notify(&thread_is_exiting->event);
+ _PyEventRc_Decref(thread_is_exiting);
+ }
+
+ // bpo-44434: Don't call explicitly PyThread_exit_thread(). On Linux with
+ // the glibc, pthread_exit() can abort the whole process if dlopen() fails
+ // to open the libgcc_s.so library (ex: EMFILE error).
+ return;
}
-static PyGetSetDef ThreadHandle_getsetlist[] = {
- {"ident", (getter)ThreadHandle_get_ident, NULL, NULL},
- {0},
-};
-static PyMethodDef ThreadHandle_methods[] =
+static void _lock_unlock(PyObject *);
+
+static void
+release_sentinel(void *weakref_raw)
{
- {"join", (PyCFunction)ThreadHandle_join, METH_NOARGS},
- {0, 0}
-};
+ PyObject *weakref = _PyObject_CAST(weakref_raw);
-static PyType_Slot ThreadHandle_Type_slots[] = {
- {Py_tp_dealloc, (destructor)ThreadHandle_dealloc},
- {Py_tp_repr, (reprfunc)ThreadHandle_repr},
- {Py_tp_getset, ThreadHandle_getsetlist},
- {Py_tp_methods, ThreadHandle_methods},
- {0, 0}
-};
+ /* Tricky: this function is called when the current thread state
+ is being deleted. Therefore, only simple C code can safely
+ execute here. */
+ PyObject *lock = _PyWeakref_GET_REF(weakref);
+ if (lock != NULL) {
+ _lock_unlock(lock);
+ Py_DECREF(lock);
+ }
-static PyType_Spec ThreadHandle_Type_spec = {
- "_thread._ThreadHandle",
- sizeof(ThreadHandleObject),
- 0,
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
- ThreadHandle_Type_slots,
-};
+ /* Deallocating a weakref with a NULL callback only calls
+ PyObject_GC_Del(), which can't call any Python code. */
+ Py_DECREF(weakref);
+}
+
+static int
+set_threadstate_finalizer(PyThreadState *tstate, PyObject *lock)
+{
+ PyObject *wr;
+ if (tstate->on_delete_data != NULL) {
+ /* We must support the re-creation of the lock from a
+ fork()ed child. */
+ assert(tstate->on_delete == &release_sentinel);
+ wr = (PyObject *) tstate->on_delete_data;
+ tstate->on_delete = NULL;
+ tstate->on_delete_data = NULL;
+ Py_DECREF(wr);
+ }
+
+ /* The lock is owned by whoever called set_threadstate_finalizer(),
+ but the weakref clings to the thread state. */
+ wr = PyWeakref_NewRef(lock, NULL);
+ if (wr == NULL) {
+ return -1;
+ }
+ tstate->on_delete_data = (void *) wr;
+ tstate->on_delete = &release_sentinel;
+ return 0;
+}
+
+/****************/
/* Lock objects */
+/****************/
typedef struct {
PyObject_HEAD
@@ -381,6 +378,16 @@ With an argument, this will only block if the argument is true,\n\
and the return value reflects whether the lock is acquired.\n\
The blocking operation is interruptible.");
+static void
+_lock_unlock(PyObject *obj)
+{
+ lockobject *lock = (lockobject *)obj;
+ if (lock->locked) {
+ lock->locked = 0;
+ PyThread_release_lock(lock->lock_lock);
+ }
+}
+
static PyObject *
lock_PyThread_release_lock(lockobject *self, PyObject *Py_UNUSED(ignored))
{
@@ -519,7 +526,33 @@ static PyType_Spec lock_type_spec = {
.slots = lock_type_slots,
};
+static lockobject *
+newlockobject(PyObject *module)
+{
+ thread_module_state *state = get_thread_state(module);
+
+ PyTypeObject *type = state->lock_type;
+ lockobject *self = (lockobject *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ self->lock_lock = PyThread_allocate_lock();
+ self->locked = 0;
+ self->in_weakreflist = NULL;
+
+ if (self->lock_lock == NULL) {
+ Py_DECREF(self);
+ PyErr_SetString(ThreadError, "can't allocate lock");
+ return NULL;
+ }
+ return self;
+}
+
+
+/**************************/
/* Recursive lock objects */
+/**************************/
typedef struct {
PyObject_HEAD
@@ -829,30 +862,10 @@ static PyType_Spec rlock_type_spec = {
.slots = rlock_type_slots,
};
-static lockobject *
-newlockobject(PyObject *module)
-{
- thread_module_state *state = get_thread_state(module);
-
- PyTypeObject *type = state->lock_type;
- lockobject *self = (lockobject *)type->tp_alloc(type, 0);
- if (self == NULL) {
- return NULL;
- }
-
- self->lock_lock = PyThread_allocate_lock();
- self->locked = 0;
- self->in_weakreflist = NULL;
-
- if (self->lock_lock == NULL) {
- Py_DECREF(self);
- PyErr_SetString(ThreadError, "can't allocate lock");
- return NULL;
- }
- return self;
-}
+/************************/
/* Thread-local objects */
+/************************/
/* Quick overview:
@@ -1270,292 +1283,111 @@ _localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref)
Py_RETURN_NONE;
}
-/* Module functions */
-// bootstate is used to "bootstrap" new threads. Any arguments needed by
-// `thread_run()`, which can only take a single argument due to platform
-// limitations, are contained in bootstate.
-struct bootstate {
- PyThreadState *tstate;
- PyObject *func;
- PyObject *args;
- PyObject *kwargs;
- _PyEventRc *thread_is_exiting;
-};
+/********************/
+/* Module functions */
+/********************/
+/* runtime state inquiries */
-static void
-thread_bootstate_free(struct bootstate *boot, int decref)
+static PyObject *
+threadmod_daemon_threads_allowed(PyObject *module, PyObject *Py_UNUSED(ignored))
{
- if (decref) {
- Py_DECREF(boot->func);
- Py_DECREF(boot->args);
- Py_XDECREF(boot->kwargs);
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ if (interp->feature_flags & Py_RTFLAGS_DAEMON_THREADS) {
+ Py_RETURN_TRUE;
}
- if (boot->thread_is_exiting != NULL) {
- _PyEventRc_Decref(boot->thread_is_exiting);
+ else {
+ Py_RETURN_FALSE;
}
- PyMem_RawFree(boot);
}
+PyDoc_STRVAR(daemon_threads_allowed_doc,
+"daemon_threads_allowed()\n\
+\n\
+Return True if daemon threads are allowed in the current interpreter,\n\
+and False otherwise.\n");
-static void
-thread_run(void *boot_raw)
-{
- struct bootstate *boot = (struct bootstate *) boot_raw;
- PyThreadState *tstate = boot->tstate;
-
- // `thread_is_exiting` needs to be set after bootstate has been freed
- _PyEventRc *thread_is_exiting = boot->thread_is_exiting;
- boot->thread_is_exiting = NULL;
-
- // gh-108987: If _thread.start_new_thread() is called before or while
- // Python is being finalized, thread_run() can called *after*.
- // _PyRuntimeState_SetFinalizing() is called. At this point, all Python
- // threads must exit, except of the thread calling Py_Finalize() whch holds
- // the GIL and must not exit.
- //
- // At this stage, tstate can be a dangling pointer (point to freed memory),
- // it's ok to call _PyThreadState_MustExit() with a dangling pointer.
- if (_PyThreadState_MustExit(tstate)) {
- // Don't call PyThreadState_Clear() nor _PyThreadState_DeleteCurrent().
- // These functions are called on tstate indirectly by Py_Finalize()
- // which calls _PyInterpreterState_Clear().
- //
- // Py_DECREF() cannot be called because the GIL is not held: leak
- // references on purpose. Python is being finalized anyway.
- thread_bootstate_free(boot, 0);
- goto exit;
- }
-
- _PyThreadState_Bind(tstate);
- PyEval_AcquireThread(tstate);
- _Py_atomic_add_ssize(&tstate->interp->threads.count, 1);
-
- PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs);
- if (res == NULL) {
- if (PyErr_ExceptionMatches(PyExc_SystemExit))
- /* SystemExit is ignored silently */
- PyErr_Clear();
- else {
- PyErr_FormatUnraisable(
- "Exception ignored in thread started by %R", boot->func);
- }
- }
- else {
- Py_DECREF(res);
- }
-
- thread_bootstate_free(boot, 1);
-
- _Py_atomic_add_ssize(&tstate->interp->threads.count, -1);
- PyThreadState_Clear(tstate);
- _PyThreadState_DeleteCurrent(tstate);
-
-exit:
- if (thread_is_exiting != NULL) {
- _PyEvent_Notify(&thread_is_exiting->event);
- _PyEventRc_Decref(thread_is_exiting);
- }
-
- // bpo-44434: Don't call explicitly PyThread_exit_thread(). On Linux with
- // the glibc, pthread_exit() can abort the whole process if dlopen() fails
- // to open the libgcc_s.so library (ex: EMFILE error).
- return;
-}
static PyObject *
-thread_daemon_threads_allowed(PyObject *module, PyObject *Py_UNUSED(ignored))
+threadmod__count(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (interp->feature_flags & Py_RTFLAGS_DAEMON_THREADS) {
- Py_RETURN_TRUE;
- }
- else {
- Py_RETURN_FALSE;
- }
+ return PyLong_FromSsize_t(_Py_atomic_load_ssize(&interp->threads.count));
}
-PyDoc_STRVAR(daemon_threads_allowed_doc,
-"daemon_threads_allowed()\n\
+PyDoc_STRVAR(_count_doc,
+"_count() -> integer\n\
\n\
-Return True if daemon threads are allowed in the current interpreter,\n\
-and False otherwise.\n");
-
-static int
-do_start_new_thread(thread_module_state* state,
- PyObject *func, PyObject* args, PyObject* kwargs,
- int joinable,
- PyThread_ident_t* ident, PyThread_handle_t* handle,
- _PyEventRc *thread_is_exiting)
-{
- PyInterpreterState *interp = _PyInterpreterState_GET();
- if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
- PyErr_SetString(PyExc_RuntimeError,
- "thread is not supported for isolated subinterpreters");
- return -1;
- }
- if (interp->finalizing) {
- PyErr_SetString(PyExc_PythonFinalizationError,
- "can't create new thread at interpreter shutdown");
- return -1;
- }
-
- // gh-109795: Use PyMem_RawMalloc() instead of PyMem_Malloc(),
- // because it should be possible to call thread_bootstate_free()
- // without holding the GIL.
- struct bootstate *boot = PyMem_RawMalloc(sizeof(struct bootstate));
- if (boot == NULL) {
- PyErr_NoMemory();
- return -1;
- }
- boot->tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_THREADING);
- if (boot->tstate == NULL) {
- PyMem_RawFree(boot);
- if (!PyErr_Occurred()) {
- PyErr_NoMemory();
- }
- return -1;
- }
- boot->func = Py_NewRef(func);
- boot->args = Py_NewRef(args);
- boot->kwargs = Py_XNewRef(kwargs);
- boot->thread_is_exiting = thread_is_exiting;
- if (thread_is_exiting != NULL) {
- _PyEventRc_Incref(thread_is_exiting);
- }
+\
+Return the number of currently running Python threads, excluding\n\
+the main thread. The returned number comprises all threads created\n\
+through `start_new_thread()` as well as `threading.Thread`, and not\n\
+yet finished.\n\
+\n\
+This function is meant for internal and specialized purposes only.\n\
+In most applications `threading.enumerate()` should be used instead.");
- int err;
- if (joinable) {
- err = PyThread_start_joinable_thread(thread_run, (void*) boot, ident, handle);
- } else {
- *handle = 0;
- *ident = PyThread_start_new_thread(thread_run, (void*) boot);
- err = (*ident == PYTHREAD_INVALID_THREAD_ID);
- }
- if (err) {
- PyErr_SetString(ThreadError, "can't start new thread");
- PyThreadState_Clear(boot->tstate);
- thread_bootstate_free(boot, 1);
- return -1;
- }
- return 0;
-}
static PyObject *
-thread_PyThread_start_new_thread(PyObject *module, PyObject *fargs)
+threadmod_stack_size(PyObject *self, PyObject *args)
{
- PyObject *func, *args, *kwargs = NULL;
- thread_module_state *state = get_thread_state(module);
-
- if (!PyArg_UnpackTuple(fargs, "start_new_thread", 2, 3,
- &func, &args, &kwargs))
- return NULL;
- if (!PyCallable_Check(func)) {
- PyErr_SetString(PyExc_TypeError,
- "first arg must be callable");
- return NULL;
- }
- if (!PyTuple_Check(args)) {
- PyErr_SetString(PyExc_TypeError,
- "2nd arg must be a tuple");
- return NULL;
- }
- if (kwargs != NULL && !PyDict_Check(kwargs)) {
- PyErr_SetString(PyExc_TypeError,
- "optional 3rd arg must be a dictionary");
- return NULL;
- }
+ size_t old_size;
+ Py_ssize_t new_size = 0;
+ int rc;
- if (PySys_Audit("_thread.start_new_thread", "OOO",
- func, args, kwargs ? kwargs : Py_None) < 0) {
+ if (!PyArg_ParseTuple(args, "|n:stack_size", &new_size))
return NULL;
- }
- PyThread_ident_t ident = 0;
- PyThread_handle_t handle;
- if (do_start_new_thread(state, func, args, kwargs, /*joinable=*/ 0,
- &ident, &handle, NULL)) {
+ if (new_size < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "size must be 0 or a positive value");
return NULL;
}
- return PyLong_FromUnsignedLongLong(ident);
-}
-PyDoc_STRVAR(start_new_doc,
-"start_new_thread(function, args[, kwargs])\n\
-(start_new() is an obsolete synonym)\n\
-\n\
-Start a new thread and return its identifier.\n\
-\n\
-The thread will call the function with positional arguments from the\n\
-tuple args and keyword arguments taken from the optional dictionary\n\
-kwargs. The thread exits when the function returns; the return value\n\
-is ignored. The thread will also exit when the function raises an\n\
-unhandled exception; a stack trace will be printed unless the exception\n\
-is SystemExit.\n");
-
-static PyObject *
-thread_PyThread_start_joinable_thread(PyObject *module, PyObject *func)
-{
- thread_module_state *state = get_thread_state(module);
+ old_size = PyThread_get_stacksize();
- if (!PyCallable_Check(func)) {
- PyErr_SetString(PyExc_TypeError,
- "thread function must be callable");
+ rc = PyThread_set_stacksize((size_t) new_size);
+ if (rc == -1) {
+ PyErr_Format(PyExc_ValueError,
+ "size not valid: %zd bytes",
+ new_size);
return NULL;
}
-
- if (PySys_Audit("_thread.start_joinable_thread", "O", func) < 0) {
+ if (rc == -2) {
+ PyErr_SetString(ThreadError,
+ "setting stack size not supported");
return NULL;
}
- PyObject* args = PyTuple_New(0);
- if (args == NULL) {
- return NULL;
- }
- ThreadHandleObject* hobj = new_thread_handle(state);
- if (hobj == NULL) {
- Py_DECREF(args);
- return NULL;
- }
- if (do_start_new_thread(state, func, args, /*kwargs=*/ NULL, /*joinable=*/ 1,
- &hobj->ident, &hobj->handle, hobj->thread_is_exiting)) {
- Py_DECREF(args);
- Py_DECREF(hobj);
- return NULL;
- }
- set_thread_handle_state(hobj, THREAD_HANDLE_RUNNING);
- Py_DECREF(args);
- return (PyObject*) hobj;
+ return PyLong_FromSsize_t((Py_ssize_t) old_size);
}
-PyDoc_STRVAR(start_joinable_doc,
-"start_joinable_thread(function)\n\
+PyDoc_STRVAR(stack_size_doc,
+"stack_size([size]) -> size\n\
\n\
-*For internal use only*: start a new thread.\n\
+Return the thread stack size used when creating new threads. The\n\
+optional size argument specifies the stack size (in bytes) to be used\n\
+for subsequently created threads, and must be 0 (use platform or\n\
+configured default) or a positive integer value of at least 32,768 (32k).\n\
+If changing the thread stack size is unsupported, a ThreadError\n\
+exception is raised. If the specified size is invalid, a ValueError\n\
+exception is raised, and the stack size is unmodified. 32k bytes\n\
+ currently the minimum supported stack size value to guarantee\n\
+sufficient stack space for the interpreter itself.\n\
\n\
-Like start_new_thread(), this starts a new thread calling the given function.\n\
-Unlike start_new_thread(), this returns a handle object with methods to join\n\
-or detach the given thread.\n\
-This function is not for third-party code, please use the\n\
-`threading` module instead.\n");
+Note that some platforms may have particular restrictions on values for\n\
+the stack size, such as requiring a minimum stack size larger than 32 KiB or\n\
+requiring allocation in multiples of the system memory page size\n\
+- platform documentation should be referred to for more information\n\
+(4 KiB pages are common; using multiples of 4096 for the stack size is\n\
+the suggested approach in the absence of more specific information).");
-static PyObject *
-thread_PyThread_exit_thread(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
- PyErr_SetNone(PyExc_SystemExit);
- return NULL;
-}
-PyDoc_STRVAR(exit_doc,
-"exit()\n\
-(exit_thread() is an obsolete synonym)\n\
-\n\
-This is synonymous to ``raise SystemExit''. It will cause the current\n\
-thread to exit silently unless the exception is caught.");
+/* signals */
static PyObject *
-thread_PyThread_interrupt_main(PyObject *self, PyObject *args)
+threadmod_interrupt_main(PyObject *self, PyObject *args)
{
int signum = SIGINT;
if (!PyArg_ParseTuple(args, "|i:signum", &signum)) {
@@ -1580,21 +1412,11 @@ A subthread can use this function to interrupt the main thread.\n\
Note: the default signal handler for SIGINT raises ``KeyboardInterrupt``."
);
-static PyObject *
-thread_PyThread_allocate_lock(PyObject *module, PyObject *Py_UNUSED(ignored))
-{
- return (PyObject *) newlockobject(module);
-}
-PyDoc_STRVAR(allocate_doc,
-"allocate_lock() -> lock object\n\
-(allocate() is an obsolete synonym)\n\
-\n\
-Create a new lock object. See help(type(threading.Lock())) for\n\
-information about locks.");
+/* the current OS thread */
static PyObject *
-thread_get_ident(PyObject *self, PyObject *Py_UNUSED(ignored))
+threadmod_get_ident(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyThread_ident_t ident = PyThread_get_thread_ident_ex();
if (ident == PYTHREAD_INVALID_THREAD_ID) {
@@ -1615,9 +1437,10 @@ allocated consecutive numbers starting at 1, this behavior should not\n\
be relied upon, and the number should be seen purely as a magic cookie.\n\
A thread's identity may be reused for another thread after it exits.");
+
#ifdef PY_HAVE_THREAD_NATIVE_ID
static PyObject *
-thread_get_native_id(PyObject *self, PyObject *Py_UNUSED(ignored))
+threadmod_get_native_id(PyObject *self, PyObject *Py_UNUSED(ignored))
{
unsigned long native_id = PyThread_get_thread_native_id();
return PyLong_FromUnsignedLong(native_id);
@@ -1631,139 +1454,167 @@ by the OS (kernel). This may be used to uniquely identify a\n\
particular thread within a system.");
#endif
+
static PyObject *
-thread__count(PyObject *self, PyObject *Py_UNUSED(ignored))
+threadmod__is_main_interpreter(PyObject *module, PyObject *Py_UNUSED(ignored))
{
PyInterpreterState *interp = _PyInterpreterState_GET();
- return PyLong_FromSsize_t(_Py_atomic_load_ssize(&interp->threads.count));
+ return PyBool_FromLong(_Py_IsMainInterpreter(interp));
}
-PyDoc_STRVAR(_count_doc,
-"_count() -> integer\n\
-\n\
-\
-Return the number of currently running Python threads, excluding\n\
-the main thread. The returned number comprises all threads created\n\
-through `start_new_thread()` as well as `threading.Thread`, and not\n\
-yet finished.\n\
+PyDoc_STRVAR(thread__is_main_interpreter_doc,
+"_is_main_interpreter()\n\
\n\
-This function is meant for internal and specialized purposes only.\n\
-In most applications `threading.enumerate()` should be used instead.");
+Return True if the current interpreter is the main Python interpreter.");
-static void
-release_sentinel(void *weakref_raw)
+
+static PyObject *
+threadmod_exit_thread(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- PyObject *weakref = _PyObject_CAST(weakref_raw);
+ PyErr_SetNone(PyExc_SystemExit);
+ return NULL;
+}
- /* Tricky: this function is called when the current thread state
- is being deleted. Therefore, only simple C code can safely
- execute here. */
- lockobject *lock = (lockobject *)_PyWeakref_GET_REF(weakref);
- if (lock != NULL) {
- if (lock->locked) {
- lock->locked = 0;
- PyThread_release_lock(lock->lock_lock);
- }
- Py_DECREF(lock);
- }
+PyDoc_STRVAR(exit_doc,
+"exit()\n\
+(exit_thread() is an obsolete synonym)\n\
+\n\
+This is synonymous to ``raise SystemExit''. It will cause the current\n\
+thread to exit silently unless the exception is caught.");
- /* Deallocating a weakref with a NULL callback only calls
- PyObject_GC_Del(), which can't call any Python code. */
- Py_DECREF(weakref);
-}
+
+/* thread execution */
static PyObject *
-thread__set_sentinel(PyObject *module, PyObject *Py_UNUSED(ignored))
+threadmod_start_new_thread(PyObject *module, PyObject *fargs)
{
- PyObject *wr;
- PyThreadState *tstate = _PyThreadState_GET();
- lockobject *lock;
+ PyObject *func, *args, *kwargs = NULL;
+ if (!PyArg_UnpackTuple(fargs, "start_new_thread", 2, 3,
+ &func, &args, &kwargs))
+ return NULL;
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_TypeError,
+ "first arg must be callable");
+ return NULL;
+ }
+ if (!PyTuple_Check(args)) {
+ PyErr_SetString(PyExc_TypeError,
+ "2nd arg must be a tuple");
+ return NULL;
+ }
+ if (kwargs != NULL && !PyDict_Check(kwargs)) {
+ PyErr_SetString(PyExc_TypeError,
+ "optional 3rd arg must be a dictionary");
+ return NULL;
+ }
- if (tstate->on_delete_data != NULL) {
- /* We must support the re-creation of the lock from a
- fork()ed child. */
- assert(tstate->on_delete == &release_sentinel);
- wr = (PyObject *) tstate->on_delete_data;
- tstate->on_delete = NULL;
- tstate->on_delete_data = NULL;
- Py_DECREF(wr);
+ if (PySys_Audit("_thread.start_new_thread", "OOO",
+ func, args, kwargs ? kwargs : Py_None) < 0) {
+ return NULL;
}
- lock = newlockobject(module);
- if (lock == NULL)
+
+ struct bootstate *boot = thread_bootstate_new(func, args, kwargs, NULL);
+ if (boot == NULL) {
return NULL;
- /* The lock is owned by whoever called _set_sentinel(), but the weakref
- hangs to the thread state. */
- wr = PyWeakref_NewRef((PyObject *) lock, NULL);
- if (wr == NULL) {
- Py_DECREF(lock);
+ }
+
+ PyThread_ident_t ident = PyThread_start_new_thread(thread_run, (void*) boot);
+ if (ident == PYTHREAD_INVALID_THREAD_ID) {
+ PyErr_SetString(ThreadError, "can't start new thread");
+ thread_bootstate_free(boot);
return NULL;
}
- tstate->on_delete_data = (void *) wr;
- tstate->on_delete = &release_sentinel;
- return (PyObject *) lock;
+ return PyLong_FromUnsignedLongLong(ident);
}
-PyDoc_STRVAR(_set_sentinel_doc,
-"_set_sentinel() -> lock\n\
+PyDoc_STRVAR(start_new_doc,
+"start_new_thread(function, args[, kwargs])\n\
+(start_new() is an obsolete synonym)\n\
\n\
-Set a sentinel lock that will be released when the current thread\n\
-state is finalized (after it is untied from the interpreter).\n\
+Start a new thread and return its identifier.\n\
\n\
-This is a private API for the threading module.");
+The thread will call the function with positional arguments from the\n\
+tuple args and keyword arguments taken from the optional dictionary\n\
+kwargs. The thread exits when the function returns; the return value\n\
+is ignored. The thread will also exit when the function raises an\n\
+unhandled exception; a stack trace will be printed unless the exception\n\
+is SystemExit.\n");
+
static PyObject *
-thread_stack_size(PyObject *self, PyObject *args)
+threadmod_start_joinable_thread(PyObject *module, PyObject *func)
{
- size_t old_size;
- Py_ssize_t new_size = 0;
- int rc;
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_TypeError,
+ "thread function must be callable");
+ return NULL;
+ }
- if (!PyArg_ParseTuple(args, "|n:stack_size", &new_size))
+ if (PySys_Audit("_thread.start_joinable_thread", "O", func) < 0) {
return NULL;
+ }
- if (new_size < 0) {
- PyErr_SetString(PyExc_ValueError,
- "size must be 0 or a positive value");
+ PyObject *hobj = _PyThreadHandle_NewObject();
+ if (hobj == NULL) {
+ return NULL;
+ }
+ struct bootstate *boot = thread_bootstate_new(
+ func, NULL, NULL, _PyThreadHandle_GetExitingEvent(hobj));
+ if (boot == NULL) {
+ Py_DECREF(hobj);
return NULL;
}
+ PyThread_ident_t ident = 0;
+ PyThread_handle_t handle = 0;
+ if (PyThread_start_joinable_thread(
+ thread_run, (void*) boot, &ident, &handle) < 0)
+ {
+ PyErr_SetString(ThreadError, "can't start new thread");
+ thread_bootstate_free(boot);
+ Py_DECREF(hobj);
+ return NULL;
+ }
+ _PyThreadHandle_SetStarted(hobj, handle, ident);
+ return hobj;
+}
- old_size = PyThread_get_stacksize();
+PyDoc_STRVAR(start_joinable_doc,
+"start_joinable_thread(function)\n\
+\n\
+*For internal use only*: start a new thread.\n\
+\n\
+Like start_new_thread(), this starts a new thread calling the given function.\n\
+Unlike start_new_thread(), this returns a handle object with methods to join\n\
+or detach the given thread.\n\
+This function is not for third-party code, please use the\n\
+`threading` module instead.\n");
- rc = PyThread_set_stacksize((size_t) new_size);
- if (rc == -1) {
- PyErr_Format(PyExc_ValueError,
- "size not valid: %zd bytes",
- new_size);
+
+static PyObject *
+threadmod__set_sentinel(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyObject *lock = (PyObject *)newlockobject(module);
+ if (lock == NULL) {
return NULL;
}
- if (rc == -2) {
- PyErr_SetString(ThreadError,
- "setting stack size not supported");
+ if (set_threadstate_finalizer(tstate, lock) < 0) {
+ Py_DECREF(lock);
return NULL;
}
-
- return PyLong_FromSsize_t((Py_ssize_t) old_size);
+ return lock;
}
-PyDoc_STRVAR(stack_size_doc,
-"stack_size([size]) -> size\n\
+PyDoc_STRVAR(_set_sentinel_doc,
+"_set_sentinel() -> lock\n\
\n\
-Return the thread stack size used when creating new threads. The\n\
-optional size argument specifies the stack size (in bytes) to be used\n\
-for subsequently created threads, and must be 0 (use platform or\n\
-configured default) or a positive integer value of at least 32,768 (32k).\n\
-If changing the thread stack size is unsupported, a ThreadError\n\
-exception is raised. If the specified size is invalid, a ValueError\n\
-exception is raised, and the stack size is unmodified. 32k bytes\n\
- currently the minimum supported stack size value to guarantee\n\
-sufficient stack space for the interpreter itself.\n\
+Set a sentinel lock that will be released when the current thread\n\
+state is finalized (after it is untied from the interpreter).\n\
\n\
-Note that some platforms may have particular restrictions on values for\n\
-the stack size, such as requiring a minimum stack size larger than 32 KiB or\n\
-requiring allocation in multiples of the system memory page size\n\
-- platform documentation should be referred to for more information\n\
-(4 KiB pages are common; using multiples of 4096 for the stack size is\n\
-the suggested approach in the absence of more specific information).");
+This is a private API for the threading module.");
+
+
+/* thread excepthook */
static int
thread_excepthook_file(PyObject *file, PyObject *exc_type, PyObject *exc_value,
@@ -1844,7 +1695,7 @@ static PyStructSequence_Desc ExceptHookArgs_desc = {
static PyObject *
-thread_excepthook(PyObject *module, PyObject *args)
+threadmod_excepthook(PyObject *module, PyObject *args)
{
thread_module_state *state = get_thread_state(module);
@@ -1905,58 +1756,65 @@ PyDoc_STRVAR(excepthook_doc,
\n\
Handle uncaught Thread.run() exception.");
+
+/* other */
+
static PyObject *
-thread__is_main_interpreter(PyObject *module, PyObject *Py_UNUSED(ignored))
+threadmod_allocate_lock(PyObject *module, PyObject *Py_UNUSED(ignored))
{
- PyInterpreterState *interp = _PyInterpreterState_GET();
- return PyBool_FromLong(_Py_IsMainInterpreter(interp));
+ return (PyObject *) newlockobject(module);
}
-PyDoc_STRVAR(thread__is_main_interpreter_doc,
-"_is_main_interpreter()\n\
+PyDoc_STRVAR(allocate_doc,
+"allocate_lock() -> lock object\n\
+(allocate() is an obsolete synonym)\n\
\n\
-Return True if the current interpreter is the main Python interpreter.");
+Create a new lock object. See help(type(threading.Lock())) for\n\
+information about locks.");
+
static PyMethodDef thread_methods[] = {
- {"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
+ {"start_new_thread", (PyCFunction)threadmod_start_new_thread,
METH_VARARGS, start_new_doc},
- {"start_new", (PyCFunction)thread_PyThread_start_new_thread,
+ {"start_new", (PyCFunction)threadmod_start_new_thread,
METH_VARARGS, start_new_doc},
- {"start_joinable_thread", (PyCFunction)thread_PyThread_start_joinable_thread,
+ {"start_joinable_thread", (PyCFunction)threadmod_start_joinable_thread,
METH_O, start_joinable_doc},
- {"daemon_threads_allowed", (PyCFunction)thread_daemon_threads_allowed,
+ {"daemon_threads_allowed", (PyCFunction)threadmod_daemon_threads_allowed,
METH_NOARGS, daemon_threads_allowed_doc},
- {"allocate_lock", thread_PyThread_allocate_lock,
+ {"allocate_lock", threadmod_allocate_lock,
METH_NOARGS, allocate_doc},
- {"allocate", thread_PyThread_allocate_lock,
+ {"allocate", threadmod_allocate_lock,
METH_NOARGS, allocate_doc},
- {"exit_thread", thread_PyThread_exit_thread,
+ {"exit_thread", threadmod_exit_thread,
METH_NOARGS, exit_doc},
- {"exit", thread_PyThread_exit_thread,
+ {"exit", threadmod_exit_thread,
METH_NOARGS, exit_doc},
- {"interrupt_main", (PyCFunction)thread_PyThread_interrupt_main,
+ {"interrupt_main", (PyCFunction)threadmod_interrupt_main,
METH_VARARGS, interrupt_doc},
- {"get_ident", thread_get_ident,
+ {"get_ident", threadmod_get_ident,
METH_NOARGS, get_ident_doc},
#ifdef PY_HAVE_THREAD_NATIVE_ID
- {"get_native_id", thread_get_native_id,
+ {"get_native_id", threadmod_get_native_id,
METH_NOARGS, get_native_id_doc},
#endif
- {"_count", thread__count,
+ {"_count", threadmod__count,
METH_NOARGS, _count_doc},
- {"stack_size", (PyCFunction)thread_stack_size,
+ {"stack_size", (PyCFunction)threadmod_stack_size,
METH_VARARGS, stack_size_doc},
- {"_set_sentinel", thread__set_sentinel,
+ {"_set_sentinel", threadmod__set_sentinel,
METH_NOARGS, _set_sentinel_doc},
- {"_excepthook", thread_excepthook,
+ {"_excepthook", threadmod_excepthook,
METH_O, excepthook_doc},
- {"_is_main_interpreter", thread__is_main_interpreter,
+ {"_is_main_interpreter", threadmod__is_main_interpreter,
METH_NOARGS, thread__is_main_interpreter_doc},
{NULL, NULL} /* sentinel */
};
+/***************************/
/* Initialization function */
+/***************************/
static int
thread_module_exec(PyObject *module)
@@ -1968,11 +1826,9 @@ thread_module_exec(PyObject *module)
PyThread_init_thread();
// _ThreadHandle
- state->thread_handle_type = (PyTypeObject *)PyType_FromSpec(&ThreadHandle_Type_spec);
- if (state->thread_handle_type == NULL) {
- return -1;
- }
- if (PyDict_SetItemString(d, "_ThreadHandle", (PyObject *)state->thread_handle_type) < 0) {
+ if (PyDict_SetItemString(d, "_ThreadHandle",
+ (PyObject *)&_PyThreadHandle_Type) < 0)
+ {
return -1;
}
@@ -2053,7 +1909,6 @@ thread_module_traverse(PyObject *module, visitproc visit, void *arg)
Py_VISIT(state->lock_type);
Py_VISIT(state->local_type);
Py_VISIT(state->local_dummy_type);
- Py_VISIT(state->thread_handle_type);
return 0;
}
@@ -2065,7 +1920,6 @@ thread_module_clear(PyObject *module)
Py_CLEAR(state->lock_type);
Py_CLEAR(state->local_type);
Py_CLEAR(state->local_dummy_type);
- Py_CLEAR(state->thread_handle_type);
return 0;
}
diff --git a/Objects/object.c b/Objects/object.c
index df14fe0c6fbfec..d659c24dc987f4 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2301,6 +2301,7 @@ static PyTypeObject* static_types[] = {
&_PyNone_Type,
&_PyNotImplemented_Type,
&_PyPositionsIterator,
+ &_PyThreadHandle_Type,
&_PyUnicodeASCIIIter_Type,
&_PyUnion_Type,
&_PyUOpExecutor_Type,
diff --git a/Objects/threadhandleobject.c b/Objects/threadhandleobject.c
new file mode 100644
index 00000000000000..138fa36836f82a
--- /dev/null
+++ b/Objects/threadhandleobject.c
@@ -0,0 +1,361 @@
+/* ThreadHandle object (see Modules/_threadmodule.c) */
+
+#include "Python.h"
+#include "pycore_lock.h" // _PyEventRc
+#include "pycore_pystate.h" // HEAD_LOCK()
+#include "pycore_pythread.h" // ThreadHandleState
+#include "pycore_runtime.h" // _PyRuntime
+
+
+// ThreadHandleError is just an alias to PyExc_RuntimeError.
+#define ThreadHandleError PyExc_RuntimeError
+
+
+/**********************/
+/* thread handle data */
+/**********************/
+
+// A handle around an OS thread.
+//
+// The OS thread is either joined or detached after the handle is destroyed.
+//
+// Joining the handle is idempotent; the underlying OS thread is joined or
+// detached only once. Concurrent join operations are serialized until it is
+// their turn to execute or an earlier operation completes successfully. Once a
+// join has completed successfully all future joins complete immediately.
+struct _PyThread_handle_data {
+ struct llist_node fork_node; // linked list node (see _pythread_runtime_state)
+
+ // The `ident` and `handle` fields are immutable once the object is visible
+ // to threads other than its creator, thus they do not need to be accessed
+ // atomically.
+ PyThread_ident_t ident;
+ PyThread_handle_t handle;
+
+ // Holds a value from the `ThreadHandleState` enum.
+ int state;
+
+ // Set immediately before `thread_run` returns to indicate that the OS
+ // thread is about to exit. This is used to avoid false positives when
+ // detecting self-join attempts. See the comment in `ThreadHandle_join()`
+ // for a more detailed explanation.
+ _PyEventRc *thread_is_exiting;
+
+ // Serializes calls to `join`.
+ _PyOnceFlag once;
+};
+
+
+static void track_thread_handle_for_fork(struct _PyThread_handle_data *);
+
+static struct _PyThread_handle_data *
+new_thread_handle_data(void)
+{
+ _PyEventRc *event = _PyEventRc_New();
+ if (event == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ struct _PyThread_handle_data *data = \
+ PyMem_RawMalloc(sizeof(struct _PyThread_handle_data));
+ if (data == NULL) {
+ _PyEventRc_Decref(event);
+ return NULL;
+ }
+ *data = (struct _PyThread_handle_data){
+ .thread_is_exiting = event,
+ .state = THREAD_HANDLE_INVALID,
+ };
+
+ track_thread_handle_for_fork(data);
+
+ return data;
+}
+
+static inline int get_thread_handle_state(struct _PyThread_handle_data *);
+static inline void set_thread_handle_state(
+ struct _PyThread_handle_data *, _PyThreadHandleState);
+static void untrack_thread_handle_for_fork(struct _PyThread_handle_data *);
+
+
+static void
+free_thread_handle_data(struct _PyThread_handle_data *data)
+{
+ if (get_thread_handle_state(data) == THREAD_HANDLE_RUNNING) {
+ // This is typically short so no need to release the GIL
+ if (PyThread_detach_thread(data->handle)) {
+ PyErr_SetString(ThreadHandleError, "Failed detaching thread");
+ PyErr_WriteUnraisable(NULL);
+ }
+ else {
+ set_thread_handle_state(data, THREAD_HANDLE_DETACHED);
+ }
+ }
+ untrack_thread_handle_for_fork(data);
+ _PyEventRc_Decref(data->thread_is_exiting);
+ PyMem_RawFree(data);
+}
+
+
+/***************************/
+/* internal implementation */
+/***************************/
+
+static inline int
+get_thread_handle_state(struct _PyThread_handle_data *data)
+{
+ return _Py_atomic_load_int(&data->state);
+}
+
+static inline void
+set_thread_handle_state(struct _PyThread_handle_data *data,
+ _PyThreadHandleState state)
+{
+ _Py_atomic_store_int(&data->state, state);
+}
+
+static int
+_join_thread(struct _PyThread_handle_data *data)
+{
+ assert(get_thread_handle_state(data) == THREAD_HANDLE_RUNNING);
+
+ int err;
+ Py_BEGIN_ALLOW_THREADS
+ err = PyThread_join_thread(data->handle);
+ Py_END_ALLOW_THREADS
+ if (err) {
+ PyErr_SetString(ThreadHandleError, "Failed joining thread");
+ return -1;
+ }
+ set_thread_handle_state(data, THREAD_HANDLE_JOINED);
+ return 0;
+}
+
+static int
+join_thread(struct _PyThread_handle_data *data)
+{
+ if (get_thread_handle_state(data) == THREAD_HANDLE_INVALID) {
+ PyErr_SetString(PyExc_ValueError,
+ "the handle is invalid and thus cannot be joined");
+ return -1;
+ }
+
+ // We want to perform this check outside of the `_PyOnceFlag` to prevent
+ // deadlock in the scenario where another thread joins us and we then
+ // attempt to join ourselves. However, it's not safe to check thread
+ // identity once the handle's os thread has finished. We may end up reusing
+ // the identity stored in the handle and erroneously think we are
+ // attempting to join ourselves.
+ //
+ // To work around this, we set `thread_is_exiting` immediately before
+ // `thread_run` returns. We can be sure that we are not attempting to join
+ // ourselves if the handle's thread is about to exit.
+ if (!_PyEvent_IsSet(&data->thread_is_exiting->event) &&
+ data->ident == PyThread_get_thread_ident_ex())
+ {
+ // PyThread_join_thread() would deadlock or error out.
+ PyErr_SetString(ThreadHandleError, "Cannot join current thread");
+ return -1;
+ }
+
+ if (_PyOnceFlag_CallOnce(
+ &data->once, (_Py_once_fn_t *)_join_thread, data) == -1)
+ {
+ return -1;
+ }
+ assert(get_thread_handle_state(data) == THREAD_HANDLE_JOINED);
+ return 0;
+}
+
+
+/*********************************/
+/* thread handle C-API functions */
+/*********************************/
+
+static void
+_PyThread_SetStarted(struct _PyThread_handle_data *data,
+ PyThread_handle_t handle, PyThread_ident_t ident)
+{
+ assert(get_thread_handle_state(data) == THREAD_HANDLE_INVALID);
+ data->handle = handle;
+ data->ident = ident;
+ set_thread_handle_state(data, THREAD_HANDLE_RUNNING);
+}
+
+
+static _PyEventRc *
+_PyThread_GetExitingEvent(struct _PyThread_handle_data *data)
+{
+ return data->thread_is_exiting;
+}
+
+
+/*************************/
+/* _ThreadHandle objects */
+/*************************/
+
+typedef struct {
+ PyObject_HEAD
+
+ struct _PyThread_handle_data *data;
+} thandleobject;
+
+
+PyObject *
+_PyThreadHandle_NewObject(void)
+{
+ thandleobject *self = PyObject_New(thandleobject, &_PyThreadHandle_Type);
+ if (self == NULL) {
+ return NULL;
+ }
+ self->data = new_thread_handle_data();
+ if (self->data == NULL) {
+ Py_DECREF(self);
+ return NULL;
+ }
+
+ return (PyObject *)self;
+}
+
+
+/* _ThreadHandle instance methods */
+
+static PyObject *
+ThreadHandle_join(thandleobject *self, void* ignored)
+{
+ if (join_thread(self->data) < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyMethodDef ThreadHandle_methods[] =
+{
+ {"join", (PyCFunction)ThreadHandle_join, METH_NOARGS},
+ {0, 0}
+};
+
+
+/* _ThreadHandle instance properties */
+
+static PyObject *
+ThreadHandle_get_ident(thandleobject *self, void *ignored)
+{
+ return PyLong_FromUnsignedLongLong(self->data->ident);
+}
+
+
+static PyGetSetDef ThreadHandle_getsetlist[] = {
+ {"ident", (getter)ThreadHandle_get_ident, NULL, NULL},
+ {0},
+};
+
+
+/* The _ThreadHandle class */
+
+static void
+ThreadHandle_dealloc(thandleobject *self)
+{
+ // It's safe to access state non-atomically:
+ // 1. This is the destructor; nothing else holds a reference.
+ // 2. The refcount going to zero is a "synchronizes-with" event;
+ // all changes from other threads are visible.
+ free_thread_handle_data(self->data);
+ PyObject_Free(self);
+}
+
+
+static PyObject *
+ThreadHandle_repr(thandleobject *self)
+{
+ return PyUnicode_FromFormat("<%s object: ident=%" PY_FORMAT_THREAD_IDENT_T ">",
+ Py_TYPE(self)->tp_name, self->data->ident);
+}
+
+
+PyTypeObject _PyThreadHandle_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ .tp_name = "_ThreadHandle",
+ .tp_basicsize = sizeof(thandleobject),
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
+ .tp_dealloc = (destructor)ThreadHandle_dealloc,
+ .tp_repr = (reprfunc)ThreadHandle_repr,
+ .tp_getset = ThreadHandle_getsetlist,
+ .tp_methods = ThreadHandle_methods,
+};
+
+
+/************************************/
+/* tracking thread handles for fork */
+/************************************/
+
+// XXX Track the handles instead of the objects.
+
+static void
+track_thread_handle_for_fork(struct _PyThread_handle_data *data)
+{
+ HEAD_LOCK(&_PyRuntime);
+ llist_insert_tail(&_PyRuntime.threads.handles, &data->fork_node);
+ HEAD_UNLOCK(&_PyRuntime);
+}
+
+static void
+untrack_thread_handle_for_fork(struct _PyThread_handle_data *data)
+{
+ HEAD_LOCK(&_PyRuntime);
+ if (data->fork_node.next != NULL) {
+ llist_remove(&data->fork_node);
+ }
+ HEAD_UNLOCK(&_PyRuntime);
+}
+
+static void
+clear_tracked_thread_handles(struct _pythread_runtime_state *state,
+ PyThread_ident_t current)
+{
+ struct llist_node *node;
+ llist_for_each_safe(node, &state->handles) {
+ struct _PyThread_handle_data *data = llist_data(
+ node, struct _PyThread_handle_data, fork_node);
+ if (data->ident == current) {
+ continue;
+ }
+
+ // Disallow calls to join() as they could crash. We are the only
+ // thread; it's safe to set this without an atomic.
+ data->state = THREAD_HANDLE_INVALID;
+ llist_remove(node);
+ }
+}
+
+
+/*************/
+/* other API */
+/*************/
+
+void
+_PyThreadHandle_SetStarted(PyObject *hobj,
+ PyThread_handle_t handle, PyThread_ident_t ident)
+{
+ _PyThread_SetStarted(((thandleobject *)hobj)->data, handle, ident);
+}
+
+
+_PyEventRc *
+_PyThreadHandle_GetExitingEvent(PyObject *hobj)
+{
+ return _PyThread_GetExitingEvent(((thandleobject *)hobj)->data);
+}
+
+
+void
+_PyThread_AfterFork(struct _pythread_runtime_state *state)
+{
+ // gh-115035: We mark ThreadHandles as not joinable early in the child's
+ // after-fork handler. We do this before calling any Python code to ensure
+ // that it happens before any ThreadHandles are deallocated, such as by a
+ // GC cycle.
+ PyThread_ident_t current = PyThread_get_thread_ident_ex();
+ clear_tracked_thread_handles(state, current);
+}
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index 88a4a7c9564309..e7b7f487eef768 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -520,6 +520,7 @@
+
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index 27bd1121663398..9c51373331da8c 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -1184,6 +1184,9 @@
Objects
+
+ Objects
+
Objects