From aae4ad7838b665cdf6e4f5688c02d46c7e2bd9f7 Mon Sep 17 00:00:00 2001 From: Abdul Date: Mon, 15 Sep 2025 12:13:37 +0100 Subject: [PATCH 1/2] gh-137017: Ensure `Thread.is_alive()` only returns False after the underlying OS thread exits (gh-137315) (cherry picked from commit aa9ceb17215af21ed6618d6f7ccb5bf57ca57101) Co-authored-by: Abdul --- .../Library/2025-08-01-23-11-25.gh-issue-137017.0yGcNc.rst | 3 +++ Modules/_threadmodule.c | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-08-01-23-11-25.gh-issue-137017.0yGcNc.rst diff --git a/Misc/NEWS.d/next/Library/2025-08-01-23-11-25.gh-issue-137017.0yGcNc.rst b/Misc/NEWS.d/next/Library/2025-08-01-23-11-25.gh-issue-137017.0yGcNc.rst new file mode 100644 index 00000000000000..7c2c013016d72e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-01-23-11-25.gh-issue-137017.0yGcNc.rst @@ -0,0 +1,3 @@ +Fix :obj:`threading.Thread.is_alive` to remain ``True`` until the underlying OS +thread is fully cleaned up. This avoids false negatives in edge cases +involving thread monitoring or premature :obj:`threading.Thread.is_alive` calls. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 3f9e0a948568ca..cc9262f397872d 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -657,6 +657,9 @@ PyThreadHandleObject_is_done(PyThreadHandleObject *self, PyObject *Py_UNUSED(ignored)) { if (_PyEvent_IsSet(&self->handle->thread_is_exiting)) { + if (_PyOnceFlag_CallOnce(&self->handle->once, join_thread, self->handle) == -1) { + return NULL; + } Py_RETURN_TRUE; } else { From c7cb7f3ab0a6cb87930fe51fcef410c49a14d267 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 15 Sep 2025 08:19:11 -0400 Subject: [PATCH 2/2] Fix function pointer type --- Modules/_threadmodule.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index cc9262f397872d..bc9433afe41ef1 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -453,8 +453,9 @@ ThreadHandle_start(ThreadHandle *self, PyObject *func, PyObject *args, } static int -join_thread(ThreadHandle *handle) +join_thread(void *arg) { + ThreadHandle *handle = (ThreadHandle*)arg; assert(get_thread_handle_state(handle) == THREAD_HANDLE_RUNNING); PyThread_handle_t os_handle; if (ThreadHandle_get_os_handle(handle, &os_handle)) { @@ -528,8 +529,7 @@ ThreadHandle_join(ThreadHandle *self, PyTime_t timeout_ns) } } - if (_PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)join_thread, - self) == -1) { + if (_PyOnceFlag_CallOnce(&self->once, join_thread, self) == -1) { return -1; } assert(get_thread_handle_state(self) == THREAD_HANDLE_DONE);