https://github.com/python/cpython/commit/596cd5c5d7b6ada3e50c6499447eb90a065769ad
commit: 596cd5c5d7b6ada3e50c6499447eb90a065769ad
branch: main
author: sobolevn <[email protected]>
committer: ambv <[email protected]>
date: 2026-07-22T14:09:20+02:00
summary:
gh-154431: Fix data race in `sys.audithook` (#154462)
files:
A Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst
M Include/internal/pycore_interp_structs.h
M Lib/test/test_free_threading/test_sys.py
M Python/pystate.c
M Python/sysmodule.c
diff --git a/Include/internal/pycore_interp_structs.h
b/Include/internal/pycore_interp_structs.h
index 0623adce693d46..3d577ff717cc6e 100644
--- a/Include/internal/pycore_interp_structs.h
+++ b/Include/internal/pycore_interp_structs.h
@@ -977,6 +977,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 271fdd13c62b66..b8ba933cb01adc 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 00000000000000..a56c3b3a6f21f3
--- /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 d10b38def32911..b6c34552e94337 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -578,6 +578,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 9442472b53abbe..207ccbce41d71d 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -236,7 +236,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());
}
@@ -306,13 +306,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;
}
@@ -536,20 +537,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]
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]