-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
gh-148925: Add PyUnstable_CollectCallStack and PyUnstable_PrintCallStack #148930
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
danielsn
wants to merge
1
commit into
python:main
Choose a base branch
from
danielsn:dsn/c-stracktrace
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| .. highlight:: c | ||
|
|
||
|
|
||
| .. _traceback: | ||
|
|
||
| ********** | ||
| Tracebacks | ||
| ********** | ||
|
|
||
| The functions below collect Python stack frames into a caller-supplied array of | ||
| :c:type:`PyUnstable_FrameInfo` structs. Because they do not acquire or release | ||
| the GIL or allocate heap memory, they can be called from signal handlers and | ||
| are suitable for low-overhead observability tools such as sampling profilers | ||
| and tracers. | ||
|
|
||
| .. c:function:: int PyUnstable_CollectCallStack(PyThreadState *tstate, PyUnstable_FrameInfo *frames, int max_frames) | ||
|
|
||
| Collect up to *max_frames* frames from *tstate* into the caller-supplied | ||
| *frames* array and return the number of frames written (0..*max_frames*). | ||
| Returns ``-1`` if *frames* is NULL, *tstate* is freed, or *tstate* has no | ||
| current Python frame. | ||
|
|
||
| Filenames and function names are ASCII-encoded (non-ASCII characters are | ||
| backslash-escaped) and truncated to 500 characters; if truncated, the | ||
| corresponding ``filename_truncated`` or ``name_truncated`` field is set | ||
| to ``1``. | ||
|
|
||
| In crash scenarios such as signal handlers for SIGSEGV, where the | ||
| interpreter may be in an inconsistent state, the function might produce | ||
| incomplete output or it may even crash itself. | ||
|
|
||
| The caller does not need to hold an attached thread state, nor does *tstate* | ||
| need to be attached. | ||
|
|
||
| This function does not acquire or release the GIL, modify reference counts, | ||
| or allocate heap memory. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
| .. c:function:: void PyUnstable_PrintCallStack(int fd, const PyUnstable_FrameInfo *frames, int n_frames, int write_header) | ||
|
|
||
| Write a traceback collected by :c:func:`PyUnstable_CollectCallStack` to | ||
| *fd*. The format looks like:: | ||
|
|
||
| Stack (most recent call first): | ||
| File "foo/bar.py", line 42 in myfunc | ||
| File "foo/bar.py", line 99 in caller | ||
|
|
||
| Pass *write_header* as ``1`` to emit the ``Stack (most recent call first):`` | ||
| header line, or ``0`` to omit it. Truncated filenames and function names | ||
| are followed by ``...``. | ||
|
|
||
| This function only reads the caller-supplied *frames* array and does not | ||
| access interpreter state. It is async-signal-safe: it does not acquire or | ||
| release the GIL, modify reference counts, or allocate heap memory, and its | ||
| only I/O is via :c:func:`!write`. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
| .. c:type:: PyUnstable_FrameInfo | ||
|
|
||
| A plain-data struct representing a single Python stack frame, suitable for | ||
| use in crash-handling code. Populated by | ||
| :c:func:`PyUnstable_CollectCallStack`. | ||
|
|
||
| .. c:member:: int lineno | ||
|
|
||
| The line number, or ``-1`` if unknown. | ||
|
|
||
| .. c:member:: int filename_truncated | ||
|
|
||
| ``1`` if :c:member:`filename` was truncated, ``0`` otherwise. | ||
|
|
||
| .. c:member:: int name_truncated | ||
|
|
||
| ``1`` if :c:member:`name` was truncated, ``0`` otherwise. | ||
|
|
||
| .. c:member:: char filename[Py_UNSTABLE_FRAMEINFO_STRSIZE] | ||
|
|
||
| The source filename, ASCII-encoded with ``backslashreplace`` and | ||
| null-terminated. Empty string if unavailable. | ||
|
|
||
| .. c:member:: char name[Py_UNSTABLE_FRAMEINFO_STRSIZE] | ||
|
|
||
| The function name, ASCII-encoded with ``backslashreplace`` and | ||
| null-terminated. Empty string if unavailable. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
| .. c:macro:: Py_UNSTABLE_FRAMEINFO_STRSIZE | ||
|
|
||
| The size in bytes of the :c:member:`PyUnstable_FrameInfo.filename` and | ||
| :c:member:`PyUnstable_FrameInfo.name` character arrays (``501``): up to | ||
| 500 content bytes plus a null terminator. | ||
|
|
||
| .. versionadded:: next |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,3 +11,71 @@ struct _traceback { | |
| int tb_lasti; | ||
| int tb_lineno; | ||
| }; | ||
|
|
||
| /* Buffer size for the filename and name fields in PyUnstable_FrameInfo: | ||
| up to 500 content bytes plus '\0' (1). */ | ||
| #define Py_UNSTABLE_FRAMEINFO_STRSIZE 501 | ||
|
|
||
| /* Structured, plain-data representation of a single Python frame. | ||
| PyUnstable_CollectCallStack and PyUnstable_PrintCallStack() do not | ||
| acquire or release the GIL or allocate heap memory, so they can be called | ||
| from signal handlers and are suitable for low-overhead observability tools | ||
| such as sampling profilers and tracers. | ||
|
|
||
| Populated by PyUnstable_CollectCallStack. filename and name are | ||
| ASCII-encoded (non-ASCII characters are backslash-escaped) and | ||
| null-terminated; they are empty strings if the corresponding code | ||
| attribute is missing or not a unicode object. lineno is -1 when it | ||
| cannot be determined. filename_truncated and name_truncated are 1 if | ||
| the respective string was longer than Py_UNSTABLE_FRAMEINFO_STRSIZE-1 | ||
| bytes and was truncated. */ | ||
| typedef struct { | ||
| int lineno; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code object has other fields (e.g. column), would these be worth collecting as well? |
||
| int filename_truncated; | ||
| int name_truncated; | ||
| char filename[Py_UNSTABLE_FRAMEINFO_STRSIZE]; | ||
| char name[Py_UNSTABLE_FRAMEINFO_STRSIZE]; | ||
| } PyUnstable_FrameInfo; | ||
|
|
||
| /* Collect up to max_frames frames from tstate into the caller-supplied | ||
| frames array and return the number of frames written (0..max_frames). | ||
| Returns -1 if frames is NULL, tstate is freed, or tstate has no current | ||
| Python frame. | ||
|
|
||
| The filename and function names are encoded to ASCII with backslashreplace | ||
| and truncated to 500 characters; when truncated, the corresponding | ||
| filename_truncated or name_truncated field is set to 1. | ||
|
|
||
| In crash scenarios such as signal handlers for SIGSEGV, where the | ||
| interpreter may be in an inconsistent state, the function might produce | ||
| incomplete output or it may even crash itself. | ||
|
|
||
| The caller does not need to hold an attached thread state, nor does tstate | ||
| need to be attached. | ||
|
|
||
| This function does not acquire or release the GIL, modify reference counts, | ||
| or allocate heap memory. */ | ||
| PyAPI_FUNC(int) PyUnstable_CollectCallStack( | ||
| PyThreadState *tstate, | ||
| PyUnstable_FrameInfo *frames, | ||
| int max_frames); | ||
|
|
||
| /* Write a traceback collected by PyUnstable_CollectCallStack to fd. | ||
| The format looks like: | ||
|
|
||
| Stack (most recent call first): | ||
| File "foo/bar.py", line 42 in myfunc | ||
| File "foo/bar.py", line 99 in caller | ||
|
|
||
| Pass write_header=1 to emit the "Stack (most recent call first):" header | ||
| line, or write_header=0 to omit it. | ||
|
|
||
| This function only reads the caller-supplied frames array and does not | ||
| access interpreter state. It is async-signal-safe: it does not acquire or | ||
| release the GIL, modify reference counts, or allocate heap memory, and its | ||
| only I/O is via write(2). */ | ||
| PyAPI_FUNC(void) PyUnstable_PrintCallStack( | ||
| int fd, | ||
| const PyUnstable_FrameInfo *frames, | ||
| int n_frames, | ||
| int write_header); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
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.
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 used 500 because that was what the print to fd version used, https://github.com/danielsn/cpython/blob/main/Python/traceback.c#L56C9-L56C26 but this seems larger than we'd need in practice.