Skip to content
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

gh-104341: Fix threading Module Shutdown #104560

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Add _thread._wait_for_threads_fini().
  • Loading branch information
ericsnowcurrently committed May 16, 2023
commit ad9bbcec3cac3a9509fa9491921fee19a6d79b0f
5 changes: 5 additions & 0 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
except AttributeError:
_CRLock = None
TIMEOUT_MAX = _thread.TIMEOUT_MAX
_wait_for_threads_fini = _thread._wait_for_threads_fini
_internal_after_fork = _thread._after_fork
del _thread

Expand Down Expand Up @@ -1590,6 +1591,7 @@ def _shutdown():
pass

# Join all non-deamon threads
# XXX We should be able to drop this in favor of _wait_for_threads_fini().
while True:
with _shutdown_locks_lock:
locks = list(_shutdown_locks)
Expand All @@ -1606,6 +1608,9 @@ def _shutdown():
# new threads can be spawned while we were waiting for the other
# threads to complete

# Wait for all non-daemon threads to be finalized.
_wait_for_threads_fini()


def main_thread():
"""Return the main thread object.
Expand Down
41 changes: 41 additions & 0 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,37 @@ remove_module_thread(struct module_threads *threads, struct module_thread *mt)
PyMem_RawFree(mt);
}

static void
wait_for_threads_fini(struct module_threads *threads)
{
Py_BEGIN_ALLOW_THREADS

int done = 0;
while (!done) {
PyThread_acquire_lock(threads->mutex, WAIT_LOCK);

done = 1;
struct module_thread *mt = threads->head;
while (mt != NULL) {
if (!mt->daemonic) {
// Wait for the next thread to be finalized.
if (PyThread_acquire_lock(mt->lifetime_mutex, NOWAIT_LOCK)) {
PyThread_release_lock(mt->lifetime_mutex);
}
else {
done = 0;
break;
}
}
mt = mt->next;
}

PyThread_release_lock(threads->mutex);
}

Py_END_ALLOW_THREADS
}


/* module state */

Expand Down Expand Up @@ -1780,6 +1811,14 @@ PyDoc_STRVAR(excepthook_doc,
Handle uncaught Thread.run() exception.");

#ifdef HAVE_FORK
static PyObject *
thread__wait_for_threads_fini(PyObject *module, PyObject *Py_UNUSED(ignored))
{
thread_module_state *state = get_thread_state(module);
wait_for_threads_fini(&state->threads);
Py_RETURN_NONE;
}

static PyObject *
thread__after_fork(PyObject *module, PyObject *Py_UNUSED(ignored))
{
Expand Down Expand Up @@ -1822,6 +1861,8 @@ static PyMethodDef thread_methods[] = {
METH_NOARGS, _set_sentinel_doc},
{"_excepthook", thread_excepthook,
METH_O, excepthook_doc},
{"_wait_for_threads_fini", thread__wait_for_threads_fini,
METH_NOARGS, NULL},
#ifdef HAVE_FORK
{"_after_fork", thread__after_fork,
METH_NOARGS, NULL},
Expand Down