-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
gh-91053: Add an optional callback that is invoked whenever a function is modified #97834
Conversation
JIT compilers may need to invalidate compiled code when a function is modified (e.g. if its code object is modified). This adds the ability to set a callback that, when set, is called each time a function is modified.
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
The primary use case for this is a JIT, which will be per-runtime, not per-interpreter, so move the callback onto the runtime.
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
… be deleted This should would across platforms and implementations.
pyperformance results for the updated version where the callback is stored on the interpreter state still look perf-neutral: https://gist.github.com/mpage/26d2efdf0f6f166cf536c1ee9e5841a9 I wrote a microbenchmark to attempt to measure the overhead this imposes on function creation when no callback is set: https://gist.github.com/mpage/b3006c3b490460272d7a95438345b3f3 The overhead looks like it's small enough to be lost in the noise. Happy to use a different benchmark or a quieter machine if one is available. Upstream:
This PR with no modified callback set:
|
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.
You should also add documentation for the new API in Doc/c-api/function.rst
; don't forget the versionadded
tag which I missed in the original dict watchers PR, too, and also add mention in Doc/whatsnew/3.12.rst
.
Also it's worth running your added tests with -R
just to ensure no accidental refleaks; CI won't catch that until post-land.
Include/cpython/funcobject.h
Outdated
* | ||
* Pass NULL to clear the callback. | ||
*/ | ||
PyAPI_FUNC(void) PyFunction_SetEventCallback(PyFunction_EventCallback callback); |
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 we are going to want to support multiple watchers (up to 8, to match dict and type watchers), which means the API here should look like int PyFunction_AddWatcher
which gives you back an id 0-7 (or -1 and sets an exception if there are no more watcher IDs available), and also PyFunction_ClearWatcher(int watcher_id)
to clear a watcher. Can look at the dict watcher API in Include/cpython/dictobject.h
and Python/dictobject.c
to make it as parallel as we can. (Obviously with the difference that here we aren't doing per-function watching so there's no equivalent to PyDict_Watch
API.)
Include/internal/pycore_interp.h
Outdated
@@ -175,6 +175,9 @@ struct _is { | |||
struct types_state types; | |||
struct callable_cache callable_cache; | |||
|
|||
|
|||
PyFunction_EventCallback func_event_callback; |
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.
So this will be PyFunction_EventCallback func_event_callbacks[FUNC_MAX_WATCHERS];
or similar.
Objects/funcobject.c
Outdated
void | ||
PyFunction_SetEventCallback(PyFunction_EventCallback callback) | ||
{ | ||
PyThreadState *tstate = _PyThreadState_GET(); |
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.
You can also do PyInterpreterState *interp = _PyInterpreterState_GET();
directly since that's all you're using.
…yncioTestCase (python#93369) Lay the foundation for further work in `asyncio.test_streams`.
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Doc: Add references to PEP 686.
…eError suggestions (python#97022) Relevant tests moved from test_exceptions to test_traceback to be able to compare both implementations. Co-authored-by: Carl Friedrich Bolz-Tereick <cfbolz@gmx.de>
Ugh I botched rebasing this. Sorry for the noise everyone. Will open a new PR. |
@mpage once there's been some reviews you should always merge from main, never rebase |
JIT compilers may need to invalidate compiled code when a function is modified (e.g. if its code object is modified). This adds the ability to set a callback that, when set, is called each time a function is modified.
Pyperformance results look neutral: https://gist.github.com/mpage/0ffeaa76fa759c8230e91415eec44be3