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 }