@@ -204,6 +204,42 @@ enum perf_trampoline_type {
204204#define persist_after_fork _PyRuntime.ceval.perf.persist_after_fork
205205#define perf_trampoline_type _PyRuntime.ceval.perf.perf_trampoline_type
206206#define prev_eval_frame _PyRuntime.ceval.perf.prev_eval_frame
207+ #define trampoline_refcount _PyRuntime.ceval.perf.trampoline_refcount
208+ #define code_watcher_id _PyRuntime.ceval.perf.code_watcher_id
209+
210+ static void free_code_arenas(void);
211+
212+ static void
213+ perf_trampoline_reset_state(void)
214+ {
215+ free_code_arenas();
216+ if (code_watcher_id >= 0) {
217+ PyCode_ClearWatcher(code_watcher_id);
218+ code_watcher_id = -1;
219+ }
220+ extra_code_index = -1;
221+ }
222+
223+ static int
224+ perf_trampoline_code_watcher(PyCodeEvent event, PyCodeObject *co)
225+ {
226+ if (event != PY_CODE_EVENT_DESTROY) {
227+ return 0;
228+ }
229+ if (extra_code_index == -1) {
230+ return 0;
231+ }
232+ py_trampoline f = NULL;
233+ int ret = _PyCode_GetExtra((PyObject *)co, extra_code_index, (void **)&f);
234+ if (ret != 0 || f == NULL) {
235+ return 0;
236+ }
237+ trampoline_refcount--;
238+ if (trampoline_refcount == 0) {
239+ perf_trampoline_reset_state();
240+ }
241+ return 0;
242+ }
207243
208244static void
209245perf_map_write_entry(void *state, const void *code_addr,
@@ -407,6 +443,7 @@ py_trampoline_evaluator(PyThreadState *ts, _PyInterpreterFrame *frame,
407443 perf_code_arena->code_size, co);
408444 _PyCode_SetExtra((PyObject *)co, extra_code_index,
409445 (void *)new_trampoline);
446+ trampoline_refcount++;
410447 f = new_trampoline;
411448 }
412449 assert(f != NULL);
@@ -433,6 +470,7 @@ int PyUnstable_PerfTrampoline_CompileCode(PyCodeObject *co)
433470 }
434471 trampoline_api.write_state(trampoline_api.state, new_trampoline,
435472 perf_code_arena->code_size, co);
473+ trampoline_refcount++;
436474 return _PyCode_SetExtra((PyObject *)co, extra_code_index,
437475 (void *)new_trampoline);
438476 }
@@ -487,6 +525,10 @@ _PyPerfTrampoline_Init(int activate)
487525{
488526#ifdef PY_HAVE_PERF_TRAMPOLINE
489527 PyThreadState *tstate = _PyThreadState_GET();
528+ if (code_watcher_id == 0) {
529+ // Initialize to -1 since 0 is a valid watcher ID
530+ code_watcher_id = -1;
531+ }
490532 if (!activate) {
491533 _PyInterpreterState_SetEvalFrameFunc(tstate->interp, prev_eval_frame);
492534 perf_status = PERF_STATUS_NO_INIT;
@@ -504,6 +546,13 @@ _PyPerfTrampoline_Init(int activate)
504546 if (new_code_arena() < 0) {
505547 return -1;
506548 }
549+ code_watcher_id = PyCode_AddWatcher(perf_trampoline_code_watcher);
550+ if (code_watcher_id < 0) {
551+ PyErr_FormatUnraisable("Failed to register code watcher for perf trampoline");
552+ free_code_arenas();
553+ return -1;
554+ }
555+ trampoline_refcount = 1; // Base refcount held by the system
507556 perf_status = PERF_STATUS_OK;
508557 }
509558#endif
@@ -525,17 +574,19 @@ _PyPerfTrampoline_Fini(void)
525574 trampoline_api.free_state(trampoline_api.state);
526575 perf_trampoline_type = PERF_TRAMPOLINE_UNSET;
527576 }
528- extra_code_index = -1;
577+
578+ // Prevent new trampolines from being created
529579 perf_status = PERF_STATUS_NO_INIT;
530- #endif
531- return 0;
532- }
533580
534- void _PyPerfTrampoline_FreeArenas(void) {
535- #ifdef PY_HAVE_PERF_TRAMPOLINE
536- free_code_arenas();
581+ // Decrement base refcount. If refcount reaches 0, all code objects are already
582+ // dead so clean up now. Otherwise, watcher remains active to clean up when last
583+ // code object dies; extra_code_index stays valid so watcher can identify them.
584+ trampoline_refcount--;
585+ if (trampoline_refcount == 0) {
586+ perf_trampoline_reset_state();
587+ }
537588#endif
538- return;
589+ return 0 ;
539590}
540591
541592int
0 commit comments