]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-151436: Fix missing `tstate->last_profiled_frame` updates (GH-151437) ...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 19 Jun 2026 08:24:53 +0000 (10:24 +0200)
committerGitHub <noreply@github.com>
Fri, 19 Jun 2026 08:24:53 +0000 (10:24 +0200)
gh-151436: Fix missing `tstate->last_profiled_frame` updates (GH-151437)
(cherry picked from commit a8d74c062fe3c5cb2962dde8bee83704fcfa1bc9)

Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
Include/internal/pycore_interpframe.h
Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst [new file with mode: 0644]
Modules/_testinternalcapi/test_cases.c.h
Objects/genobject.c
Python/bytecodes.c
Python/ceval.c
Python/executor_cases.c.h
Python/generated_cases.c.h

index 28370ababc47b92095e19853f5a93c4d6d40e108..3fc7c48ddececc4e5c06552196862c0a6386dd26 100644 (file)
@@ -282,6 +282,20 @@ _PyThreadState_GetFrame(PyThreadState *tstate)
     return _PyFrame_GetFirstComplete(tstate->current_frame);
 }
 
+// Update last_profiled_frame for remote profiler frame caching.
+// Only update if we're removing the exact frame that was last profiled.
+// This avoids corrupting the cache when transient frames (called and returned
+// between profiler samples) update last_profiled_frame to addresses the
+// profiler never saw.
+#define _PyThreadState_UpdateLastProfiledFrame(tstate, frame, previous) \
+    do { \
+        PyThreadState *tstate_ = (tstate); \
+        _PyInterpreterFrame *frame_ = (frame); \
+        if (tstate_->last_profiled_frame == frame_) { \
+            tstate_->last_profiled_frame = (previous); \
+        } \
+    } while (0)
+
 /* For use by _PyFrame_GetFrameObject
   Do not call directly. */
 PyAPI_FUNC(PyFrameObject *)
diff --git a/Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst b/Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst
new file mode 100644 (file)
index 0000000..1d1aadb
--- /dev/null
@@ -0,0 +1,4 @@
+Fix skewed stack trackes in the Tachyon profiler when caching is enabled and
+when generators and coroutines are profiled, by updating
+``tstate->last_profiled_frame`` at every frame-removal site. The issue resulted
+in total erasure of some callers. Patch by Maurycy Pawłowski-Wieroński.
index aa4419b323e5b3fcee937269a8ef73b0689fb295..12f028618f5c69ae5ac2bfef7f8718949746a65e 100644 (file)
                 gen->gi_exc_state.previous_item = NULL;
                 _Py_LeaveRecursiveCallPy(tstate);
                 _PyInterpreterFrame *gen_frame = frame;
+                _PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);
                 frame = tstate->current_frame = frame->previous;
                 gen_frame->previous = NULL;
                 ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
             gen_frame->owner = FRAME_OWNED_BY_GENERATOR;
             _Py_LeaveRecursiveCallPy(tstate);
             _PyInterpreterFrame *prev = frame->previous;
+            _PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);
             _PyThreadState_PopFrame(tstate, frame);
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
                 gen->gi_exc_state.previous_item = NULL;
                 _Py_LeaveRecursiveCallPy(tstate);
                 _PyInterpreterFrame *gen_frame = frame;
+                _PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);
                 frame = tstate->current_frame = frame->previous;
                 gen_frame->previous = NULL;
                 ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
index 38d493343454fce6303d4a467db14c37424998f3..3cdc06733363d3e217ed270786046aa24fd6f200 100644 (file)
@@ -168,6 +168,7 @@ gen_clear_frame(PyGenObject *gen)
 {
     assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_CLEARED);
     _PyInterpreterFrame *frame = &gen->gi_iframe;
+    _PyThreadState_UpdateLastProfiledFrame(_PyThreadState_GET(), frame, frame->previous);
     frame->previous = NULL;
     _PyFrame_ClearExceptCode(frame);
     _PyErr_ClearExcState(&gen->gi_exc_state);
@@ -681,6 +682,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
                'yield from' or awaiting on with 'await'. */
             ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
                              typ, val, tb);
+            _PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);
             tstate->current_frame = prev;
             frame->previous = NULL;
         }
@@ -701,6 +703,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
             frame->previous = prev;
             tstate->current_frame = frame;
             ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
+            _PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);
             tstate->current_frame = prev;
             frame->previous = NULL;
             Py_DECREF(meth);
index c77823b78eadc1986ffbfba9bbcec0580e1532bf..4f8a67d33fff5299e0602c3a0aec3052ce9e4810 100644 (file)
@@ -1859,6 +1859,7 @@ dummy_func(
             gen->gi_exc_state.previous_item = NULL;
             _Py_LeaveRecursiveCallPy(tstate);
             _PyInterpreterFrame *gen_frame = frame;
+            _PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);
             frame = tstate->current_frame = frame->previous;
             gen_frame->previous = NULL;
             ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
@@ -5880,6 +5881,7 @@ dummy_func(
             gen_frame->owner = FRAME_OWNED_BY_GENERATOR;
             _Py_LeaveRecursiveCallPy(tstate);
             _PyInterpreterFrame *prev = frame->previous;
+            _PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);
             _PyThreadState_PopFrame(tstate, frame);
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
index 3feb6ad0050d14c1899d82588287f570549e6b90..464a00860524fcc7cc5e2738518ea39a850d22f0 100644 (file)
@@ -1988,15 +1988,8 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
 void
 _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame)
 {
-    // Update last_profiled_frame for remote profiler frame caching.
     // By this point, tstate->current_frame is already set to the parent frame.
-    // Only update if we're popping the exact frame that was last profiled.
-    // This avoids corrupting the cache when transient frames (called and returned
-    // between profiler samples) update last_profiled_frame to addresses the
-    // profiler never saw.
-    if (tstate->last_profiled_frame != NULL && tstate->last_profiled_frame == frame) {
-        tstate->last_profiled_frame = tstate->current_frame;
-    }
+    _PyThreadState_UpdateLastProfiledFrame(tstate, frame, tstate->current_frame);
 
     if (frame->owner == FRAME_OWNED_BY_THREAD) {
         clear_thread_frame(tstate, frame);
@@ -2022,6 +2015,7 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, _PyStackRef func,
     _PyFrame_Initialize(tstate, frame, func, locals, code, 0, previous);
     if (initialize_locals(tstate, func_obj, frame->localsplus, args, argcount, kwnames)) {
         assert(frame->owner == FRAME_OWNED_BY_THREAD);
+        _PyThreadState_UpdateLastProfiledFrame(tstate, frame, tstate->current_frame);
         clear_thread_frame(tstate, frame);
         return NULL;
     }
index 882201bbc06c1610ac921b8a80344524fcb77093..4d5a8ab6b8af0b9204277e001db63e5b82cd717a 100644 (file)
             gen->gi_exc_state.previous_item = NULL;
             _Py_LeaveRecursiveCallPy(tstate);
             _PyInterpreterFrame *gen_frame = frame;
+            _PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);
             frame = tstate->current_frame = frame->previous;
             gen_frame->previous = NULL;
             ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
             gen_frame->owner = FRAME_OWNED_BY_GENERATOR;
             _Py_LeaveRecursiveCallPy(tstate);
             _PyInterpreterFrame *prev = frame->previous;
+            _PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);
             _PyThreadState_PopFrame(tstate, frame);
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
index 5033b994c33512db3e78437b8c33a715f0057acd..24ffb07830adf471f96c1937fd44ac914406cb68 100644 (file)
                 gen->gi_exc_state.previous_item = NULL;
                 _Py_LeaveRecursiveCallPy(tstate);
                 _PyInterpreterFrame *gen_frame = frame;
+                _PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);
                 frame = tstate->current_frame = frame->previous;
                 gen_frame->previous = NULL;
                 ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
             gen_frame->owner = FRAME_OWNED_BY_GENERATOR;
             _Py_LeaveRecursiveCallPy(tstate);
             _PyInterpreterFrame *prev = frame->previous;
+            _PyThreadState_UpdateLastProfiledFrame(tstate, frame, prev);
             _PyThreadState_PopFrame(tstate, frame);
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
                 gen->gi_exc_state.previous_item = NULL;
                 _Py_LeaveRecursiveCallPy(tstate);
                 _PyInterpreterFrame *gen_frame = frame;
+                _PyThreadState_UpdateLastProfiledFrame(tstate, gen_frame, gen_frame->previous);
                 frame = tstate->current_frame = frame->previous;
                 gen_frame->previous = NULL;
                 ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;