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-111997: C-API for signalling monitoring events by iritkatriel · Pull Request #116413 · python/cpython

Conversation

@iritkatriel

@iritkatriel iritkatriel commented Mar 6, 2024 •
edited by bedevere-app Bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member
  • Issue: C-API for signalling monitoring events #111997

Sorry, something went wrong.

Uh oh!

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

corona10 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@iritkatriel
pythongh-111997: C-API for signalling monitoring events
b2d19c3
@bedevere-app bedevere-app Bot added the awaiting core review label Mar 6, 2024
@iritkatriel
iritkatriel marked this pull request as draft March 6, 2024 14:11
@bedevere-app bedevere-app Bot mentioned this pull request Mar 6, 2024
C-API for signalling monitoring events #111997
Closed
@bedevere-app bedevere-app Bot removed the awaiting core review label Mar 6, 2024
markshannon
markshannon reviewed Mar 6, 2024
View reviewed changes
Comment thread Python/instrumentation.c Resolved

Uh oh!

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

iritkatriel added 4 commits March 6, 2024 21:58
@iritkatriel
remove unnecessary events and add a basic test
1b9487c
@iritkatriel
enhance test
150011a
@iritkatriel
enable disable tests. api test functions return the new 'active' value
ebc36b5
@iritkatriel
Merge remote-tracking branch 'upstream/main' into monitoring-capi
1ad872c
@iritkatriel
iritkatriel requested a review from scoder March 7, 2024 15:39
iritkatriel added 4 commits March 7, 2024 15:44
@iritkatriel
update PCbuild
34179c1
@iritkatriel
use int
3c9f8bb
@iritkatriel
implement _PyMonitoringScopeBegin and add CodeLike type
344fd26
@iritkatriel
more tests
ecaee5e
scoder
scoder reviewed Mar 12, 2024
View reviewed changes

@scoder scoder left a comment

Copy link
Copy Markdown
Contributor

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.

Do I understand correctly that the CodeLike type is expected to be provided by user code? How do we guarantee its struct layout then? Why not define a public object struct for it that users can use C-struct inheritance with? (I.e. if they need more fields on their side, they can put the public struct into the first field of their own struct.)

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Python/instrumentation.c Resolved

Uh oh!

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

Comment thread Include/internal/pycore_instruments.h Resolved

Uh oh!

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

@iritkatriel

iritkatriel commented Mar 12, 2024

Copy link
Copy Markdown
Member Author

Do I understand correctly that the CodeLike type is expected to be provided by user code?

I think we should provide CodeLike in some form, but for now I put it in the text file so we can talk about how and where we want it to be. The offset to location mapping should also be part of this.

I don't see why this public API should live in an internal header file. And why the underscore names? We only use(d) them for non-public functions.

@markshannon did you intend for these to be private?

All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon

markshannon commented Mar 12, 2024

Copy link
Copy Markdown
Member

@markshannon did you intend for these to be private?

My thinking was that it is better to keep any function private unless we need it to be public. It sounds like they need to be public.

How about this?

  • The declaration of the implementation function, _PyMonitoring_FirePyStartEvent(), goes in the semi-private cpython/ folder.
  • We expose an inline function calling for the unstable API and a non-inline one for the stable API in the public header.

Something like this:

#ifndef Py_LIMITED_API
static inline int
PyMonitoring_FirePyStartEvent(PyMonitoringState *state, PyObject *codelike, uint32_t offset)
{
    if (state->active)
        return _PyMonitoring_FirePyStartEvent(state, codelike, offset);
    else
        return 0;
}
#else
extern int
PyMonitoring_FirePyStartEvent(PyMonitoringState *state, PyObject *codelike, uint32_t offset);
#endif
All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon

markshannon commented Mar 12, 2024

Copy link
Copy Markdown
Member

The "code like" object is entirely opaque to the monitoring machinery. As far as it is concerned, a location is a pair of an object and an integer index.
Debuggers, profilers, etc need to present that object, int as a meaningful location. If the object is a code object, they can already do that using co.co_filename and co.co_positions().
The point of a "code like" object is to provide the necessary interface for tools (debuggers, profilers, etc) to turn object, int into a meaningful source location.

So, no C struct, but a Python abstract base class.

All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon markshannon closed this Mar 12, 2024
@markshannon markshannon reopened this Mar 12, 2024
@iritkatriel
make functions public
5c68095
scoder
scoder reviewed Mar 13, 2024
View reviewed changes
Comment thread Include/monitoring.h Resolved

Uh oh!

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

Comment thread Include/monitoring.h Resolved

Uh oh!

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

Comment thread Python/instrumentation.c Resolved

Uh oh!

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

iritkatriel added 7 commits March 13, 2024 11:43
@iritkatriel
move tests from testinternalcapi to testcapi
7661865
@iritkatriel
add include
580a9f3
@iritkatriel
PyMonitoringScopeBegin --> PyMonitoring_BeginScope
19a1f90
@iritkatriel
IF_ACTIVE --> _PyMonitoring_IF_ACTIVE
745a2b3
@iritkatriel
upate tests
ba5e7e0
@iritkatriel
offset is int, event_types is const, remove "opaque"
aa19675
@iritkatriel
PyMonitoring_FireLineEvent takes lineno as int
68aa352
@markshannon

markshannon commented May 1, 2024

Copy link
Copy Markdown
Member

Looking through the code for the interpeter implementation of firing events about exceptions, it looks like the exception is usually passed explicitly.

And considering that the concept of there being one (and only one) "current exception" doesn't always make sense, I think all API functions for events where the callback receives the exception should have an exception parameter.

All reactions

Sorry, something went wrong.

Uh oh!

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

@iritkatriel
Merge branch 'main' into monitoring-capi
c8499e5
@iritkatriel

iritkatriel commented May 2, 2024

Copy link
Copy Markdown
Member Author

Looking through the code for the interpeter implementation of firing events about exceptions, it looks like the exception is usually passed explicitly.

What do you mean? I see all those functions call do_monitor_exc without an exception, and it calls PyErr_GetRaisedException to get the exception.

All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon

markshannon commented May 2, 2024

Copy link
Copy Markdown
Member

What do you mean?

Good question. I mean that the exception is passed explicitly to any callback function.

From PEP 669:

func(code: CodeType, instruction_offset: int, exception: BaseException) -> DISABLE | Any

do_monitor_exc may not take an exception as an argument, but it calls _Py_call_instrumentation_arg() which does.

Passing the exception as an argument might require the user of the API to do a bit more work, but it removes a lot of uncertainty about the "current exception".

For example, consider a C extension that is about to raise an exception. To raise an exception, it must have a reference to that exception. Since it has a reference to the exception is straightforward to pass it as an argument:

PyObject *exception; /* Strong reference to the exception to be raised */
if (PyMonitoring_FireRaiseEvent(state, codelike, offset, exception) < 0) {
    /* Would could chain the exceptions, but I'm lazy, so I'm just going to discard it */
    Py_DECREF(exception);
    return NULL;
}
PyErr_SetRaisedException(exception);
return NULL;
All reactions

Sorry, something went wrong.

Uh oh!

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

@iritkatriel

iritkatriel commented May 2, 2024

Copy link
Copy Markdown
Member Author

The c api also passes the exception to the callbacks. It's the Fire functions that we removed it from.

All reactions

Sorry, something went wrong.

Uh oh!

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

@scoder

scoder commented May 2, 2024 via email

Copy link
Copy Markdown
Contributor
For example, consider a C extension that is about to raise an exception. To raise an exception, it must have a reference to that exception.
I would rather think that the authors would use something like PyErr_SetString() to create the exception, and then be annoyed by having to do the "get it, fire, set it back" dance. It seems unlikely that exception code manually creates an exception instance.
All reactions

Sorry, something went wrong.

Uh oh!

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

iritkatriel added 2 commits May 3, 2024 11:10
@iritkatriel
Merge branch 'main' into monitoring-capi
aa6aa8a
@iritkatriel
Merge branch 'main' into monitoring-capi
6c65373
@iritkatriel

iritkatriel commented May 3, 2024

Copy link
Copy Markdown
Member Author

We need to merge this pretty much now, so last call for strong objections. Otherwise we can iterate via bugfixes.

All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon

markshannon commented May 3, 2024

Copy link
Copy Markdown
Member

No strong objections.

All reactions

Sorry, something went wrong.

Uh oh!

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

@scoder

scoder commented May 3, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

the only events that should happen with the current exception set are RAISE, RERAISE, STOP_ITERATION, C_RAISE and UNWIND.

Does STOP_ITERATION really belong into this group? Isn't the idea rather that generators do not need to create a StopIteration in order to save time on termination?

Does CPython really create a StopIteration instance here just for monitoring it?

EDIT: it seems that it does:

cpython/Python/bytecodes.c

Lines 289 to 317 in 5248596

macro(END_FOR) = POP_TOP;
tier1 inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) {
/* Need to create a fake StopIteration error here,
* to conform to PEP 380 */
if (PyGen_Check(receiver)) {
PyErr_SetObject(PyExc_StopIteration, value);
if (monitor_stop_iteration(tstate, frame, this_instr)) {
ERROR_NO_POP();
}
PyErr_SetRaisedException(NULL);
}
DECREF_INPUTS();
}
pure inst(END_SEND, (receiver, value -- value)) {
Py_DECREF(receiver);
}
tier1 inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) {
if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) {
PyErr_SetObject(PyExc_StopIteration, value);
if (monitor_stop_iteration(tstate, frame, this_instr)) {
ERROR_NO_POP();
}
PyErr_SetRaisedException(NULL);
}
Py_DECREF(receiver);
}

All reactions

Sorry, something went wrong.

Uh oh!

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

@scoder

scoder commented May 3, 2024

Copy link
Copy Markdown
Contributor

That said, this is a detail that can still be decided during the beta phase, so feel free to merge this PR as it is if there's remaining need for discussion.

All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon

markshannon commented May 3, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

Does CPython really create a StopIteration instance here just for monitoring it?

Yes, but only if you ask for it (it has zero cost if you don't ask for it, thanks to the adaptive interpreter)

https://github.com/python/cpython/blob/main/Python/bytecodes.c#L291

All reactions

Sorry, something went wrong.

Uh oh!

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

@markshannon

markshannon commented May 3, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

It seems that you already found that 🙂

All reactions

Sorry, something went wrong.

Uh oh!

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

@scoder

scoder commented May 3, 2024

Copy link
Copy Markdown
Contributor

Does CPython really create a StopIteration instance here just for monitoring it?

Yes, but only if you ask for it (it has zero cost if you don't ask for it, thanks to the adaptive interpreter)

Ok … then I think it would be best if CPython's monitoring infrastructure created a StopIteration instance automatically when it detects that it needs to notify about it without having one. It's in the best position to detect whether it really needs an exception instance or not. Event sources shouldn't be forced to a) figure that out themselves or b) create an instance just for it to be thrown away.

Basically, move the existing code from the byte code handlers all the way to the other side into the notification mechanism.

All reactions

Sorry, something went wrong.

Uh oh!

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

@scoder

scoder commented May 3, 2024

Copy link
Copy Markdown
Contributor

Basically, move the existing code from the byte code handlers all the way to the other side into the notification mechanism.

That might suggest that the signature for the StopIteration event should not receive an exception or use the current one, but should better receive the StopIteration value directly (with or without a live exception).

All reactions

Sorry, something went wrong.

Uh oh!

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

@iritkatriel
Merge branch 'main' into monitoring-capi
bf84396
@iritkatriel

iritkatriel commented May 4, 2024

Copy link
Copy Markdown
Member Author

I'll merge, please create an issue to iron out the StopIteration business.

All reactions

Sorry, something went wrong.

Uh oh!

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

@iritkatriel
iritkatriel enabled auto-merge (squash) May 4, 2024 08:01
@iritkatriel

iritkatriel commented May 4, 2024

Copy link
Copy Markdown
Member Author

I'll merge, please create an issue to iron out the StopIteration business.

Or discuss it on #111997.

All reactions

Sorry, something went wrong.

Uh oh!

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

@iritkatriel
iritkatriel merged commit 85af789 into python:main May 4, 2024
@bedevere-app bedevere-app Bot removed the awaiting merge label May 4, 2024
@scoder scoder mentioned this pull request May 7, 2024
Avoid creating a StopIteration instance for monitoring #118692
Closed
@scoder

scoder commented May 7, 2024

Copy link
Copy Markdown
Contributor

In order to avoid any confusion about blocker tickets during the beta release, I created a new ticket to discuss the StopIteration behaviour/functions here: #118692

iritkatriel reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

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

SonicField pushed a commit to SonicField/cpython that referenced this pull request May 8, 2024
@iritkatriel @SonicField
pythongh-111997: C-API for signalling monitoring events (python#116413)
31f6162
scoder referenced this pull request May 19, 2024
@markshannon
GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPyth…
411b169
…on (GH-103083)

* The majority of the monitoring code is in instrumentation.c

* The new instrumentation bytecodes are in bytecodes.c

* legacy_tracing.c adapts the new API to the old sys.setrace and sys.setprofile APIs
scoder
scoder reviewed May 19, 2024
View reviewed changes
Comment thread Python/instrumentation.c
}
assert(args[2] == NULL);
args[2] = offset_obj;
Py_ssize_t nargsf = nargs | PY_VECTORCALL_ARGUMENTS_OFFSET;

@scoder scoder May 19, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

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 think this is incorrect. We call call_one_instrument() below, which passes nargsf on into _PyObject_VectorcallTstate() as a Py_ssize_t, but that actually wants a size_t, thus receiving a huge positive value:

#8  0x00005555558b0e7f in _PyObject_VectorcallTstate (kwnames=0x0, nargsf=9223372036854775811, args=0x7fffffffd048, callable=0x7ffff650d380, tstate=0x555555c2a050 <_PyRuntime+294032>) at ./Include/internal/pycore_call.h:168
#9  call_one_instrument (event=5, tool=<optimized out>, nargsf=-9223372036854775805, args=0x7fffffffd048, tstate=0x555555c2a050 <_PyRuntime+294032>, interp=<optimized out>) at Python/instrumentation.c:907
#10 capi_call_instrumentation (state=state@entry=0x7fffffffd0fa, codelike=codelike@entry=0x7ffff744cba0, offset=offset@entry=5, args=args@entry=0x7fffffffd040, nargs=<optimized out>, nargs@entry=3, event=event@entry=5) at Python/instrumentation.c:2457
#11 0x00005555558b6893 in _PyMonitoring_FireLineEvent (state=state@entry=0x7fffffffd0fa, codelike=codelike@entry=0x7ffff744cba0, offset=offset@entry=5, lineno=lineno@entry=5) at Python/instrumentation.c:2569
#12 0x00007ffff65f152c in PyMonitoring_FireLineEvent (lineno=5, offset=5, codelike=0x7ffff744cba0, state=0x7fffffffd0fa) at /opt/python3.13/include/python3.13d/cpython/monitoring.h:162

I think we should be using a size_t here directly and cast nargs before or-ing it.

That problem already seems to exist in the original sys.monitoring implementation. I've now reported it in 411b1692811b#r142178099

Edit: I now figured out that it's a minor issue, though. It gives the correct results. It still seems error prone to pass a Py_ssize_t around here because it no longer has a signed value from the moment where we or it with the argument flag.

Sorry, something went wrong.

Uh oh!

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

All reactions
scoder
scoder reviewed May 19, 2024
View reviewed changes
Comment thread Python/instrumentation.c
Comment on lines +2569 to +2571
PyObject *args[4] = { NULL, NULL, NULL, lno };
int res= capi_call_instrumentation(state, codelike, offset, args, 3,
PY_MONITORING_EVENT_LINE);

@scoder scoder May 19, 2024

Copy link
Copy Markdown
Contributor

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.

According to the documentation, the LINE event listeners only receive two arguments, not three.
https://docs.python.org/3.13/library/sys.monitoring.html#callback-function-arguments

Sorry, something went wrong.

Uh oh!

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

All reactions
@sklam sklam mentioned this pull request Mar 10, 2025
Adapt to Python 3.13 low-level monitoring C-API numba/numba#9760
Open
@godlygeek godlygeek mentioned this pull request Oct 4, 2026
PyMonitoring_FirePyStartEvent results in incorrect duplicate calls to sys.setprofile profile functions #158711
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@encukou encukou encukou left review comments
@scoder scoder scoder left review comments
@markshannon markshannon markshannon approved these changes
@erlend-aasland erlend-aasland erlend-aasland left review comments
@berkerpeksag berkerpeksag Awaiting requested review from berkerpeksag
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner

Uh oh!

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

Assignees

No one assigned

Labels

None yet

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

@iritkatriel @markshannon @encukou @scoder @erlend-aasland
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.