@@ -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