Skip to content

Commit e9d9fd9

Browse files
[3.15] gh-154431: Fix data race in sys.audithook (GH-154462) (#154477)
gh-154431: Fix data race in `sys.audithook` (GH-154462) (cherry picked from commit 596cd5c) Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent 7676f95 commit e9d9fd9

5 files changed

Lines changed: 35 additions & 8 deletions

File tree

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ struct _is {
988988
struct _obmalloc_state *obmalloc;
989989

990990
PyObject *audit_hooks;
991+
PyMutex audit_hooks_mutex;
991992
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
992993
PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
993994
PyContext_WatchCallback context_watchers[CONTEXT_MAX_WATCHERS];

Lib/test/test_free_threading/test_sys.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ def worker(worker_id):
4444
workers = [lambda: worker(i) for i in range(5)]
4545
threading_helper.run_concurrently(workers)
4646

47+
def test_sys_audit_hooks(self):
48+
def _hook(*args):
49+
return None
50+
51+
def adder():
52+
for _ in range(100):
53+
sys.addaudithook(_hook)
54+
55+
def auditor():
56+
for _ in range(2000):
57+
sys.audit("fusil.tsan.test")
58+
59+
threading_helper.run_concurrently([adder, auditor])
60+
4761

4862
if __name__ == "__main__":
4963
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes a data race in free-threading build in :func:`sys.addaudithook`.

Python/pystate.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ init_interpreter(PyInterpreterState *interp,
580580
llist_init(&interp->mem_free_queue.head);
581581
llist_init(&interp->asyncio_tasks_head);
582582
interp->asyncio_tasks_lock = (PyMutex){0};
583+
interp->audit_hooks_mutex = (PyMutex){0};
583584
for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
584585
interp->monitors.tools[i] = 0;
585586
}

Python/sysmodule.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ should_audit(PyInterpreterState *interp)
236236
return 0;
237237
}
238238
return (interp->runtime->audit_hooks.head
239-
|| interp->audit_hooks
239+
|| FT_ATOMIC_LOAD_PTR_ACQUIRE(interp->audit_hooks)
240240
|| PyDTrace_AUDIT_ENABLED());
241241
}
242242

@@ -306,13 +306,14 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
306306
}
307307

308308
/* Call interpreter hooks */
309-
if (is->audit_hooks) {
309+
PyObject *audit_hooks = FT_ATOMIC_LOAD_PTR_ACQUIRE(is->audit_hooks);
310+
if (audit_hooks) {
310311
eventName = PyUnicode_FromString(event);
311312
if (!eventName) {
312313
goto exit;
313314
}
314315

315-
hooks = PyObject_GetIter(is->audit_hooks);
316+
hooks = PyObject_GetIter(audit_hooks);
316317
if (!hooks) {
317318
goto exit;
318319
}
@@ -536,20 +537,29 @@ sys_addaudithook_impl(PyObject *module, PyObject *hook)
536537
}
537538

538539
PyInterpreterState *interp = tstate->interp;
540+
PyMutex mutex = interp->audit_hooks_mutex;
541+
PyMutex_Lock(&mutex);
542+
539543
if (interp->audit_hooks == NULL) {
540-
interp->audit_hooks = PyList_New(0);
541-
if (interp->audit_hooks == NULL) {
542-
return NULL;
544+
PyObject *new_list = PyList_New(0);
545+
if (new_list == NULL) {
546+
goto error;
543547
}
544548
/* Avoid having our list of hooks show up in the GC module */
545-
PyObject_GC_UnTrack(interp->audit_hooks);
549+
PyObject_GC_UnTrack(new_list);
550+
FT_ATOMIC_STORE_PTR_RELEASE(interp->audit_hooks, new_list);
546551
}
547552

548553
if (PyList_Append(interp->audit_hooks, hook) < 0) {
549-
return NULL;
554+
goto error;
550555
}
551556

557+
PyMutex_Unlock(&mutex);
552558
Py_RETURN_NONE;
559+
560+
error:
561+
PyMutex_Unlock(&mutex);
562+
return NULL;
553563
}
554564

555565
/*[clinic input]

0 commit comments

Comments
 (0)