Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-119333: Add c-api to have contextvar enter/exit callbacks#119335
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
b8767c355e642a1e0317a0d54500359263380d6f559ec22602b4bd531ed9657546538aa7b60d6c7e5b853480c66c210351c80dd9d4b78fd3File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -27,6 +27,38 @@ PyAPI_FUNC(PyObject *) PyContext_CopyCurrent(void); | ||
| PyAPI_FUNC(int) PyContext_Enter(PyObject *); | ||
| PyAPI_FUNC(int) PyContext_Exit(PyObject *); | ||
| typedef enum { | ||
| Py_CONTEXT_EVENT_ENTER, | ||
| Py_CONTEXT_EVENT_EXIT, | ||
| } PyContextEvent; | ||
| /* | ||
| * A Callback to clue in non-python contexts impls about a | ||
| * change in the active python context. | ||
| * | ||
| * The callback is invoked with the event and a reference to = | ||
Contributor 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. Remove the | ||
| * the context after its entered and before its exited. | ||
| * | ||
| * if the callback returns with an exception set, it must return -1. Otherwise | ||
| * it should return 0 | ||
| */ | ||
| typedef int (*PyContext_WatchCallback)(PyContextEvent, PyContext *); | ||
| /* | ||
| * Register a per-interpreter callback that will be invoked for context object | ||
| * enter/exit events. | ||
| * | ||
| * Returns a handle that may be passed to PyContext_ClearWatcher on success, | ||
| * or -1 and sets and error if no more handles are available. | ||
| */ | ||
| PyAPI_FUNC(int) PyContext_AddWatcher(PyContext_WatchCallback callback); | ||
| /* | ||
| * Clear the watcher associated with the watcher_id handle. | ||
| * | ||
| * Returns 0 on success or -1 if no watcher exists for the provided id. | ||
| */ | ||
| PyAPI_FUNC(int) PyContext_ClearWatcher(int watcher_id); | ||
| /* Create a new context variable. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Add :c:func:`PyContext_AddWatcher` and :c:func:`PyContext_ClearWatcher` APIs to | ||
| register callbacks to receive notification on enter and exit of context objects. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -8,6 +8,7 @@ | ||
| #define Py_BUILD_CORE | ||
| #include "pycore_function.h" // FUNC_MAX_WATCHERS | ||
| #include "pycore_code.h" // CODE_MAX_WATCHERS | ||
| #include "pycore_context.h" // CONTEXT_MAX_WATCHERS | ||
| /*[clinic input] | ||
| module _testcapi | ||
| @@ -622,6 +623,147 @@ allocate_too_many_func_watchers(PyObject *self, PyObject *args) | ||
| Py_RETURN_NONE; | ||
| } | ||
| // Test contexct object watchers | ||
| #define NUM_CONTEXT_WATCHERS 2 | ||
| static int context_watcher_ids[NUM_CONTEXT_WATCHERS] = {-1, -1}; | ||
| static int num_context_object_enter_events[NUM_CONTEXT_WATCHERS] = {0, 0}; | ||
| static int num_context_object_exit_events[NUM_CONTEXT_WATCHERS] = {0, 0}; | ||
| static int | ||
| handle_context_watcher_event(int which_watcher, PyContextEvent event, PyContext *ctx) { | ||
| if (event == Py_CONTEXT_EVENT_ENTER) { | ||
| num_context_object_enter_events[which_watcher]++; | ||
| } | ||
| else if (event == Py_CONTEXT_EVENT_EXIT) { | ||
| num_context_object_exit_events[which_watcher]++; | ||
| } | ||
| else { | ||
| return -1; | ||
fried marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| return 0; | ||
| } | ||
| static int | ||
| first_context_watcher_callback(PyContextEvent event, PyContext *ctx) { | ||
| return handle_context_watcher_event(0, event, ctx); | ||
| } | ||
| static int | ||
| second_context_watcher_callback(PyContextEvent event, PyContext *ctx) { | ||
| return handle_context_watcher_event(1, event, ctx); | ||
| } | ||
| static int | ||
| noop_context_event_handler(PyContextEvent event, PyContext *ctx) { | ||
| return 0; | ||
| } | ||
| static int | ||
| error_context_event_handler(PyContextEvent event, PyContext *ctx) { | ||
| PyErr_SetString(PyExc_RuntimeError, "boom!"); | ||
| return -1; | ||
| } | ||
| static PyObject * | ||
| add_context_watcher(PyObject *self, PyObject *which_watcher) | ||
| { | ||
| int watcher_id; | ||
| assert(PyLong_Check(which_watcher)); | ||
| long which_l = PyLong_AsLong(which_watcher); | ||
| if (which_l == 0) { | ||
| watcher_id = PyContext_AddWatcher(first_context_watcher_callback); | ||
| context_watcher_ids[0] = watcher_id; | ||
| num_context_object_enter_events[0] = 0; | ||
| num_context_object_exit_events[0] = 0; | ||
| } | ||
| else if (which_l == 1) { | ||
| watcher_id = PyContext_AddWatcher(second_context_watcher_callback); | ||
| context_watcher_ids[1] = watcher_id; | ||
| num_context_object_enter_events[1] = 0; | ||
| num_context_object_exit_events[1] = 0; | ||
| } | ||
| else if (which_l == 2) { | ||
| watcher_id = PyContext_AddWatcher(error_context_event_handler); | ||
| } | ||
| else { | ||
| PyErr_Format(PyExc_ValueError, "invalid watcher %d", which_l); | ||
| return NULL; | ||
| } | ||
| if (watcher_id < 0) { | ||
| return NULL; | ||
| } | ||
| return PyLong_FromLong(watcher_id); | ||
| } | ||
| static PyObject * | ||
| clear_context_watcher(PyObject *self, PyObject *watcher_id) | ||
| { | ||
| assert(PyLong_Check(watcher_id)); | ||
| long watcher_id_l = PyLong_AsLong(watcher_id); | ||
| if (PyContext_ClearWatcher(watcher_id_l) < 0) { | ||
| return NULL; | ||
| } | ||
| // reset static events counters | ||
| if (watcher_id_l >= 0) { | ||
| for (int i = 0; i < NUM_CONTEXT_WATCHERS; i++) { | ||
| if (watcher_id_l == context_watcher_ids[i]) { | ||
| context_watcher_ids[i] = -1; | ||
| num_context_object_enter_events[i] = 0; | ||
| num_context_object_exit_events[i] = 0; | ||
| } | ||
| } | ||
| } | ||
| Py_RETURN_NONE; | ||
| } | ||
| static PyObject * | ||
| get_context_watcher_num_enter_events(PyObject *self, PyObject *watcher_id) | ||
| { | ||
| assert(PyLong_Check(watcher_id)); | ||
| long watcher_id_l = PyLong_AsLong(watcher_id); | ||
| assert(watcher_id_l >= 0 && watcher_id_l < NUM_CONTEXT_WATCHERS); | ||
| return PyLong_FromLong(num_context_object_enter_events[watcher_id_l]); | ||
| } | ||
| static PyObject * | ||
| get_context_watcher_num_exit_events(PyObject *self, PyObject *watcher_id) | ||
| { | ||
| assert(PyLong_Check(watcher_id)); | ||
| long watcher_id_l = PyLong_AsLong(watcher_id); | ||
| assert(watcher_id_l >= 0 && watcher_id_l < NUM_CONTEXT_WATCHERS); | ||
| return PyLong_FromLong(num_context_object_exit_events[watcher_id_l]); | ||
| } | ||
| static PyObject * | ||
| allocate_too_many_context_watchers(PyObject *self, PyObject *args) | ||
| { | ||
| int watcher_ids[CONTEXT_MAX_WATCHERS + 1]; | ||
| int num_watchers = 0; | ||
| for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) { | ||
| int watcher_id = PyContext_AddWatcher(noop_context_event_handler); | ||
| if (watcher_id == -1) { | ||
| break; | ||
| } | ||
| watcher_ids[i] = watcher_id; | ||
| num_watchers++; | ||
| } | ||
| PyObject *exc = PyErr_GetRaisedException(); | ||
| for (int i = 0; i < num_watchers; i++) { | ||
| if (PyContext_ClearWatcher(watcher_ids[i]) < 0) { | ||
| PyErr_WriteUnraisable(Py_None); | ||
| break; | ||
| } | ||
| } | ||
| if (exc) { | ||
| PyErr_SetRaisedException(exc); | ||
| return NULL; | ||
| } | ||
| else if (PyErr_Occurred()) { | ||
| return NULL; | ||
| } | ||
| Py_RETURN_NONE; | ||
| } | ||
| /*[clinic input] | ||
| _testcapi.set_func_defaults_via_capi | ||
| func: object | ||
| @@ -689,6 +831,16 @@ static PyMethodDef test_methods[] = { | ||
| _TESTCAPI_SET_FUNC_KWDEFAULTS_VIA_CAPI_METHODDEF | ||
| {"allocate_too_many_func_watchers", allocate_too_many_func_watchers, | ||
| METH_NOARGS, NULL}, | ||
| // Code object watchers. | ||
| {"add_context_watcher", add_context_watcher, METH_O, NULL}, | ||
| {"clear_context_watcher", clear_context_watcher, METH_O, NULL}, | ||
| {"get_context_watcher_num_enter_events", | ||
| get_context_watcher_num_enter_events, METH_O, NULL}, | ||
| {"get_context_watcher_num_exit_events", | ||
| get_context_watcher_num_exit_events, METH_O, NULL}, | ||
| {"allocate_too_many_context_watchers", | ||
| (PyCFunction) allocate_too_many_context_watchers, METH_NOARGS, NULL}, | ||
| {NULL}, | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.
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 don't understand this comment, what is meant by clue in? Also what is change in active python context?