mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
GH-91054: Add code object watchers API (GH-99859)
* Add API to allow extensions to set callback function on creation and destruction of PyCodeObject Co-authored-by: Ye11ow-Flash <janshah@cs.stonybrook.edu>
This commit is contained in:
parent
0563be23a5
commit
3c137dc613
11 changed files with 364 additions and 0 deletions
|
@ -115,3 +115,51 @@ bound into a function.
|
||||||
the free variables. On error, ``NULL`` is returned and an exception is raised.
|
the free variables. On error, ``NULL`` is returned and an exception is raised.
|
||||||
|
|
||||||
.. versionadded:: 3.11
|
.. versionadded:: 3.11
|
||||||
|
|
||||||
|
.. c:function:: int PyCode_AddWatcher(PyCode_WatchCallback callback)
|
||||||
|
|
||||||
|
Register *callback* as a code object watcher for the current interpreter.
|
||||||
|
Return an ID which may be passed to :c:func:`PyCode_ClearWatcher`.
|
||||||
|
In case of error (e.g. no more watcher IDs available),
|
||||||
|
return ``-1`` and set an exception.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
.. c:function:: int PyCode_ClearWatcher(int watcher_id)
|
||||||
|
|
||||||
|
Clear watcher identified by *watcher_id* previously returned from
|
||||||
|
:c:func:`PyCode_AddWatcher` for the current interpreter.
|
||||||
|
Return ``0`` on success, or ``-1`` and set an exception on error
|
||||||
|
(e.g. if the given *watcher_id* was never registered.)
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
.. c:type:: PyCodeEvent
|
||||||
|
|
||||||
|
Enumeration of possible code object watcher events:
|
||||||
|
- ``PY_CODE_EVENT_CREATE``
|
||||||
|
- ``PY_CODE_EVENT_DESTROY``
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
.. c:type:: int (*PyCode_WatchCallback)(PyCodeEvent event, PyCodeObject* co)
|
||||||
|
|
||||||
|
Type of a code object watcher callback function.
|
||||||
|
|
||||||
|
If *event* is ``PY_CODE_EVENT_CREATE``, then the callback is invoked
|
||||||
|
after `co` has been fully initialized. Otherwise, the callback is invoked
|
||||||
|
before the destruction of *co* takes place, so the prior state of *co*
|
||||||
|
can be inspected.
|
||||||
|
|
||||||
|
Users of this API should not rely on internal runtime implementation
|
||||||
|
details. Such details may include, but are not limited to, the exact
|
||||||
|
order and timing of creation and destruction of code objects. While
|
||||||
|
changes in these details may result in differences observable by watchers
|
||||||
|
(including whether a callback is invoked or not), it does not change
|
||||||
|
the semantics of the Python code being executed.
|
||||||
|
|
||||||
|
If the callback returns with an exception set, it must return ``-1``; this
|
||||||
|
exception will be printed as an unraisable exception using
|
||||||
|
:c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
|
@ -773,6 +773,10 @@ New Features
|
||||||
callbacks to receive notification on changes to a type.
|
callbacks to receive notification on changes to a type.
|
||||||
(Contributed by Carl Meyer in :gh:`91051`.)
|
(Contributed by Carl Meyer in :gh:`91051`.)
|
||||||
|
|
||||||
|
* Added :c:func:`PyCode_AddWatcher` and :c:func:`PyCode_ClearWatcher`
|
||||||
|
APIs to register callbacks to receive notification on creation and
|
||||||
|
destruction of code objects.
|
||||||
|
(Contributed by Itamar Ostricher in :gh:`91054`.)
|
||||||
|
|
||||||
* Add :c:func:`PyFrame_GetVar` and :c:func:`PyFrame_GetVarString` functions to
|
* Add :c:func:`PyFrame_GetVar` and :c:func:`PyFrame_GetVarString` functions to
|
||||||
get a frame variable by its name.
|
get a frame variable by its name.
|
||||||
|
|
|
@ -181,6 +181,41 @@ PyAPI_FUNC(int) PyCode_Addr2Line(PyCodeObject *, int);
|
||||||
|
|
||||||
PyAPI_FUNC(int) PyCode_Addr2Location(PyCodeObject *, int, int *, int *, int *, int *);
|
PyAPI_FUNC(int) PyCode_Addr2Location(PyCodeObject *, int, int *, int *, int *, int *);
|
||||||
|
|
||||||
|
typedef enum PyCodeEvent {
|
||||||
|
PY_CODE_EVENT_CREATE,
|
||||||
|
PY_CODE_EVENT_DESTROY
|
||||||
|
} PyCodeEvent;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A callback that is invoked for different events in a code object's lifecycle.
|
||||||
|
*
|
||||||
|
* The callback is invoked with a borrowed reference to co, after it is
|
||||||
|
* created and before it is destroyed.
|
||||||
|
*
|
||||||
|
* If the callback returns with an exception set, it must return -1. Otherwise
|
||||||
|
* it should return 0.
|
||||||
|
*/
|
||||||
|
typedef int (*PyCode_WatchCallback)(
|
||||||
|
PyCodeEvent event,
|
||||||
|
PyCodeObject* co);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Register a per-interpreter callback that will be invoked for code object
|
||||||
|
* lifecycle events.
|
||||||
|
*
|
||||||
|
* Returns a handle that may be passed to PyCode_ClearWatcher on success,
|
||||||
|
* or -1 and sets an error if no more handles are available.
|
||||||
|
*/
|
||||||
|
PyAPI_FUNC(int) PyCode_AddWatcher(PyCode_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) PyCode_ClearWatcher(int watcher_id);
|
||||||
|
|
||||||
/* for internal use only */
|
/* for internal use only */
|
||||||
struct _opaque {
|
struct _opaque {
|
||||||
int computed_line;
|
int computed_line;
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define CODE_MAX_WATCHERS 8
|
||||||
|
|
||||||
/* PEP 659
|
/* PEP 659
|
||||||
* Specialization and quickening structs and helper functions
|
* Specialization and quickening structs and helper functions
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -191,6 +191,9 @@ struct _is {
|
||||||
|
|
||||||
PyObject *audit_hooks;
|
PyObject *audit_hooks;
|
||||||
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
|
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
|
||||||
|
PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
|
||||||
|
// One bit is set for each non-NULL entry in code_watchers
|
||||||
|
uint8_t active_code_watchers;
|
||||||
|
|
||||||
struct _Py_unicode_state unicode;
|
struct _Py_unicode_state unicode;
|
||||||
struct _Py_float_state float_state;
|
struct _Py_float_state float_state;
|
||||||
|
|
|
@ -336,6 +336,74 @@ class TestTypeWatchers(unittest.TestCase):
|
||||||
self.add_watcher()
|
self.add_watcher()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCodeObjectWatchers(unittest.TestCase):
|
||||||
|
@contextmanager
|
||||||
|
def code_watcher(self, which_watcher):
|
||||||
|
wid = _testcapi.add_code_watcher(which_watcher)
|
||||||
|
try:
|
||||||
|
yield wid
|
||||||
|
finally:
|
||||||
|
_testcapi.clear_code_watcher(wid)
|
||||||
|
|
||||||
|
def assert_event_counts(self, exp_created_0, exp_destroyed_0,
|
||||||
|
exp_created_1, exp_destroyed_1):
|
||||||
|
self.assertEqual(
|
||||||
|
exp_created_0, _testcapi.get_code_watcher_num_created_events(0))
|
||||||
|
self.assertEqual(
|
||||||
|
exp_destroyed_0, _testcapi.get_code_watcher_num_destroyed_events(0))
|
||||||
|
self.assertEqual(
|
||||||
|
exp_created_1, _testcapi.get_code_watcher_num_created_events(1))
|
||||||
|
self.assertEqual(
|
||||||
|
exp_destroyed_1, _testcapi.get_code_watcher_num_destroyed_events(1))
|
||||||
|
|
||||||
|
def test_code_object_events_dispatched(self):
|
||||||
|
# verify that all counts are zero before any watchers are registered
|
||||||
|
self.assert_event_counts(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# verify that all counts remain zero when a code object is
|
||||||
|
# created and destroyed with no watchers registered
|
||||||
|
co1 = _testcapi.code_newempty("test_watchers", "dummy1", 0)
|
||||||
|
self.assert_event_counts(0, 0, 0, 0)
|
||||||
|
del co1
|
||||||
|
self.assert_event_counts(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# verify counts are as expected when first watcher is registered
|
||||||
|
with self.code_watcher(0):
|
||||||
|
self.assert_event_counts(0, 0, 0, 0)
|
||||||
|
co2 = _testcapi.code_newempty("test_watchers", "dummy2", 0)
|
||||||
|
self.assert_event_counts(1, 0, 0, 0)
|
||||||
|
del co2
|
||||||
|
self.assert_event_counts(1, 1, 0, 0)
|
||||||
|
|
||||||
|
# again with second watcher registered
|
||||||
|
with self.code_watcher(1):
|
||||||
|
self.assert_event_counts(1, 1, 0, 0)
|
||||||
|
co3 = _testcapi.code_newempty("test_watchers", "dummy3", 0)
|
||||||
|
self.assert_event_counts(2, 1, 1, 0)
|
||||||
|
del co3
|
||||||
|
self.assert_event_counts(2, 2, 1, 1)
|
||||||
|
|
||||||
|
# verify counts remain as they were after both watchers are cleared
|
||||||
|
co4 = _testcapi.code_newempty("test_watchers", "dummy4", 0)
|
||||||
|
self.assert_event_counts(2, 2, 1, 1)
|
||||||
|
del co4
|
||||||
|
self.assert_event_counts(2, 2, 1, 1)
|
||||||
|
|
||||||
|
def test_clear_out_of_range_watcher_id(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, r"Invalid code watcher ID -1"):
|
||||||
|
_testcapi.clear_code_watcher(-1)
|
||||||
|
with self.assertRaisesRegex(ValueError, r"Invalid code watcher ID 8"):
|
||||||
|
_testcapi.clear_code_watcher(8) # CODE_MAX_WATCHERS = 8
|
||||||
|
|
||||||
|
def test_clear_unassigned_watcher_id(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, r"No code watcher set for ID 1"):
|
||||||
|
_testcapi.clear_code_watcher(1)
|
||||||
|
|
||||||
|
def test_allocate_too_many_watchers(self):
|
||||||
|
with self.assertRaisesRegex(RuntimeError, r"no more code watcher IDs available"):
|
||||||
|
_testcapi.allocate_too_many_code_watchers()
|
||||||
|
|
||||||
|
|
||||||
class TestFuncWatchers(unittest.TestCase):
|
class TestFuncWatchers(unittest.TestCase):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def add_watcher(self, func):
|
def add_watcher(self, func):
|
||||||
|
|
|
@ -1320,6 +1320,7 @@ Michele Orrù
|
||||||
Tomáš Orsava
|
Tomáš Orsava
|
||||||
Oleg Oshmyan
|
Oleg Oshmyan
|
||||||
Denis Osipov
|
Denis Osipov
|
||||||
|
Itamar Ostricher
|
||||||
Denis S. Otkidach
|
Denis S. Otkidach
|
||||||
Peter Otten
|
Peter Otten
|
||||||
Michael Otteneder
|
Michael Otteneder
|
||||||
|
@ -1627,6 +1628,7 @@ Silas Sewell
|
||||||
Ian Seyer
|
Ian Seyer
|
||||||
Dmitry Shachnev
|
Dmitry Shachnev
|
||||||
Anish Shah
|
Anish Shah
|
||||||
|
Jaineel Shah
|
||||||
Daniel Shahaf
|
Daniel Shahaf
|
||||||
Hui Shang
|
Hui Shang
|
||||||
Geoff Shannon
|
Geoff Shannon
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Add :c:func:`PyCode_AddWatcher` and :c:func:`PyCode_ClearWatcher` APIs to
|
||||||
|
register callbacks to receive notification on creation and destruction of
|
||||||
|
code objects.
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#define Py_BUILD_CORE
|
#define Py_BUILD_CORE
|
||||||
#include "pycore_function.h" // FUNC_MAX_WATCHERS
|
#include "pycore_function.h" // FUNC_MAX_WATCHERS
|
||||||
|
#include "pycore_code.h" // CODE_MAX_WATCHERS
|
||||||
|
|
||||||
// Test dict watching
|
// Test dict watching
|
||||||
static PyObject *g_dict_watch_events;
|
static PyObject *g_dict_watch_events;
|
||||||
|
@ -277,6 +278,126 @@ unwatch_type(PyObject *self, PyObject *args)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Test code object watching
|
||||||
|
|
||||||
|
#define NUM_CODE_WATCHERS 2
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
else if (which_l == 1) {
|
||||||
|
watcher_id = PyCode_AddWatcher(second_code_object_callback);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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 *type, *value, *traceback;
|
||||||
|
PyErr_Fetch(&type, &value, &traceback);
|
||||||
|
for (int i = 0; i < num_watchers; i++) {
|
||||||
|
if (PyCode_ClearWatcher(watcher_ids[i]) < 0) {
|
||||||
|
PyErr_WriteUnraisable(Py_None);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type) {
|
||||||
|
PyErr_Restore(type, value, traceback);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// Test function watchers
|
// Test function watchers
|
||||||
|
|
||||||
#define NUM_FUNC_WATCHERS 2
|
#define NUM_FUNC_WATCHERS 2
|
||||||
|
@ -509,6 +630,16 @@ static PyMethodDef test_methods[] = {
|
||||||
{"unwatch_type", unwatch_type, METH_VARARGS, NULL},
|
{"unwatch_type", unwatch_type, METH_VARARGS, NULL},
|
||||||
{"get_type_modified_events", get_type_modified_events, METH_NOARGS, NULL},
|
{"get_type_modified_events", 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.
|
// Function watchers.
|
||||||
{"add_func_watcher", add_func_watcher, METH_O, NULL},
|
{"add_func_watcher", add_func_watcher, METH_O, NULL},
|
||||||
{"clear_func_watcher", clear_func_watcher, METH_O, NULL},
|
{"clear_func_watcher", clear_func_watcher, METH_O, NULL},
|
||||||
|
|
|
@ -12,6 +12,66 @@
|
||||||
#include "clinic/codeobject.c.h"
|
#include "clinic/codeobject.c.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
notify_code_watchers(PyCodeEvent event, PyCodeObject *co)
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
if (interp->active_code_watchers) {
|
||||||
|
assert(interp->_initialized);
|
||||||
|
for (int i = 0; i < CODE_MAX_WATCHERS; i++) {
|
||||||
|
PyCode_WatchCallback cb = interp->code_watchers[i];
|
||||||
|
if ((cb != NULL) && (cb(event, co) < 0)) {
|
||||||
|
PyErr_WriteUnraisable((PyObject *) co);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PyCode_AddWatcher(PyCode_WatchCallback callback)
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
assert(interp->_initialized);
|
||||||
|
|
||||||
|
for (int i = 0; i < CODE_MAX_WATCHERS; i++) {
|
||||||
|
if (!interp->code_watchers[i]) {
|
||||||
|
interp->code_watchers[i] = callback;
|
||||||
|
interp->active_code_watchers |= (1 << i);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "no more code watcher IDs available");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
validate_watcher_id(PyInterpreterState *interp, int watcher_id)
|
||||||
|
{
|
||||||
|
if (watcher_id < 0 || watcher_id >= CODE_MAX_WATCHERS) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "Invalid code watcher ID %d", watcher_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!interp->code_watchers[watcher_id]) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "No code watcher set for ID %d", watcher_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PyCode_ClearWatcher(int watcher_id)
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
assert(interp->_initialized);
|
||||||
|
if (validate_watcher_id(interp, watcher_id) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
interp->code_watchers[watcher_id] = NULL;
|
||||||
|
interp->active_code_watchers &= ~(1 << watcher_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/******************
|
/******************
|
||||||
* generic helpers
|
* generic helpers
|
||||||
******************/
|
******************/
|
||||||
|
@ -355,6 +415,7 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
|
||||||
}
|
}
|
||||||
co->_co_firsttraceable = entry_point;
|
co->_co_firsttraceable = entry_point;
|
||||||
_PyCode_Quicken(co);
|
_PyCode_Quicken(co);
|
||||||
|
notify_code_watchers(PY_CODE_EVENT_CREATE, co);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -1615,6 +1676,8 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
|
||||||
static void
|
static void
|
||||||
code_dealloc(PyCodeObject *co)
|
code_dealloc(PyCodeObject *co)
|
||||||
{
|
{
|
||||||
|
notify_code_watchers(PY_CODE_EVENT_DESTROY, co);
|
||||||
|
|
||||||
if (co->co_extra != NULL) {
|
if (co->co_extra != NULL) {
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
_PyCodeObjectExtra *co_extra = co->co_extra;
|
_PyCodeObjectExtra *co_extra = co->co_extra;
|
||||||
|
|
|
@ -466,6 +466,11 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
|
||||||
}
|
}
|
||||||
interp->active_func_watchers = 0;
|
interp->active_func_watchers = 0;
|
||||||
|
|
||||||
|
for (int i=0; i < CODE_MAX_WATCHERS; i++) {
|
||||||
|
interp->code_watchers[i] = NULL;
|
||||||
|
}
|
||||||
|
interp->active_code_watchers = 0;
|
||||||
|
|
||||||
// XXX Once we have one allocator per interpreter (i.e.
|
// XXX Once we have one allocator per interpreter (i.e.
|
||||||
// per-interpreter GC) we must ensure that all of the interpreter's
|
// per-interpreter GC) we must ensure that all of the interpreter's
|
||||||
// objects have been cleaned up at the point.
|
// objects have been cleaned up at the point.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue