[3.14] gh-144766: Fix a crash in fork child process when perf support is enabled. (GH-144795) (#144816)

This commit is contained in:
Miss Islington (bot) 2026-02-14 13:09:00 +01:00 committed by GitHub
parent c7ceb75ada
commit 77b71ac793
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 0 deletions

View file

@ -170,6 +170,47 @@ def baz():
self.assertNotIn(f"py::bar:{script}", child_perf_file_contents)
self.assertNotIn(f"py::baz:{script}", child_perf_file_contents)
@unittest.skipIf(support.check_bolt_optimized(), "fails on BOLT instrumented binaries")
def test_trampoline_works_after_fork_with_many_code_objects(self):
code = """if 1:
import gc, os, sys, signal
# Create many code objects so trampoline_refcount > 1
for i in range(50):
exec(compile(f"def _dummy_{i}(): pass", f"<test{i}>", "exec"))
pid = os.fork()
if pid == 0:
# Child: create and destroy new code objects,
# then collect garbage. If the old code watcher
# survived the fork, the double-decrement of
# trampoline_refcount will cause a SIGSEGV.
for i in range(50):
exec(compile(f"def _child_{i}(): pass", f"<child{i}>", "exec"))
gc.collect()
os._exit(0)
else:
_, status = os.waitpid(pid, 0)
if os.WIFSIGNALED(status):
print(f"FAIL: child killed by signal {os.WTERMSIG(status)}", file=sys.stderr)
sys.exit(1)
sys.exit(os.WEXITSTATUS(status))
"""
with temp_dir() as script_dir:
script = make_script(script_dir, "perftest", code)
env = {**os.environ, "PYTHON_JIT": "0"}
with subprocess.Popen(
[sys.executable, "-Xperf", script],
text=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
env=env,
) as process:
stdout, stderr = process.communicate()
self.assertEqual(process.returncode, 0, stderr)
self.assertEqual(stderr, "")
@unittest.skipIf(support.check_bolt_optimized(), "fails on BOLT instrumented binaries")
def test_sys_api(self):
code = """if 1:

View file

@ -0,0 +1 @@
Fix a crash in fork child process when perf support is enabled.

View file

@ -620,6 +620,12 @@ _PyPerfTrampoline_AfterFork_Child(void)
int was_active = _PyIsPerfTrampolineActive();
_PyPerfTrampoline_Fini();
if (was_active) {
// After fork, Fini may leave the old code watcher registered
// if trampolined code objects from the parent still exist
// (trampoline_refcount > 0). Clear it unconditionally before
// Init registers a new one, to prevent two watchers sharing
// the same globals and double-decrementing trampoline_refcount.
perf_trampoline_reset_state();
_PyPerfTrampoline_Init(1);
}
}