Skip to content
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

Closed
wants to merge 152 commits into from

Conversation

mpage
Copy link
Contributor

@mpage mpage commented Oct 4, 2022

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

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.
@bedevere-bot
Copy link

Most changes to Python require a NEWS entry.

Please add it using the blurb_it web app or the blurb command-line tool.

@cpython-cla-bot
Copy link

cpython-cla-bot bot commented Oct 4, 2022

All commit authors signed the Contributor License Agreement.
CLA signed

@bedevere-bot
Copy link

Most changes to Python require a NEWS entry.

Please add it using the blurb_it web app or the blurb command-line tool.

Modules/_testcapi/func_events.c Outdated Show resolved Hide resolved
Objects/funcobject.c Outdated Show resolved Hide resolved
The primary use case for this is a JIT, which will be per-runtime, not
per-interpreter, so move the callback onto the runtime.
@mpage mpage requested a review from a team as a code owner October 5, 2022 00:39
@bedevere-bot
Copy link

Most changes to Python require a NEWS entry.

Please add it using the blurb_it web app or the blurb command-line tool.

@mpage mpage marked this pull request as draft October 5, 2022 01:42
@mpage mpage marked this pull request as ready for review October 5, 2022 23:59
@mpage
Copy link
Contributor Author

mpage commented Oct 6, 2022

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:

mpage@ip-172-31-41-159:~$ ./upstream/python bench_comprehensions.py
0.07469153602141887
mpage@ip-172-31-41-159:~$ ./upstream/python bench_comprehensions.py
0.07751996896695346
mpage@ip-172-31-41-159:~$ ./upstream/python bench_comprehensions.py
0.07642328296788037
mpage@ip-172-31-41-159:~$ ./upstream/python bench_comprehensions.py
0.0777301819762215
mpage@ip-172-31-41-159:~$ ./upstream/python bench_comprehensions.py
0.07707617298001423

This PR with no modified callback set:

mpage@ip-172-31-41-159:~$ ./cpython/python bench_comprehensions.py
0.07540378603152931
mpage@ip-172-31-41-159:~$ ./cpython/python bench_comprehensions.py
0.07542523700976744
mpage@ip-172-31-41-159:~$ ./cpython/python bench_comprehensions.py
0.07418339903233573
mpage@ip-172-31-41-159:~$ ./cpython/python bench_comprehensions.py
0.07634932000655681
mpage@ip-172-31-41-159:~$ ./cpython/python bench_comprehensions.py
0.07911138999043033

Copy link
Member

@carljm carljm left a 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.

*
* Pass NULL to clear the callback.
*/
PyAPI_FUNC(void) PyFunction_SetEventCallback(PyFunction_EventCallback callback);
Copy link
Member

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.)

@@ -175,6 +175,9 @@ struct _is {
struct types_state types;
struct callable_cache callable_cache;


PyFunction_EventCallback func_event_callback;
Copy link
Member

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.

void
PyFunction_SetEventCallback(PyFunction_EventCallback callback)
{
PyThreadState *tstate = _PyThreadState_GET();
Copy link
Member

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.

arhadthedev and others added 6 commits October 10, 2022 17:16
…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>
)

* fix documentation for _thread.lock.acquire

* update formatting of _thread.lock.acquire() doc
…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>
@mpage
Copy link
Contributor Author

mpage commented Oct 11, 2022

Ugh I botched rebasing this. Sorry for the noise everyone. Will open a new PR.

@mpage mpage closed this Oct 11, 2022
@carljm
Copy link
Member

carljm commented Oct 11, 2022

@mpage once there's been some reviews you should always merge from main, never rebase

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.