diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index db4e1eb9999c1f..6ec6c7fb436b98 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -289,6 +289,14 @@ def test_time_fail(self): self.assertNotEqual(returncode, 0) self.assertIn('hook failed', stderr.splitlines()[-1]) + def test_time_leak(self): + import sys + import time # Leaks struct_time_type + + hook = lambda a, b: None + sys.addaudithook(hook) + t = time.localtime() + def test_sys_monitoring_register_callback(self): returncode, events, stderr = self.run_python("test_sys_monitoring_register_callback") if returncode: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-31-17-47-44.gh-issue-140860.hexT7T.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-31-17-47-44.gh-issue-140860.hexT7T.rst new file mode 100644 index 00000000000000..0a91f4033cf9bb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-31-17-47-44.gh-issue-140860.hexT7T.rst @@ -0,0 +1,2 @@ +Fix memory leak where modules that create types during initialization +leak those types when audit hooks are active. diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 8fcb31cfd12299..2e2fc499379ba5 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2251,6 +2251,9 @@ _Py_Finalize(_PyRuntimeState *runtime) */ PyGC_Collect(); + /* Clear audit hooks before destroying modules to avoid reference cycles */ + Py_CLEAR(tstate->interp->audit_hooks); + /* Destroy all modules */ _PyImport_FiniExternal(tstate->interp); finalize_modules(tstate);