mirror of
https://github.com/python/cpython.git
synced 2024-11-24 08:52:25 +01:00
bee112a94d
Users want to know when the current context switches to a different context object. Right now this happens when and only when a context is entered or exited, so the enter and exit events are synonymous with "switched". However, if the changes proposed for gh-99633 are implemented, the current context will also switch for reasons other than context enter or exit. Since users actually care about context switches and not enter or exit, replace the enter and exit events with a single switched event. The former exit event was emitted just before exiting the context. The new switched event is emitted after the context is exited to match the semantics users expect of an event with a past-tense name. If users need the ability to clean up before the switch takes effect, another event type can be added in the future. It is not added here because YAGNI. I skipped 0 in the enum as a matter of practice. Skipping 0 makes it easier to troubleshoot when code forgets to set zeroed memory, and it aligns with best practices for other tools (e.g., https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum). Co-authored-by: Richard Hansen <rhansen@rhansen.org> Co-authored-by: Victor Stinner <vstinner@python.org>
868 lines
24 KiB
C
868 lines
24 KiB
C
// clinic/watchers.c.h uses internal pycore_modsupport.h API
|
|
#define PYTESTCAPI_NEED_INTERNAL_API
|
|
|
|
#include "parts.h"
|
|
|
|
#include "clinic/watchers.c.h"
|
|
|
|
#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
|
|
[clinic start generated code]*/
|
|
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/
|
|
|
|
// Test dict watching
|
|
static PyObject *g_dict_watch_events = NULL;
|
|
static int g_dict_watchers_installed = 0;
|
|
|
|
static int
|
|
dict_watch_callback(PyDict_WatchEvent event,
|
|
PyObject *dict,
|
|
PyObject *key,
|
|
PyObject *new_value)
|
|
{
|
|
PyObject *msg;
|
|
switch (event) {
|
|
case PyDict_EVENT_CLEARED:
|
|
msg = PyUnicode_FromString("clear");
|
|
break;
|
|
case PyDict_EVENT_DEALLOCATED:
|
|
msg = PyUnicode_FromString("dealloc");
|
|
break;
|
|
case PyDict_EVENT_CLONED:
|
|
msg = PyUnicode_FromString("clone");
|
|
break;
|
|
case PyDict_EVENT_ADDED:
|
|
msg = PyUnicode_FromFormat("new:%S:%S", key, new_value);
|
|
break;
|
|
case PyDict_EVENT_MODIFIED:
|
|
msg = PyUnicode_FromFormat("mod:%S:%S", key, new_value);
|
|
break;
|
|
case PyDict_EVENT_DELETED:
|
|
msg = PyUnicode_FromFormat("del:%S", key);
|
|
break;
|
|
default:
|
|
msg = PyUnicode_FromString("unknown");
|
|
}
|
|
if (msg == NULL) {
|
|
return -1;
|
|
}
|
|
assert(PyList_Check(g_dict_watch_events));
|
|
if (PyList_Append(g_dict_watch_events, msg) < 0) {
|
|
Py_DECREF(msg);
|
|
return -1;
|
|
}
|
|
Py_DECREF(msg);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dict_watch_callback_second(PyDict_WatchEvent event,
|
|
PyObject *dict,
|
|
PyObject *key,
|
|
PyObject *new_value)
|
|
{
|
|
PyObject *msg = PyUnicode_FromString("second");
|
|
if (msg == NULL) {
|
|
return -1;
|
|
}
|
|
int rc = PyList_Append(g_dict_watch_events, msg);
|
|
Py_DECREF(msg);
|
|
if (rc < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dict_watch_callback_error(PyDict_WatchEvent event,
|
|
PyObject *dict,
|
|
PyObject *key,
|
|
PyObject *new_value)
|
|
{
|
|
PyErr_SetString(PyExc_RuntimeError, "boom!");
|
|
return -1;
|
|
}
|
|
|
|
static PyObject *
|
|
add_dict_watcher(PyObject *self, PyObject *kind)
|
|
{
|
|
int watcher_id;
|
|
assert(PyLong_Check(kind));
|
|
long kind_l = PyLong_AsLong(kind);
|
|
if (kind_l == 2) {
|
|
watcher_id = PyDict_AddWatcher(dict_watch_callback_second);
|
|
}
|
|
else if (kind_l == 1) {
|
|
watcher_id = PyDict_AddWatcher(dict_watch_callback_error);
|
|
}
|
|
else {
|
|
watcher_id = PyDict_AddWatcher(dict_watch_callback);
|
|
}
|
|
if (watcher_id < 0) {
|
|
return NULL;
|
|
}
|
|
if (!g_dict_watchers_installed) {
|
|
assert(!g_dict_watch_events);
|
|
if (!(g_dict_watch_events = PyList_New(0))) {
|
|
return NULL;
|
|
}
|
|
}
|
|
g_dict_watchers_installed++;
|
|
return PyLong_FromLong(watcher_id);
|
|
}
|
|
|
|
static PyObject *
|
|
clear_dict_watcher(PyObject *self, PyObject *watcher_id)
|
|
{
|
|
if (PyDict_ClearWatcher(PyLong_AsLong(watcher_id))) {
|
|
return NULL;
|
|
}
|
|
g_dict_watchers_installed--;
|
|
if (!g_dict_watchers_installed) {
|
|
assert(g_dict_watch_events);
|
|
Py_CLEAR(g_dict_watch_events);
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
/*[clinic input]
|
|
_testcapi.watch_dict
|
|
watcher_id: int
|
|
dict: object
|
|
/
|
|
[clinic start generated code]*/
|
|
|
|
static PyObject *
|
|
_testcapi_watch_dict_impl(PyObject *module, int watcher_id, PyObject *dict)
|
|
/*[clinic end generated code: output=1426e0273cebe2d8 input=269b006d60c358bd]*/
|
|
{
|
|
if (PyDict_Watch(watcher_id, dict)) {
|
|
return NULL;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
/*[clinic input]
|
|
_testcapi.unwatch_dict = _testcapi.watch_dict
|
|
[clinic start generated code]*/
|
|
|
|
static PyObject *
|
|
_testcapi_unwatch_dict_impl(PyObject *module, int watcher_id, PyObject *dict)
|
|
/*[clinic end generated code: output=512b1a71ae33c351 input=cae7dc1b6f7713b8]*/
|
|
{
|
|
if (PyDict_Unwatch(watcher_id, dict)) {
|
|
return NULL;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *
|
|
get_dict_watcher_events(PyObject *self, PyObject *Py_UNUSED(args))
|
|
{
|
|
if (!g_dict_watch_events) {
|
|
PyErr_SetString(PyExc_RuntimeError, "no watchers active");
|
|
return NULL;
|
|
}
|
|
return Py_NewRef(g_dict_watch_events);
|
|
}
|
|
|
|
// Test type watchers
|
|
static PyObject *g_type_modified_events;
|
|
static int g_type_watchers_installed;
|
|
|
|
static int
|
|
type_modified_callback(PyTypeObject *type)
|
|
{
|
|
assert(PyList_Check(g_type_modified_events));
|
|
if(PyList_Append(g_type_modified_events, (PyObject *)type) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
type_modified_callback_wrap(PyTypeObject *type)
|
|
{
|
|
assert(PyList_Check(g_type_modified_events));
|
|
PyObject *list = PyList_New(0);
|
|
if (list == NULL) {
|
|
return -1;
|
|
}
|
|
if (PyList_Append(list, (PyObject *)type) < 0) {
|
|
Py_DECREF(list);
|
|
return -1;
|
|
}
|
|
if (PyList_Append(g_type_modified_events, list) < 0) {
|
|
Py_DECREF(list);
|
|
return -1;
|
|
}
|
|
Py_DECREF(list);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
type_modified_callback_error(PyTypeObject *type)
|
|
{
|
|
PyErr_SetString(PyExc_RuntimeError, "boom!");
|
|
return -1;
|
|
}
|
|
|
|
static PyObject *
|
|
add_type_watcher(PyObject *self, PyObject *kind)
|
|
{
|
|
int watcher_id;
|
|
assert(PyLong_Check(kind));
|
|
long kind_l = PyLong_AsLong(kind);
|
|
if (kind_l == 2) {
|
|
watcher_id = PyType_AddWatcher(type_modified_callback_wrap);
|
|
}
|
|
else if (kind_l == 1) {
|
|
watcher_id = PyType_AddWatcher(type_modified_callback_error);
|
|
}
|
|
else {
|
|
watcher_id = PyType_AddWatcher(type_modified_callback);
|
|
}
|
|
if (watcher_id < 0) {
|
|
return NULL;
|
|
}
|
|
if (!g_type_watchers_installed) {
|
|
assert(!g_type_modified_events);
|
|
if (!(g_type_modified_events = PyList_New(0))) {
|
|
return NULL;
|
|
}
|
|
}
|
|
g_type_watchers_installed++;
|
|
return PyLong_FromLong(watcher_id);
|
|
}
|
|
|
|
static PyObject *
|
|
clear_type_watcher(PyObject *self, PyObject *watcher_id)
|
|
{
|
|
if (PyType_ClearWatcher(PyLong_AsLong(watcher_id))) {
|
|
return NULL;
|
|
}
|
|
g_type_watchers_installed--;
|
|
if (!g_type_watchers_installed) {
|
|
assert(g_type_modified_events);
|
|
Py_CLEAR(g_type_modified_events);
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *
|
|
get_type_modified_events(PyObject *self, PyObject *Py_UNUSED(args))
|
|
{
|
|
if (!g_type_modified_events) {
|
|
PyErr_SetString(PyExc_RuntimeError, "no watchers active");
|
|
return NULL;
|
|
}
|
|
return Py_NewRef(g_type_modified_events);
|
|
}
|
|
|
|
/*[clinic input]
|
|
_testcapi.watch_type
|
|
watcher_id: int
|
|
type: object
|
|
/
|
|
[clinic start generated code]*/
|
|
|
|
static PyObject *
|
|
_testcapi_watch_type_impl(PyObject *module, int watcher_id, PyObject *type)
|
|
/*[clinic end generated code: output=fdf4777126724fc4 input=5a808bf12be7e3ed]*/
|
|
{
|
|
if (PyType_Watch(watcher_id, type)) {
|
|
return NULL;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
/*[clinic input]
|
|
_testcapi.unwatch_type = _testcapi.watch_type
|
|
[clinic start generated code]*/
|
|
|
|
static PyObject *
|
|
_testcapi_unwatch_type_impl(PyObject *module, int watcher_id, PyObject *type)
|
|
/*[clinic end generated code: output=0389672d4ad5f68b input=6701911fb45edc9e]*/
|
|
{
|
|
if (PyType_Unwatch(watcher_id, type)) {
|
|
return NULL;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// Test code object watching
|
|
|
|
#define NUM_CODE_WATCHERS 2
|
|
static int code_watcher_ids[NUM_CODE_WATCHERS] = {-1, -1};
|
|
static int num_code_object_created_events[NUM_CODE_WATCHERS] = {0, 0};
|
|
static int num_code_object_destroyed_events[NUM_CODE_WATCHERS] = {0, 0};
|
|
|
|
static int
|
|
handle_code_object_event(int which_watcher, PyCodeEvent event, PyCodeObject *co) {
|
|
if (event == PY_CODE_EVENT_CREATE) {
|
|
num_code_object_created_events[which_watcher]++;
|
|
}
|
|
else if (event == PY_CODE_EVENT_DESTROY) {
|
|
num_code_object_destroyed_events[which_watcher]++;
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
first_code_object_callback(PyCodeEvent event, PyCodeObject *co)
|
|
{
|
|
return handle_code_object_event(0, event, co);
|
|
}
|
|
|
|
static int
|
|
second_code_object_callback(PyCodeEvent event, PyCodeObject *co)
|
|
{
|
|
return handle_code_object_event(1, event, co);
|
|
}
|
|
|
|
static int
|
|
noop_code_event_handler(PyCodeEvent event, PyCodeObject *co)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
error_code_event_handler(PyCodeEvent event, PyCodeObject *co)
|
|
{
|
|
PyErr_SetString(PyExc_RuntimeError, "boom!");
|
|
return -1;
|
|
}
|
|
|
|
static PyObject *
|
|
add_code_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 = PyCode_AddWatcher(first_code_object_callback);
|
|
code_watcher_ids[0] = watcher_id;
|
|
num_code_object_created_events[0] = 0;
|
|
num_code_object_destroyed_events[0] = 0;
|
|
}
|
|
else if (which_l == 1) {
|
|
watcher_id = PyCode_AddWatcher(second_code_object_callback);
|
|
code_watcher_ids[1] = watcher_id;
|
|
num_code_object_created_events[1] = 0;
|
|
num_code_object_destroyed_events[1] = 0;
|
|
}
|
|
else if (which_l == 2) {
|
|
watcher_id = PyCode_AddWatcher(error_code_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_code_watcher(PyObject *self, PyObject *watcher_id)
|
|
{
|
|
assert(PyLong_Check(watcher_id));
|
|
long watcher_id_l = PyLong_AsLong(watcher_id);
|
|
if (PyCode_ClearWatcher(watcher_id_l) < 0) {
|
|
return NULL;
|
|
}
|
|
// reset static events counters
|
|
if (watcher_id_l >= 0) {
|
|
for (int i = 0; i < NUM_CODE_WATCHERS; i++) {
|
|
if (watcher_id_l == code_watcher_ids[i]) {
|
|
code_watcher_ids[i] = -1;
|
|
num_code_object_created_events[i] = 0;
|
|
num_code_object_destroyed_events[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *
|
|
get_code_watcher_num_created_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_CODE_WATCHERS);
|
|
return PyLong_FromLong(num_code_object_created_events[watcher_id_l]);
|
|
}
|
|
|
|
static PyObject *
|
|
get_code_watcher_num_destroyed_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_CODE_WATCHERS);
|
|
return PyLong_FromLong(num_code_object_destroyed_events[watcher_id_l]);
|
|
}
|
|
|
|
static PyObject *
|
|
allocate_too_many_code_watchers(PyObject *self, PyObject *args)
|
|
{
|
|
int watcher_ids[CODE_MAX_WATCHERS + 1];
|
|
int num_watchers = 0;
|
|
for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) {
|
|
int watcher_id = PyCode_AddWatcher(noop_code_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 (PyCode_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;
|
|
}
|
|
|
|
// Test function watchers
|
|
|
|
#define NUM_TEST_FUNC_WATCHERS 2
|
|
static PyObject *pyfunc_watchers[NUM_TEST_FUNC_WATCHERS];
|
|
static int func_watcher_ids[NUM_TEST_FUNC_WATCHERS] = {-1, -1};
|
|
|
|
static PyObject *
|
|
get_id(PyObject *obj)
|
|
{
|
|
PyObject *builtins = PyEval_GetBuiltins(); // borrowed ref.
|
|
if (builtins == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *id_str = PyUnicode_FromString("id");
|
|
if (id_str == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *id_func = PyObject_GetItem(builtins, id_str);
|
|
Py_DECREF(id_str);
|
|
if (id_func == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *stack[] = {obj};
|
|
PyObject *id = PyObject_Vectorcall(id_func, stack, 1, NULL);
|
|
Py_DECREF(id_func);
|
|
return id;
|
|
}
|
|
|
|
static int
|
|
call_pyfunc_watcher(PyObject *watcher, PyFunction_WatchEvent event,
|
|
PyFunctionObject *func, PyObject *new_value)
|
|
{
|
|
PyObject *event_obj = PyLong_FromLong(event);
|
|
if (event_obj == NULL) {
|
|
return -1;
|
|
}
|
|
if (new_value == NULL) {
|
|
new_value = Py_None;
|
|
}
|
|
Py_INCREF(new_value);
|
|
PyObject *func_or_id = NULL;
|
|
if (event == PyFunction_EVENT_DESTROY) {
|
|
/* Don't expose a function that's about to be destroyed to managed code */
|
|
func_or_id = get_id((PyObject *) func);
|
|
if (func_or_id == NULL) {
|
|
Py_DECREF(event_obj);
|
|
Py_DECREF(new_value);
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
Py_INCREF(func);
|
|
func_or_id = (PyObject *) func;
|
|
}
|
|
PyObject *stack[] = {event_obj, func_or_id, new_value};
|
|
PyObject *res = PyObject_Vectorcall(watcher, stack, 3, NULL);
|
|
int st = (res == NULL) ? -1 : 0;
|
|
Py_XDECREF(res);
|
|
Py_DECREF(new_value);
|
|
Py_DECREF(event_obj);
|
|
Py_DECREF(func_or_id);
|
|
return st;
|
|
}
|
|
|
|
static int
|
|
first_func_watcher_callback(PyFunction_WatchEvent event, PyFunctionObject *func,
|
|
PyObject *new_value)
|
|
{
|
|
return call_pyfunc_watcher(pyfunc_watchers[0], event, func, new_value);
|
|
}
|
|
|
|
static int
|
|
second_func_watcher_callback(PyFunction_WatchEvent event,
|
|
PyFunctionObject *func, PyObject *new_value)
|
|
{
|
|
return call_pyfunc_watcher(pyfunc_watchers[1], event, func, new_value);
|
|
}
|
|
|
|
static PyFunction_WatchCallback func_watcher_callbacks[NUM_TEST_FUNC_WATCHERS] = {
|
|
first_func_watcher_callback,
|
|
second_func_watcher_callback
|
|
};
|
|
|
|
static int
|
|
add_func_event(PyObject *module, const char *name, PyFunction_WatchEvent event)
|
|
{
|
|
return PyModule_Add(module, name, PyLong_FromLong(event));
|
|
}
|
|
|
|
static PyObject *
|
|
add_func_watcher(PyObject *self, PyObject *func)
|
|
{
|
|
if (!PyFunction_Check(func)) {
|
|
PyErr_SetString(PyExc_TypeError, "'func' must be a function");
|
|
return NULL;
|
|
}
|
|
int idx = -1;
|
|
for (int i = 0; i < NUM_TEST_FUNC_WATCHERS; i++) {
|
|
if (func_watcher_ids[i] == -1) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (idx == -1) {
|
|
PyErr_SetString(PyExc_RuntimeError, "no free test watchers");
|
|
return NULL;
|
|
}
|
|
func_watcher_ids[idx] = PyFunction_AddWatcher(func_watcher_callbacks[idx]);
|
|
if (func_watcher_ids[idx] < 0) {
|
|
return NULL;
|
|
}
|
|
pyfunc_watchers[idx] = Py_NewRef(func);
|
|
PyObject *result = PyLong_FromLong(func_watcher_ids[idx]);
|
|
if (result == NULL) {
|
|
return NULL;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static PyObject *
|
|
clear_func_watcher(PyObject *self, PyObject *watcher_id_obj)
|
|
{
|
|
long watcher_id = PyLong_AsLong(watcher_id_obj);
|
|
if ((watcher_id < INT_MIN) || (watcher_id > INT_MAX)) {
|
|
PyErr_SetString(PyExc_ValueError, "invalid watcher ID");
|
|
return NULL;
|
|
}
|
|
int wid = (int) watcher_id;
|
|
if (PyFunction_ClearWatcher(wid) < 0) {
|
|
return NULL;
|
|
}
|
|
int idx = -1;
|
|
for (int i = 0; i < NUM_TEST_FUNC_WATCHERS; i++) {
|
|
if (func_watcher_ids[i] == wid) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
assert(idx != -1);
|
|
Py_CLEAR(pyfunc_watchers[idx]);
|
|
func_watcher_ids[idx] = -1;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static int
|
|
noop_func_event_handler(PyFunction_WatchEvent event, PyFunctionObject *func,
|
|
PyObject *new_value)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *
|
|
allocate_too_many_func_watchers(PyObject *self, PyObject *args)
|
|
{
|
|
int watcher_ids[FUNC_MAX_WATCHERS + 1];
|
|
int num_watchers = 0;
|
|
for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) {
|
|
int watcher_id = PyFunction_AddWatcher(noop_func_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 (PyFunction_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;
|
|
}
|
|
|
|
// Test contexct object watchers
|
|
#define NUM_CONTEXT_WATCHERS 2
|
|
static int context_watcher_ids[NUM_CONTEXT_WATCHERS] = {-1, -1};
|
|
static PyObject *context_switches[NUM_CONTEXT_WATCHERS];
|
|
|
|
static int
|
|
handle_context_watcher_event(int which_watcher, PyContextEvent event, PyObject *ctx) {
|
|
if (event == Py_CONTEXT_SWITCHED) {
|
|
PyList_Append(context_switches[which_watcher], ctx);
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
first_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
|
|
return handle_context_watcher_event(0, event, ctx);
|
|
}
|
|
|
|
static int
|
|
second_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
|
|
return handle_context_watcher_event(1, event, ctx);
|
|
}
|
|
|
|
static int
|
|
noop_context_event_handler(PyContextEvent event, PyObject *ctx) {
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
error_context_event_handler(PyContextEvent event, PyObject *ctx) {
|
|
PyErr_SetString(PyExc_RuntimeError, "boom!");
|
|
return -1;
|
|
}
|
|
|
|
static PyObject *
|
|
add_context_watcher(PyObject *self, PyObject *which_watcher)
|
|
{
|
|
static const PyContext_WatchCallback callbacks[] = {
|
|
&first_context_watcher_callback,
|
|
&second_context_watcher_callback,
|
|
&error_context_event_handler,
|
|
};
|
|
assert(PyLong_Check(which_watcher));
|
|
long which_l = PyLong_AsLong(which_watcher);
|
|
if (which_l < 0 || which_l >= (long)Py_ARRAY_LENGTH(callbacks)) {
|
|
PyErr_Format(PyExc_ValueError, "invalid watcher %d", which_l);
|
|
return NULL;
|
|
}
|
|
int watcher_id = PyContext_AddWatcher(callbacks[which_l]);
|
|
if (watcher_id < 0) {
|
|
return NULL;
|
|
}
|
|
if (which_l >= 0 && which_l < NUM_CONTEXT_WATCHERS) {
|
|
context_watcher_ids[which_l] = watcher_id;
|
|
Py_XSETREF(context_switches[which_l], PyList_New(0));
|
|
if (context_switches[which_l] == NULL) {
|
|
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;
|
|
Py_CLEAR(context_switches[i]);
|
|
}
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *
|
|
clear_context_stack(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
|
|
{
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
if (tstate->context == NULL) {
|
|
Py_RETURN_NONE;
|
|
}
|
|
if (((PyContext *)tstate->context)->ctx_prev != NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError,
|
|
"must first exit all non-base contexts");
|
|
return NULL;
|
|
}
|
|
Py_CLEAR(tstate->context);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *
|
|
get_context_switches(PyObject *Py_UNUSED(self), PyObject *watcher_id)
|
|
{
|
|
assert(PyLong_Check(watcher_id));
|
|
long watcher_id_l = PyLong_AsLong(watcher_id);
|
|
if (watcher_id_l < 0 || watcher_id_l >= NUM_CONTEXT_WATCHERS) {
|
|
PyErr_Format(PyExc_ValueError, "invalid watcher %ld", watcher_id_l);
|
|
return NULL;
|
|
}
|
|
if (context_switches[watcher_id_l] == NULL) {
|
|
return PyList_New(0);
|
|
}
|
|
return Py_NewRef(context_switches[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
|
|
defaults: object
|
|
/
|
|
[clinic start generated code]*/
|
|
|
|
static PyObject *
|
|
_testcapi_set_func_defaults_via_capi_impl(PyObject *module, PyObject *func,
|
|
PyObject *defaults)
|
|
/*[clinic end generated code: output=caf0cb39db31ac24 input=e04a8508ca9d42fc]*/
|
|
{
|
|
if (PyFunction_SetDefaults(func, defaults) < 0) {
|
|
return NULL;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
/*[clinic input]
|
|
_testcapi.set_func_kwdefaults_via_capi = _testcapi.set_func_defaults_via_capi
|
|
[clinic start generated code]*/
|
|
|
|
static PyObject *
|
|
_testcapi_set_func_kwdefaults_via_capi_impl(PyObject *module, PyObject *func,
|
|
PyObject *defaults)
|
|
/*[clinic end generated code: output=9ed3b08177025070 input=f3cd1ca3c18de8ce]*/
|
|
{
|
|
if (PyFunction_SetKwDefaults(func, defaults) < 0) {
|
|
return NULL;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyMethodDef test_methods[] = {
|
|
// Dict watchers.
|
|
{"add_dict_watcher", add_dict_watcher, METH_O, NULL},
|
|
{"clear_dict_watcher", clear_dict_watcher, METH_O, NULL},
|
|
_TESTCAPI_WATCH_DICT_METHODDEF
|
|
_TESTCAPI_UNWATCH_DICT_METHODDEF
|
|
{"get_dict_watcher_events",
|
|
(PyCFunction) get_dict_watcher_events, METH_NOARGS, NULL},
|
|
|
|
// Type watchers.
|
|
{"add_type_watcher", add_type_watcher, METH_O, NULL},
|
|
{"clear_type_watcher", clear_type_watcher, METH_O, NULL},
|
|
_TESTCAPI_WATCH_TYPE_METHODDEF
|
|
_TESTCAPI_UNWATCH_TYPE_METHODDEF
|
|
{"get_type_modified_events",
|
|
(PyCFunction) get_type_modified_events, METH_NOARGS, NULL},
|
|
|
|
// Code object watchers.
|
|
{"add_code_watcher", add_code_watcher, METH_O, NULL},
|
|
{"clear_code_watcher", clear_code_watcher, METH_O, NULL},
|
|
{"get_code_watcher_num_created_events",
|
|
get_code_watcher_num_created_events, METH_O, NULL},
|
|
{"get_code_watcher_num_destroyed_events",
|
|
get_code_watcher_num_destroyed_events, METH_O, NULL},
|
|
{"allocate_too_many_code_watchers",
|
|
(PyCFunction) allocate_too_many_code_watchers, METH_NOARGS, NULL},
|
|
|
|
// Function watchers.
|
|
{"add_func_watcher", add_func_watcher, METH_O, NULL},
|
|
{"clear_func_watcher", clear_func_watcher, METH_O, NULL},
|
|
_TESTCAPI_SET_FUNC_DEFAULTS_VIA_CAPI_METHODDEF
|
|
_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},
|
|
{"clear_context_stack", clear_context_stack, METH_NOARGS, NULL},
|
|
{"get_context_switches", get_context_switches, METH_O, NULL},
|
|
{"allocate_too_many_context_watchers",
|
|
(PyCFunction) allocate_too_many_context_watchers, METH_NOARGS, NULL},
|
|
{NULL},
|
|
};
|
|
|
|
int
|
|
_PyTestCapi_Init_Watchers(PyObject *mod)
|
|
{
|
|
if (PyModule_AddFunctions(mod, test_methods) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* Expose each event as an attribute on the module */
|
|
#define ADD_EVENT(event) \
|
|
if (add_func_event(mod, "PYFUNC_EVENT_" #event, \
|
|
PyFunction_EVENT_##event)) { \
|
|
return -1; \
|
|
}
|
|
PY_FOREACH_FUNC_EVENT(ADD_EVENT);
|
|
#undef ADD_EVENT
|
|
|
|
return 0;
|
|
}
|