Skip to content

Commit 5abe26e

Browse files
sobolevnmiss-islington
authored andcommitted
gh-154431: Fix data race in sys.audithook (GH-154462)
(cherry picked from commit 596cd5c) Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent 71fa9b4 commit 5abe26e

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
@@ -936,6 +936,7 @@ struct _is {
936936
struct _obmalloc_state *obmalloc;
937937

938938
PyObject *audit_hooks;
939+
PyMutex audit_hooks_mutex;
939940
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
940941
PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
941942
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
@@ -661,6 +661,7 @@ init_interpreter(PyInterpreterState *interp,
661661
llist_init(&interp->mem_free_queue.head);
662662
llist_init(&interp->asyncio_tasks_head);
663663
interp->asyncio_tasks_lock = (PyMutex){0};
664+
interp->audit_hooks_mutex = (PyMutex){0};
664665
for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
665666
interp->monitors.tools[i] = 0;
666667
}

Python/sysmodule.c

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

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

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

314-
hooks = PyObject_GetIter(is->audit_hooks);
315+
hooks = PyObject_GetIter(audit_hooks);
315316
if (!hooks) {
316317
goto exit;
317318
}
@@ -535,20 +536,29 @@ sys_addaudithook_impl(PyObject *module, PyObject *hook)
535536
}
536537

537538
PyInterpreterState *interp = tstate->interp;
539+
PyMutex mutex = interp->audit_hooks_mutex;
540+
PyMutex_Lock(&mutex);
541+
538542
if (interp->audit_hooks == NULL) {
539-
interp->audit_hooks = PyList_New(0);
540-
if (interp->audit_hooks == NULL) {
541-
return NULL;
543+
PyObject *new_list = PyList_New(0);
544+
if (new_list == NULL) {
545+
goto error;
542546
}
543547
/* Avoid having our list of hooks show up in the GC module */
544-
PyObject_GC_UnTrack(interp->audit_hooks);
548+
PyObject_GC_UnTrack(new_list);
549+
FT_ATOMIC_STORE_PTR_RELEASE(interp->audit_hooks, new_list);
545550
}
546551

547552
if (PyList_Append(interp->audit_hooks, hook) < 0) {
548-
return NULL;
553+
goto error;
549554
}
550555

556+
PyMutex_Unlock(&mutex);
551557
Py_RETURN_NONE;
558+
559+
error:
560+
PyMutex_Unlock(&mutex);
561+
return NULL;
552562
}
553563

554564
/*[clinic input]

0 commit comments

Comments
 (0)