Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Track all states of the handle in the enum
  • Loading branch information
mpage committed Feb 12, 2024
commit 321a4e089fde56448f9e62483c832a8155ac4ca8
69 changes: 46 additions & 23 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ get_thread_state(PyObject *module)

// _ThreadHandle type

// Handles transition from RUNNING to one of JOINED, DETACHED, or INVALID (post
// fork).
typedef enum {
THREAD_HANDLE_RUNNING,
THREAD_HANDLE_JOINED,
THREAD_HANDLE_DETACHED,
THREAD_HANDLE_INVALID,
} ThreadHandleState;
Comment thread
mpage marked this conversation as resolved.
Comment thread
mpage marked this conversation as resolved.

// A handle to join or detach an OS thread.
Expand All @@ -63,19 +67,29 @@ typedef struct {
PyThread_ident_t ident;
PyThread_handle_t handle;

// Set before a handle is accessible to any threads other than its creator
// and cleared post-fork. Does not need to be accessed atomically.
bool is_valid;
// 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.
Comment thread
mpage marked this conversation as resolved.
Outdated
_PyEventRc *thread_is_exiting;

// State is set once by the first successful `join` or `detach` operation.
ThreadHandleState state;
// Serializes calls to `join` and `detach`.
_PyOnceFlag once;
Comment thread
mpage marked this conversation as resolved.
} ThreadHandleObject;

static inline int
get_thread_handle_state(ThreadHandleObject *handle)
{
return _Py_atomic_load_int_relaxed(&handle->state);
Comment thread
mpage marked this conversation as resolved.
Outdated
}

static inline void
set_thread_handle_state(ThreadHandleObject *handle, ThreadHandleState state)
{
_Py_atomic_store_int_relaxed(&handle->state, state);
}

static ThreadHandleObject*
new_thread_handle(thread_module_state* state)
{
Expand All @@ -91,10 +105,11 @@ new_thread_handle(thread_module_state* state)
}
self->ident = 0;
self->handle = 0;
self->is_valid = false;
self->thread_is_exiting = event;
self->once = (_PyOnceFlag){0};

set_thread_handle_state(self, THREAD_HANDLE_INVALID);
Comment thread
mpage marked this conversation as resolved.
Outdated

HEAD_LOCK(&_PyRuntime);
llist_insert_tail(&_PyRuntime.threads.handles, &self->node);
HEAD_UNLOCK(&_PyRuntime);
Expand All @@ -105,12 +120,14 @@ new_thread_handle(thread_module_state* state)
static int
detach_thread(ThreadHandleObject *handle)
{
assert(get_thread_handle_state(handle) == THREAD_HANDLE_RUNNING);

// This is typically short so no need to release the GIL
if (PyThread_detach_thread(handle->handle)) {
PyErr_SetString(ThreadError, "Failed detaching thread");
return -1;
}
handle->state = THREAD_HANDLE_DETACHED;
set_thread_handle_state(handle, THREAD_HANDLE_DETACHED);
Comment thread
mpage marked this conversation as resolved.
Outdated
return 0;
}

Expand All @@ -126,7 +143,7 @@ ThreadHandle_dealloc(ThreadHandleObject *self)
}
HEAD_UNLOCK(&_PyRuntime);

if (self->is_valid &&
if (get_thread_handle_state(self) != THREAD_HANDLE_INVALID &&
_PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)detach_thread,
Comment thread
mpage marked this conversation as resolved.
Outdated
self) == -1) {
PyErr_WriteUnraisable(tp);
Comment thread
pitrou marked this conversation as resolved.
Outdated
Expand All @@ -152,8 +169,9 @@ _PyThread_AfterFork(struct _pythread_runtime_state *state)
continue;
}

// Disallow calls to detach() and join() as they could crash.
hobj->is_valid = false;
// Disallow calls to detach() and join() as they could crash. We are
// the only thread; its safe to do this without an atomic.
hobj->state = THREAD_HANDLE_INVALID;
llist_remove(node);
}
}
Expand All @@ -171,27 +189,30 @@ ThreadHandle_get_ident(ThreadHandleObject *self, void *ignored)
return PyLong_FromUnsignedLongLong(self->ident);
}

static PyObject *
invalid_handle_error(void)
static bool
check_handle_valid(ThreadHandleObject *handle)
{
PyErr_SetString(PyExc_ValueError,
"the handle is invalid and thus cannot be detached");
return NULL;
if (get_thread_handle_state(handle) == THREAD_HANDLE_INVALID) {
PyErr_SetString(PyExc_ValueError,
"the handle is invalid and thus cannot be detached");
return false;
}
return true;
}

static PyObject *
ThreadHandle_detach(ThreadHandleObject *self, void* ignored)
{
if (!self->is_valid) {
return invalid_handle_error();
if (!check_handle_valid(self)) {
return NULL;
}

if (_PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)detach_thread,
self) == -1) {
return NULL;
}

switch (self->state) {
switch (get_thread_handle_state(self)) {
case THREAD_HANDLE_DETACHED: {
Py_RETURN_NONE;
}
Expand All @@ -210,6 +231,8 @@ ThreadHandle_detach(ThreadHandleObject *self, void* ignored)
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);
Expand All @@ -218,15 +241,15 @@ join_thread(ThreadHandleObject *handle)
PyErr_SetString(ThreadError, "Failed joining thread");
return -1;
}
handle->state = THREAD_HANDLE_JOINED;
set_thread_handle_state(handle, THREAD_HANDLE_JOINED);
return 0;
}

static PyObject *
ThreadHandle_join(ThreadHandleObject *self, void* ignored)
{
if (!self->is_valid) {
return invalid_handle_error();
if (!check_handle_valid(self)) {
return NULL;
}

// We want to perform this check outside of the `_PyOnceFlag` to prevent
Expand All @@ -251,7 +274,7 @@ ThreadHandle_join(ThreadHandleObject *self, void* ignored)
return NULL;
}

switch (self->state) {
switch (get_thread_handle_state(self)) {
case THREAD_HANDLE_DETACHED: {
PyErr_SetString(
PyExc_ValueError,
Expand Down Expand Up @@ -1543,7 +1566,7 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *func)
return NULL;
}
Py_DECREF(args);
hobj->is_valid = true;
set_thread_handle_state(hobj, THREAD_HANDLE_RUNNING);
Comment thread
mpage marked this conversation as resolved.
return (PyObject*) hobj;
}

Expand Down