https://github.com/python/cpython/commit/77b71ac793ad7d5deb7c56ffa703d8d6f596dc74
commit: 77b71ac793ad7d5deb7c56ffa703d8d6f596dc74
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-02-14T12:09:00Z
summary:
[3.14] gh-144766: Fix a crash in fork child process when perf support is
enabled. (GH-144795) (#144816)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-02-13-18-30-59.gh-issue-144766.JGu3x3.rst
M Lib/test/test_perf_profiler.py
M Python/perf_trampoline.c
diff --git a/Lib/test/test_perf_profiler.py b/Lib/test/test_perf_profiler.py
index 1e1b0522787ff3..7824897dd29962 100644
--- a/Lib/test/test_perf_profiler.py
+++ b/Lib/test/test_perf_profiler.py
@@ -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:
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-13-18-30-59.gh-issue-144766.JGu3x3.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-13-18-30-59.gh-issue-144766.JGu3x3.rst
new file mode 100644
index 00000000000000..d9613c95af1915
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-13-18-30-59.gh-issue-144766.JGu3x3.rst
@@ -0,0 +1 @@
+Fix a crash in fork child process when perf support is enabled.
diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c
index 8feb259a63a50f..40136054221846 100644
--- a/Python/perf_trampoline.c
+++ b/Python/perf_trampoline.c
@@ -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);
}
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]