From 5abe26e2b2ba7a3a2bc3515bb6917c25cf816490 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 22 Jul 2026 15:09:20 +0300 Subject: [PATCH] gh-154431: Fix data race in `sys.audithook` (GH-154462) (cherry picked from commit 596cd5c5d7b6ada3e50c6499447eb90a065769ad) Co-authored-by: sobolevn --- Include/internal/pycore_interp_structs.h | 1 + Lib/test/test_free_threading/test_sys.py | 14 ++++++++++ ...-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst | 1 + Python/pystate.c | 1 + Python/sysmodule.c | 26 +++++++++++++------ 5 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index b93d6d3c8eda8b3..36203d8b1818092 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -936,6 +936,7 @@ struct _is { struct _obmalloc_state *obmalloc; PyObject *audit_hooks; + PyMutex audit_hooks_mutex; PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS]; PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS]; PyContext_WatchCallback context_watchers[CONTEXT_MAX_WATCHERS]; diff --git a/Lib/test/test_free_threading/test_sys.py b/Lib/test/test_free_threading/test_sys.py index 271fdd13c62b668..b8ba933cb01adc0 100644 --- a/Lib/test/test_free_threading/test_sys.py +++ b/Lib/test/test_free_threading/test_sys.py @@ -44,6 +44,20 @@ def worker(worker_id): workers = [lambda: worker(i) for i in range(5)] threading_helper.run_concurrently(workers) + def test_sys_audit_hooks(self): + def _hook(*args): + return None + + def adder(): + for _ in range(100): + sys.addaudithook(_hook) + + def auditor(): + for _ in range(2000): + sys.audit("fusil.tsan.test") + + threading_helper.run_concurrently([adder, auditor]) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst b/Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst new file mode 100644 index 000000000000000..a56c3b3a6f21f3a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst @@ -0,0 +1 @@ +Fixes a data race in free-threading build in :func:`sys.addaudithook`. diff --git a/Python/pystate.c b/Python/pystate.c index d1505be4acf50f2..8e7296f016c895e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -661,6 +661,7 @@ init_interpreter(PyInterpreterState *interp, llist_init(&interp->mem_free_queue.head); llist_init(&interp->asyncio_tasks_head); interp->asyncio_tasks_lock = (PyMutex){0}; + interp->audit_hooks_mutex = (PyMutex){0}; for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) { interp->monitors.tools[i] = 0; } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index eeb9ef2fbffd64a..76bb8e8edd06384 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -235,7 +235,7 @@ should_audit(PyInterpreterState *interp) return 0; } return (interp->runtime->audit_hooks.head - || interp->audit_hooks + || FT_ATOMIC_LOAD_PTR_ACQUIRE(interp->audit_hooks) || PyDTrace_AUDIT_ENABLED()); } @@ -305,13 +305,14 @@ sys_audit_tstate(PyThreadState *ts, const char *event, } /* Call interpreter hooks */ - if (is->audit_hooks) { + PyObject *audit_hooks = FT_ATOMIC_LOAD_PTR_ACQUIRE(is->audit_hooks); + if (audit_hooks) { eventName = PyUnicode_FromString(event); if (!eventName) { goto exit; } - hooks = PyObject_GetIter(is->audit_hooks); + hooks = PyObject_GetIter(audit_hooks); if (!hooks) { goto exit; } @@ -535,20 +536,29 @@ sys_addaudithook_impl(PyObject *module, PyObject *hook) } PyInterpreterState *interp = tstate->interp; + PyMutex mutex = interp->audit_hooks_mutex; + PyMutex_Lock(&mutex); + if (interp->audit_hooks == NULL) { - interp->audit_hooks = PyList_New(0); - if (interp->audit_hooks == NULL) { - return NULL; + PyObject *new_list = PyList_New(0); + if (new_list == NULL) { + goto error; } /* Avoid having our list of hooks show up in the GC module */ - PyObject_GC_UnTrack(interp->audit_hooks); + PyObject_GC_UnTrack(new_list); + FT_ATOMIC_STORE_PTR_RELEASE(interp->audit_hooks, new_list); } if (PyList_Append(interp->audit_hooks, hook) < 0) { - return NULL; + goto error; } + PyMutex_Unlock(&mutex); Py_RETURN_NONE; + +error: + PyMutex_Unlock(&mutex); + return NULL; } /*[clinic input]