Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

gh-104341: Clean Up threading Module #104552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Drop tstate->on_delete.
  • Loading branch information
ericsnowcurrently committed May 16, 2023
commit 86e5d821b0911019b25b284d28e19efeeb19debe
26 changes: 0 additions & 26 deletions 26 Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,32 +188,6 @@ struct _ts {

struct _py_trashcan trash;

/* Called when a thread state is deleted normally, but not when it
* is destroyed after fork().
* Pain: to prevent rare but fatal shutdown errors (issue 18808),
* Thread.join() must wait for the join'ed thread's tstate to be unlinked
* from the tstate chain. That happens at the end of a thread's life,
* in pystate.c.
* The obvious way doesn't quite work: create a lock which the tstate
* unlinking code releases, and have Thread.join() wait to acquire that
* lock. The problem is that we _are_ at the end of the thread's life:
* if the thread holds the last reference to the lock, decref'ing the
* lock will delete the lock, and that may trigger arbitrary Python code
* if there's a weakref, with a callback, to the lock. But by this time
* _PyRuntime.gilstate.tstate_current is already NULL, so only the simplest
* of C code can be allowed to run (in particular it must not be possible to
* release the GIL).
* So instead of holding the lock directly, the tstate holds a weakref to
* the lock: that's the value of on_delete_data below. Decref'ing a
* weakref is harmless.
* on_delete points to _threadmodule.c's static release_sentinel() function.
* After the tstate is unlinked, release_sentinel is called with the
* weakref-to-lock (on_delete_data) argument, and release_sentinel releases
* the indirectly held lock.
*/
void (*on_delete)(void *);
void *on_delete_data;

int coroutine_origin_tracking_depth;

PyObject *async_gen_firstiter;
Expand Down
45 changes: 32 additions & 13 deletions 45 Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,29 @@ struct module_thread {
PyThreadState *tstate;
/* This lock is released right after the Python code finishes. */
PyObject *running_lock;
/* Called when a thread state is deleted normally, but not when it
* is destroyed after fork().
* Pain: to prevent rare but fatal shutdown errors (issue 18808),
* Thread.join() must wait for the join'ed thread's tstate to be unlinked
* from the tstate chain. That happens at the end of a thread's life,
* in pystate.c.
* The obvious way doesn't quite work: create a lock which the tstate
* unlinking code releases, and have Thread.join() wait to acquire that
* lock. The problem is that we _are_ at the end of the thread's life:
* if the thread holds the last reference to the lock, decref'ing the
* lock will delete the lock, and that may trigger arbitrary Python code
* if there's a weakref, with a callback, to the lock. But by this time
* _PyRuntime.gilstate.tstate_current is already NULL, so only the simplest
* of C code can be allowed to run (in particular it must not be possible to
* release the GIL).
* So instead of holding the lock directly, the tstate holds a weakref to
* the lock: that's the value of lock_weakref. Decref'ing a
* weakref is harmless.
* After the tstate is unlinked, release_sentinel is called with the
* weakref-to-lock (lock_weakref) argument, and release_sentinel releases
* the indirectly held lock.
*/
PyObject *running_lock_weakref;
struct module_thread *prev;
struct module_thread *next;
};
Expand Down Expand Up @@ -154,6 +177,7 @@ add_module_thread(struct module_state *state, struct module_threads *threads,
PyErr_NoMemory();
return NULL;
}
mt->running_lock_weakref = NULL;

module_threads_add(threads, mt);

Expand Down Expand Up @@ -181,14 +205,15 @@ module_thread_finished(struct module_threads *threads, struct module_thread *mt)
// XXX We should be notifying other threads here.
}

static void release_sentinel(PyObject *wr);

static void
remove_module_thread(struct module_threads *threads, struct module_thread *mt)
{
// Notify other threads that this one is done.
// XXX This should happen in module_thread_finished().
// XXX These fields could be removed from PyThreadState.
if (mt->tstate->on_delete != NULL) {
mt->tstate->on_delete(mt->tstate->on_delete_data);
if (mt->running_lock_weakref != NULL) {
release_sentinel(mt->running_lock_weakref);
}

// Remove it from the list.
Expand Down Expand Up @@ -1497,9 +1522,8 @@ This function is meant for internal and specialized purposes only.\n\
In most applications `threading.enumerate()` should be used instead.");

static void
release_sentinel(void *wr_raw)
release_sentinel(PyObject *wr)
{
PyObject *wr = _PyObject_CAST(wr_raw);
/* Tricky: this function is called when the current thread state
is being deleted. Therefore, only simple C code can safely
execute here. */
Expand Down Expand Up @@ -1538,23 +1562,18 @@ thread__set_sentinel(PyObject *module, PyObject *Py_UNUSED(ignored))
}
lock = Py_NewRef(mt->running_lock);

if (tstate->on_delete_data != NULL) {
if (mt->running_lock_weakref != 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);
Py_CLEAR(mt->running_lock_weakref);
}
/* The lock is owned by whoever called _set_sentinel(), but the weakref
hangs to the thread state. */
wr = PyWeakref_NewRef(lock, NULL);
if (wr == NULL) {
return NULL;
}
tstate->on_delete_data = (void *) wr;
tstate->on_delete = &release_sentinel;
mt->running_lock_weakref = wr;

return lock;
}
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.