GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-148925: Add non-allocating PyUnstable_* APIs for call-stack iteration by danielsn · Pull Request #148930 · python/cpython

Conversation

@danielsn

@danielsn danielsn commented Apr 23, 2026 •
edited by github-actions Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

#145559 proposes a signal-safe mechanism to print backtraces. This is useful for human debugging, but not ideal for tools because it requires parsing human-readable text. For example, a crash-tracker may wish to export and analyze structured backtraces. Similarly, a profiler can benefit from the ability to generate structure stack-traces in pure C, without needing to reason about the safety of reentering the python interpreter.

  • Issue: Add unstable C APIs for allocation-free raw-frame stack inspection #148925

📚 Documentation preview 📚: https://cpython-previews--148930.org.readthedocs.build/

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@danielsn
pythongh-148925: Add PyUnstable_CollectCallStack and PyUnstable_Print…
a950a1e
…CallStack
@danielsn
danielsn requested review from a team, AA-Turner, ZeroIntensity, emmatyping and erlend-aasland as code owners April 23, 2026 19:53
@python-cla-bot

python-cla-bot Bot commented Apr 23, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot added the awaiting review label Apr 23, 2026
@bedevere-app bedevere-app Bot mentioned this pull request Apr 23, 2026
Add unstable C APIs for allocation-free raw-frame stack inspection #148925
Open
danielsn
danielsn commented Apr 23, 2026
View reviewed changes
Comment thread Include/cpython/traceback.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

danielsn
danielsn commented Apr 23, 2026
View reviewed changes
Comment thread Include/cpython/traceback.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal pablogsal self-assigned this Apr 29, 2026
@pablogsal

pablogsal commented Apr 29, 2026

Copy link
Copy Markdown
Member

Will try to review this week. Heads up that we are very close to beta freeze so we would need to land this this week or next week at the latest

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

KowalskiThomas
KowalskiThomas reviewed Apr 30, 2026
View reviewed changes
Comment thread Python/traceback.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Doc/c-api/traceback.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Python/traceback.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

pablogsal
pablogsal requested changes Apr 30, 2026
View reviewed changes

@pablogsal pablogsal left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the cost/benefit pencils out here.

This is a meaningful amount of new code to carry: a new public-ish struct with fixed buffers, a new format_ascii encoder that largely duplicates _Py_DumpASCII, a new collect/print pair, plus a refactor of dump_traceback. The safety story is subtle enough that it requires a maintainer-contract banner just to keep future contributors from breaking it (no malloc, no refcount, no GIL, no non-AS-safe libc, etc.), and even with that discipline in place the contract is admittedly best-effort as CollectCallStack itself documents that it "may even rash itself."

A third-party extension can already do all of this today: include the CPython headers, build against Py_BUILD_CORE-style internals if needed, and walk frames using the existing PyUnstable_InterpreterFrame_* primitives. That's always possible. But landing it here flips the direction of the maintenance burden instead of profiler vendors maintaining their own stack-walker against each CPython version (which they already do for everything else they care about), we take on maintaining it for them, with the additional constraint that the AS-safety invariants must be preserved by every future contributor touching traceback.c. That's a real cost, not a free PyUnstable_* escape hatch.

It's also worth noting that this only helps a fairly narrow class of tools: in-process profilers and crash handlers running inside the target interpreter. The majority of production Python profilers are out-of-process. Those tools get nothing from this API; they need stable struct layouts and offsets they can read remotely, not an in-process C function. So we'd be adding a non-trivial maintenance surface to CPython to serve a subset of in-process tooling, while the dominant profiler architecture is unaffected.

That said, I want to think about this more before taking a firm position, and I'd like to hear other core devs weigh in.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

zooba reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@bedevere-app bedevere-app Bot removed the awaiting review label Apr 30, 2026
@bedevere-app

bedevere-app Bot commented Apr 30, 2026

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot added the awaiting changes label Apr 30, 2026
@pablogsal

pablogsal commented Apr 30, 2026

Copy link
Copy Markdown
Member

CC @vstinner @lysnikolaou

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 30, 2026

Copy link
Copy Markdown
Member

It was proposed to make PyUnstable_DumpTraceback() public: see PR gh-148145. IMO it would already be a first step forward. I prefer to wait until that's done to consider adding another similar-but-different API.

pablogsal reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@danielsn

danielsn commented May 7, 2026

Copy link
Copy Markdown
Author

Thanks for the review @pablogsal and @vstinner. I talked with @pablogsal offline, and I think we can indeed push most of the work onto the caller. The one remaining piece is a signal-safe way to walk the stack frames themselves, which I think can be done with a fairly small API change. I'll test this out and get back to you with an updated diff :)

pablogsal and KowalskiThomas reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

danielsn added 2 commits May 11, 2026 16:39
@danielsn
Undo changes in preperation for a simpler API.
c56f335
@danielsn
pythongh-148925: Add signal-safe PyUnstable_* APIs for call-stack ite…
4448ac8
…ration

Add four new public APIs for walking the Python call stack without
allocating memory, changing reference counts, or acquiring the GIL,
making them safe to call from signal handlers and custom memory
allocator hooks:

- PyUnstable_ThreadState_GetInterpreterFrame(tstate): returns the
  innermost complete interpreter frame, skipping entry trampolines
  and pre-RESUME frames automatically.
- PyUnstable_InterpreterFrame_GetNextComplete(frame): returns the
  next complete calling frame, skipping incomplete frames.  Mirrors
  PyFrame_GetBack semantics without allocating.
- PyUnstable_InterpreterFrame_GetCodeSafe(frame): returns a borrowed
  reference to the code object, or NULL if freed memory is detected.
- PyUnstable_InterpreterFrame_GetLineSafe(frame): returns the current
  line number, validating the instruction offset rather than asserting,
  safe when the frame may be partially torn down.

All four use _Py_NO_SANITIZE_THREAD to suppress intentional racy reads
and heuristics (_PyMem_IsPtrFreed) to detect freed memory.

Switch tracemalloc and the fatal error traceback printer to use the
new APIs instead of internal helpers.  Add tests in
Lib/test/test_capi/test_misc.py and test helpers in
Modules/_testinternalcapi.c.
@danielsn
danielsn requested a review from markshannon as a code owner May 11, 2026 20:41
danielsn
danielsn commented May 11, 2026
View reviewed changes
Comment thread Python/frame.c
PyUnstable_ThreadState_GetInterpreterFrame(PyThreadState *tstate)
{
_PyInterpreterFrame *frame = tstate->current_frame;
while (frame != NULL) {

@danielsn danielsn May 11, 2026

Copy link
Copy Markdown
Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a "hop counter" to ensure that this function would deterministically exit

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@read-the-docs-community

read-the-docs-community Bot commented May 11, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #33188738 | 📁 Comparing 912299e against main (448d7b9)

  🔍 Preview build  

208 files changed · + 6 added · ± 202 modified

+ Added

  • c-api/sentinel.html
  • c-api/slots.html
  • deprecations/pending-removal-in-3.21.html
  • howto/abi3t-migration.html
  • library/asyncio-threading.html
  • whatsnew/3.16.html

± Modified

  • bugs.html
  • glossary.html
  • index.html
  • c-api/complex.html
  • c-api/concrete.html
  • c-api/coro.html
  • c-api/dict.html
  • c-api/exceptions.html
  • c-api/extension-modules.html
  • c-api/frame.html
  • and 192 more...
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@danielsn danielsn changed the title gh-148925: Add PyUnstable_CollectCallStack and PyUnstable_PrintCallStack gh-148925: Add signal-safe PyUnstable_* APIs for call-stack iteration May 11, 2026
@danielsn

danielsn commented May 11, 2026

Copy link
Copy Markdown
Author

@pablogsal I have reworked the proposal to add the missing iterator function, with the rest of the work able to happen on the client side. LMK if this looks any better :)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@danielsn danielsn changed the title gh-148925: Add signal-safe PyUnstable_* APIs for call-stack iteration gh-148925: Add safe PyUnstable_* APIs for call-stack iteration May 11, 2026
@danielsn
ensure we get bytecode safely as well
671d19b
@danielsn
a little more code cleanup
774b1c3
danielsn
danielsn commented May 12, 2026
View reviewed changes
Comment thread Doc/c-api/frame.rst Outdated
.. versionadded:: 3.15


.. c:function:: struct _PyInterpreterFrame* PyUnstable_ThreadState_GetInterpreterFrame(PyThreadState *tstate)

@danielsn danielsn May 12, 2026

Copy link
Copy Markdown
Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these have a Safe suffix?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@vstinner

vstinner commented May 13, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

It's hard or even impossible to write a reliable Python in-process debugger/profiler. For example, HEAD_LOCK() is missing whereas it's needed to iterate safely on interpreters and thread states: see issue gh-85234. Interpreters, thread states, frames and code objects can be added and destroyed anytime. For example, it's possible to get crashes when running faulthandler on a program which creates/destroys many threads in parallel while faulthandler is running: #116008 (comment).

"Safe" in _PyFrame_SafeGetCode() name is a trap. It's not safe, it can still crash if the frame became a dangling pointer in the meanwhile, and it returns a borrowed reference whereas the code object can be destroyed anytime.

faulthandler has to use heuristics such as checking _PyObject_IsFreed() to reduce the risk of crashes. But it's only a weak workaround, not a reliable solution. faulthandler uses a signal handler and so cannot use the Python C API or even allocate memory on the heap.

An out-of-process debugger/profiler is more reliable since it gets an error instead of a stack fault when reading invalid memory locations.

Building a more reliable in-process debugger/profiler would require synchronization and so call functions, change reference count, allocate memory on the heap, etc. It would be incompatible with a signal hander.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@danielsn

danielsn commented May 19, 2026

Copy link
Copy Markdown
Author

Thanks for the links @vstinner.

There are use-cases I can see for an API like this, and I believe 2 of them still apply.

  1. The first use-case is profiling from an in-process hook/wrapper (e.g. allocation/live-heap/lock/io profiling). In these cases, the system is in a stable state and the GIL is held (if in non-freethreaded mode). This is how tracemalloc works as I understand it, and we would like to build similar observability in userspace. The problem is that the current public API for stackwalking potentially reenters the interpreter, which makes reasoning about correctness difficult. Are there issues that would prevent a non-reentrant API working in this case?
  2. The second use-case is crash-reporting: i.e. doing what the current fault-handler does, but in userspace to generate structured data instead of free-text. In this case, any operations are clearly only best effort since we're inside a crashing program. I believe this case is still valuable, but with better naming to indicate that while the functions do not signal-unsafe operations like retentering the interpreter or allocating memory, this does not mean they are “safe”, merely “less unsafe”.
  3. The third is an in-process signal-based CPU profiler, and I agree that sadly this wouldn’t work for that. I will try to propose better naming to indicate that this is not a supported use-case at this time.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

danielsn added 3 commits May 26, 2026 14:32
@danielsn
clarify the names and intended uses of the functions
e672616
@danielsn
Merge branch 'main' into dsn/c-stracktrace
35f9148
@danielsn
small cleanups
b725a08
@danielsn

danielsn commented May 26, 2026

Copy link
Copy Markdown
Author

@vstinner @pablogsal I have updated the function names and descriptions to follow the discussion above.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 28, 2026

Copy link
Copy Markdown
Member

Adding a new C API for in-process debugger/profiler would mean that we endorse this use case. I would prefer to not endorse it for the reason Pablo and me gave above. So I don't think that's a good idea to add such C API.

For example, PyUnstable_ThreadState_GetCurrentFrame() doesn't hold a strong reference and so can become a dangling pointer anytime. Other functions have a similar issue.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@P403n1x87

P403n1x87 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

I understand some of the raised concerns have been discussed offline, so apologies if any of the following comments are redundant or moot at this point.

The majority of production Python profilers are out-of-process.

I feel that some of the (valid) points raised in previous comments hinge on putting all tools such as profilers into the same basket. In my experience, there is a set of categories that one would have to consider. Not all profilers are the same, and some make sense as out-of-process, but others are only viable as in-process. A sampling profiler (for wall/CPU time) is something that one might want to implement as an out-of-process tool, for various reasons. The stochastic nature of the tool means that a few incorrect stack walks can be tolerated. However, memory profilers cannot rely on a similar mechanism if they are to run with performance decent enough to be used in production. That's because the inevitable upscaling from a sampling mechanism would combine with potentially incorrect stacks, leading to an unacceptable compounding of errors. Therefore, I would argue that memory profilers would require efficient, in-process stack-walking logic.

It's hard or even impossible to write a reliable Python in-process debugger/profiler.

My understanding is that the proposed changes are not addressing the general (and hard) problem of adding an API for any kind of profilers/debuggers. The main scope of the proposed API is to provide efficient utilities that can be used for memory profiling. When we specialise the problem to just this case, I believe there are many simplifying assumptions that can be made. For once, grabbing a stacktrace from a hook triggered by a memory allocation will run effectively in-line with the allocation. This pins the call stack of the thread the unwinder is running on, meaning that one can assume that internal structures like frames, thread states, and everything else related to the thread are not going to mutate under the unwinder's feet. Surely some degree of caution is still required, but the problem is much more tractable than the general case.

But landing it here flips the direction of the maintenance burden

I agree that the burden of maintaining the new API to serve tools should not fall on CPython in general. However, my view is that, if we can keep the changes to a minimum and the new API surface as lean as possible, the trade-off would be that of potentially giving CPython an efficient built-in memory profiler that could well complement the newly introduced sampling profiler.

An out-of-process debugger/profiler is more reliable since it gets an error instead of a stack fault when reading invalid memory locations.

This is probably off-topic, but there is nothing preventing an in-process tool from using the same out-of-process techniques to read memory safely. Of course the shared resources in this case imply e.g. a different performance profile, but the point is that there are techniques that can be used to make in-process tools almost as safe as out-of-process ones.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

danielsn added 2 commits June 17, 2026 19:32
@danielsn
Clarify when these functions are useful
7026b7f
@danielsn
Merge branch 'main' into dsn/c-stracktrace
912299e
@danielsn danielsn changed the title gh-148925: Add safe PyUnstable_* APIs for call-stack iteration gh-148925: Add non-allocating PyUnstable_* APIs for call-stack iteration Jun 17, 2026
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 90 days with no activity.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@pablogsal pablogsal pablogsal requested changes
@ZeroIntensity ZeroIntensity Awaiting requested review from ZeroIntensity ZeroIntensity is a code owner
@erlend-aasland erlend-aasland Awaiting requested review from erlend-aasland
@AA-Turner AA-Turner Awaiting requested review from AA-Turner
@emmatyping emmatyping Awaiting requested review from emmatyping
@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner
+1 more reviewer
@KowalskiThomas KowalskiThomas KowalskiThomas left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@pablogsal pablogsal

Labels

awaiting changes stale Stale PR or inactive for long period of time.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@danielsn @pablogsal @vstinner @P403n1x87 @KowalskiThomas
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.