--- /dev/null
+#!/usr/bin/env python3
+
+# gh-115832: An object destructor running during the final GC of interpreter
+# shutdown triggered an infinite loop in the instrumentation code.
+
+import sys
+
+class CallableCycle:
+ def __init__(self):
+ self._cycle = self
+
+ def __del__(self):
+ pass
+
+ def __call__(self, code, instruction_offset):
+ pass
+
+def tracefunc(frame, event, arg):
+ pass
+
+def main():
+ tool_id = sys.monitoring.PROFILER_ID
+ event_id = sys.monitoring.events.PY_START
+
+ sys.monitoring.use_tool_id(tool_id, "test profiler")
+ sys.monitoring.set_events(tool_id, event_id)
+ sys.monitoring.register_callback(tool_id, event_id, CallableCycle())
+
+if __name__ == "__main__":
+ sys.exit(main())
import types
import unittest
import asyncio
-from test.support import requires_specialization
+from test import support
+from test.support import requires_specialization, script_helper
PAIR = (0,1)
sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
sys.monitoring.set_events(TEST_TOOL, 0)
self.assertGreater(len(events), 250)
+
+class TestMonitoringAtShutdown(unittest.TestCase):
+
+ def test_monitoring_live_at_shutdown(self):
+ # gh-115832: An object destructor running during the final GC of
+ # interpreter shutdown triggered an infinite loop in the
+ # instrumentation code.
+ script = support.findfile("_test_monitoring_shutdown.py")
+ script_helper.run_test_script(script)
static uint32_t
global_version(PyInterpreterState *interp)
{
- return (uint32_t)_Py_atomic_load_uintptr_relaxed(
+ uint32_t version = (uint32_t)_Py_atomic_load_uintptr_relaxed(
&interp->ceval.instrumentation_version);
+#ifdef Py_DEBUG
+ PyThreadState *tstate = _PyThreadState_GET();
+ uint32_t thread_version =
+ (uint32_t)(_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) &
+ ~_PY_EVAL_EVENTS_MASK);
+ assert(thread_version == version);
+#endif
+ return version;
}
/* Atomically set the given version in the given location, without touching
Py_CLEAR(interp->audit_hooks);
+ // At this time, all the threads should be cleared so we don't need atomic
+ // operations for instrumentation_version or eval_breaker.
interp->ceval.instrumentation_version = 0;
+ tstate->eval_breaker = 0;
for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
interp->monitors.tools[i] = 0;