gh-151436: Fix missing `tstate->last_profiled_frame` updates (#151437) · python/cpython@a8d74c0

GitHub

Original file line numberDiff line numberDiff line change@@ -287,6 +287,20 @@ _PyThreadState_GetFrame(PyThreadState *tstate)

287287return_PyFrame_GetFirstComplete(tstate->current_frame);

288288}

289289290+// Update last_profiled_frame for remote profiler frame caching.

291+// Only update if we're removing the exact frame that was last profiled.

292+// This avoids corrupting the cache when transient frames (called and returned

293+// between profiler samples) update last_profiled_frame to addresses the

294+// profiler never saw.

295+#define_PyThreadState_UpdateLastProfiledFrame(tstate, frame, previous) \

296+ do { \

297+ PyThreadState *tstate_ = (tstate); \

298+ _PyInterpreterFrame *frame_ = (frame); \

299+ if (tstate_->last_profiled_frame == frame_) { \

300+ tstate_->last_profiled_frame = (previous); \

301+ } \

302+ } while (0)

303+290304/* For use by _PyFrame_GetFrameObject

291305 Do not call directly. */

292306PyAPI_FUNC(PyFrameObject*)

Original file line numberDiff line numberDiff line change@@ -0,0 +1,4 @@

1+Fix skewed stack trackes in the Tachyon profiler when caching is enabled and

2+when generators and coroutines are profiled, by updating

3+``tstate->last_profiled_frame`` at every frame-removal site. The issue resulted

4+in total erasure of some callers. Patch by Maurycy Pawłowski-Wieroński.

Original file line numberDiff line numberDiff line change@@ -168,6 +168,7 @@ gen_clear_frame(PyGenObject *gen)

168168{

169169assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) ==FRAME_CLEARED);

170170_PyInterpreterFrame*frame=&gen->gi_iframe;

171+_PyThreadState_UpdateLastProfiledFrame(_PyThreadState_GET(), frame, frame->previous);

171172frame->previous=NULL;

172173_PyFrame_ClearExceptCode(frame);

173174_PyErr_ClearExcState(&gen->gi_exc_state);

@@ -681,6 +682,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,

681682 'yield from' or awaiting on with 'await'. */

682683ret=_gen_throw((PyGenObject*)yf, close_on_genexit,

683684typ, val, tb);

685+_PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);

684686tstate->current_frame=prev;

685687frame->previous=NULL;

686688 }

@@ -701,6 +703,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,

701703frame->previous=prev;

702704tstate->current_frame=frame;

703705ret=PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);

706+_PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);

704707tstate->current_frame=prev;

705708frame->previous=NULL;

706709Py_DECREF(meth);

Original file line numberDiff line numberDiff line change@@ -1860,6 +1860,7 @@ dummy_func(

18601860gen->gi_exc_state.previous_item=NULL;

18611861_Py_LeaveRecursiveCallPy(tstate);

18621862_PyInterpreterFrame*gen_frame=frame;

1863+_PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);

18631864frame=tstate->current_frame=frame->previous;

18641865gen_frame->previous=NULL;

18651866 ((_PyThreadStateImpl*)tstate)->generator_return_kind=GENERATOR_YIELD;

@@ -5874,6 +5875,7 @@ dummy_func(

58745875gen_frame->owner=FRAME_OWNED_BY_GENERATOR;

58755876_Py_LeaveRecursiveCallPy(tstate);

58765877_PyInterpreterFrame*prev=frame->previous;

5878+_PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);

58775879_PyThreadState_PopFrame(tstate, frame);

58785880frame=tstate->current_frame=prev;

58795881LOAD_IP(frame->return_offset);

Original file line numberDiff line numberDiff line change@@ -1974,15 +1974,8 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)

19741974void

19751975_PyEval_FrameClearAndPop(PyThreadState*tstate, _PyInterpreterFrame*frame)

19761976{

1977-// Update last_profiled_frame for remote profiler frame caching.

19781977// By this point, tstate->current_frame is already set to the parent frame.

1979-// Only update if we're popping the exact frame that was last profiled.

1980-// This avoids corrupting the cache when transient frames (called and returned

1981-// between profiler samples) update last_profiled_frame to addresses the

1982-// profiler never saw.

1983-if (tstate->last_profiled_frame!=NULL&&tstate->last_profiled_frame==frame) {

1984-tstate->last_profiled_frame=tstate->current_frame;

1985- }

1978+_PyThreadState_UpdateLastProfiledFrame(tstate, frame, tstate->current_frame);

1986197919871980if (frame->owner==FRAME_OWNED_BY_THREAD) {

19881981clear_thread_frame(tstate, frame);

@@ -2008,6 +2001,7 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, _PyStackRef func,

20082001_PyFrame_Initialize(tstate, frame, func, locals, code, 0, previous);

20092002if (initialize_locals(tstate, func_obj, frame->localsplus, args, argcount, kwnames)) {

20102003assert(frame->owner==FRAME_OWNED_BY_THREAD);

2004+_PyThreadState_UpdateLastProfiledFrame(tstate, frame, tstate->current_frame);

20112005clear_thread_frame(tstate, frame);

20122006returnNULL;

20132007 }