mirror of https://github.com/python/cpython
gh-115832: Fix instrumentation version mismatch during interpreter shutdown (#115856)
A previous commit introduced a bug to `interpreter_clear()`: it set `interp->ceval.instrumentation_version` to 0, without making the corresponding change to `tstate->eval_breaker` (which holds a thread-local copy of the version). After this happens, Python code can still run due to object finalizers during a GC, and the version check in bytecodes.c will see a different result than the one in instrumentation.c causing an infinite loop. The fix itself is straightforward: clear `tstate->eval_breaker` when clearing `interp->ceval.instrumentation_version`.
This commit is contained in:
parent
15dc2979bc
commit
0adfa8482d
|
@ -0,0 +1,30 @@
|
||||||
|
#!/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())
|
|
@ -9,7 +9,8 @@ import textwrap
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
import asyncio
|
import asyncio
|
||||||
from test.support import requires_specialization
|
from test import support
|
||||||
|
from test.support import requires_specialization, script_helper
|
||||||
|
|
||||||
PAIR = (0,1)
|
PAIR = (0,1)
|
||||||
|
|
||||||
|
@ -1858,3 +1859,12 @@ class TestTier2Optimizer(CheckEvents):
|
||||||
sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
|
sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
|
||||||
sys.monitoring.set_events(TEST_TOOL, 0)
|
sys.monitoring.set_events(TEST_TOOL, 0)
|
||||||
self.assertGreater(len(events), 250)
|
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)
|
||||||
|
|
|
@ -891,8 +891,16 @@ static inline int most_significant_bit(uint8_t bits) {
|
||||||
static uint32_t
|
static uint32_t
|
||||||
global_version(PyInterpreterState *interp)
|
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);
|
&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
|
/* Atomically set the given version in the given location, without touching
|
||||||
|
|
|
@ -795,7 +795,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
|
||||||
|
|
||||||
Py_CLEAR(interp->audit_hooks);
|
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;
|
interp->ceval.instrumentation_version = 0;
|
||||||
|
tstate->eval_breaker = 0;
|
||||||
|
|
||||||
for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
|
for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
|
||||||
interp->monitors.tools[i] = 0;
|
interp->monitors.tools[i] = 0;
|
||||||
|
|
Loading…
Reference in New Issue