mirror of https://github.com/python/cpython
gh-116750: Add clear_tool_id function to unregister events and callbacks (#124568)
This commit is contained in:
parent
b482538523
commit
5e0abb4788
|
@ -50,16 +50,14 @@ Registering and using tools
|
||||||
*tool_id* must be in the range 0 to 5 inclusive.
|
*tool_id* must be in the range 0 to 5 inclusive.
|
||||||
Raises a :exc:`ValueError` if *tool_id* is in use.
|
Raises a :exc:`ValueError` if *tool_id* is in use.
|
||||||
|
|
||||||
|
.. function:: clear_tool_id(tool_id: int, /) -> None
|
||||||
|
|
||||||
|
Unregister all events and callback functions associated with *tool_id*.
|
||||||
|
|
||||||
.. function:: free_tool_id(tool_id: int, /) -> None
|
.. function:: free_tool_id(tool_id: int, /) -> None
|
||||||
|
|
||||||
Should be called once a tool no longer requires *tool_id*.
|
Should be called once a tool no longer requires *tool_id*.
|
||||||
|
Will call :func:`clear_tool_id` before releasing *tool_id*.
|
||||||
.. note::
|
|
||||||
|
|
||||||
:func:`free_tool_id` will not disable global or local events associated
|
|
||||||
with *tool_id*, nor will it unregister any callback functions. This
|
|
||||||
function is only intended to be used to notify the VM that the
|
|
||||||
particular *tool_id* is no longer in use.
|
|
||||||
|
|
||||||
.. function:: get_tool(tool_id: int, /) -> str | None
|
.. function:: get_tool(tool_id: int, /) -> str | None
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Total tool ids available */
|
||||||
|
#define _PY_MONITORING_TOOL_IDS 8
|
||||||
/* Count of all local monitoring events */
|
/* Count of all local monitoring events */
|
||||||
#define _PY_MONITORING_LOCAL_EVENTS 10
|
#define _PY_MONITORING_LOCAL_EVENTS 10
|
||||||
/* Count of all "real" monitoring events (not derived from other events) */
|
/* Count of all "real" monitoring events (not derived from other events) */
|
||||||
|
@ -57,6 +59,8 @@ typedef struct {
|
||||||
_Py_LocalMonitors active_monitors;
|
_Py_LocalMonitors active_monitors;
|
||||||
/* The tools that are to be notified for events for the matching code unit */
|
/* The tools that are to be notified for events for the matching code unit */
|
||||||
uint8_t *tools;
|
uint8_t *tools;
|
||||||
|
/* The version of tools when they instrument the code */
|
||||||
|
uintptr_t tool_versions[_PY_MONITORING_TOOL_IDS];
|
||||||
/* Information to support line events */
|
/* Information to support line events */
|
||||||
_PyCoLineInstrumentationData *lines;
|
_PyCoLineInstrumentationData *lines;
|
||||||
/* The tools that are to be notified for line events for the matching code unit */
|
/* The tools that are to be notified for line events for the matching code unit */
|
||||||
|
|
|
@ -272,6 +272,7 @@ struct _is {
|
||||||
Py_ssize_t sys_tracing_threads; /* Count of threads with c_tracefunc set */
|
Py_ssize_t sys_tracing_threads; /* Count of threads with c_tracefunc set */
|
||||||
PyObject *monitoring_callables[PY_MONITORING_TOOL_IDS][_PY_MONITORING_EVENTS];
|
PyObject *monitoring_callables[PY_MONITORING_TOOL_IDS][_PY_MONITORING_EVENTS];
|
||||||
PyObject *monitoring_tool_names[PY_MONITORING_TOOL_IDS];
|
PyObject *monitoring_tool_names[PY_MONITORING_TOOL_IDS];
|
||||||
|
uintptr_t monitoring_tool_versions[PY_MONITORING_TOOL_IDS];
|
||||||
|
|
||||||
struct _Py_interp_cached_objects cached_objects;
|
struct _Py_interp_cached_objects cached_objects;
|
||||||
struct _Py_interp_static_objects static_objects;
|
struct _Py_interp_static_objects static_objects;
|
||||||
|
|
|
@ -46,10 +46,14 @@ def nth_line(func, offset):
|
||||||
|
|
||||||
class MonitoringBasicTest(unittest.TestCase):
|
class MonitoringBasicTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
sys.monitoring.free_tool_id(TEST_TOOL)
|
||||||
|
|
||||||
def test_has_objects(self):
|
def test_has_objects(self):
|
||||||
m = sys.monitoring
|
m = sys.monitoring
|
||||||
m.events
|
m.events
|
||||||
m.use_tool_id
|
m.use_tool_id
|
||||||
|
m.clear_tool_id
|
||||||
m.free_tool_id
|
m.free_tool_id
|
||||||
m.get_tool
|
m.get_tool
|
||||||
m.get_events
|
m.get_events
|
||||||
|
@ -77,6 +81,43 @@ class MonitoringBasicTest(unittest.TestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
sys.monitoring.set_events(TEST_TOOL, sys.monitoring.events.CALL)
|
sys.monitoring.set_events(TEST_TOOL, sys.monitoring.events.CALL)
|
||||||
|
|
||||||
|
def test_clear(self):
|
||||||
|
events = []
|
||||||
|
sys.monitoring.use_tool_id(TEST_TOOL, "MonitoringTest.Tool")
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_START, lambda *args: events.append(args))
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.LINE, lambda *args: events.append(args))
|
||||||
|
def f():
|
||||||
|
a = 1
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, f.__code__, E.LINE)
|
||||||
|
sys.monitoring.set_events(TEST_TOOL, E.PY_START)
|
||||||
|
|
||||||
|
f()
|
||||||
|
sys.monitoring.clear_tool_id(TEST_TOOL)
|
||||||
|
f()
|
||||||
|
|
||||||
|
# the first f() should trigger a PY_START and a LINE event
|
||||||
|
# the second f() after clear_tool_id should not trigger any event
|
||||||
|
# the callback function should be cleared as well
|
||||||
|
self.assertEqual(len(events), 2)
|
||||||
|
callback = sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
|
||||||
|
self.assertIs(callback, None)
|
||||||
|
|
||||||
|
sys.monitoring.free_tool_id(TEST_TOOL)
|
||||||
|
|
||||||
|
events = []
|
||||||
|
sys.monitoring.use_tool_id(TEST_TOOL, "MonitoringTest.Tool")
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.LINE, lambda *args: events.append(args))
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, f.__code__, E.LINE)
|
||||||
|
f()
|
||||||
|
sys.monitoring.free_tool_id(TEST_TOOL)
|
||||||
|
sys.monitoring.use_tool_id(TEST_TOOL, "MonitoringTest.Tool")
|
||||||
|
f()
|
||||||
|
# the first f() should trigger a LINE event, and even if we use the
|
||||||
|
# tool id immediately after freeing it, the second f() should not
|
||||||
|
# trigger any event
|
||||||
|
self.assertEqual(len(events), 1)
|
||||||
|
sys.monitoring.free_tool_id(TEST_TOOL)
|
||||||
|
|
||||||
|
|
||||||
class MonitoringTestBase:
|
class MonitoringTestBase:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Provide :func:`sys.monitoring.clear_tool_id` to unregister all events and callbacks set by the tool.
|
|
@ -36,6 +36,33 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(monitoring_clear_tool_id__doc__,
|
||||||
|
"clear_tool_id($module, tool_id, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n");
|
||||||
|
|
||||||
|
#define MONITORING_CLEAR_TOOL_ID_METHODDEF \
|
||||||
|
{"clear_tool_id", (PyCFunction)monitoring_clear_tool_id, METH_O, monitoring_clear_tool_id__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
monitoring_clear_tool_id_impl(PyObject *module, int tool_id);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
monitoring_clear_tool_id(PyObject *module, PyObject *arg)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
int tool_id;
|
||||||
|
|
||||||
|
tool_id = PyLong_AsInt(arg);
|
||||||
|
if (tool_id == -1 && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = monitoring_clear_tool_id_impl(module, tool_id);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(monitoring_free_tool_id__doc__,
|
PyDoc_STRVAR(monitoring_free_tool_id__doc__,
|
||||||
"free_tool_id($module, tool_id, /)\n"
|
"free_tool_id($module, tool_id, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -304,4 +331,4 @@ monitoring__all_events(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
return monitoring__all_events_impl(module);
|
return monitoring__all_events_impl(module);
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=14ffc0884a6de50a input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=8f81876c6aba9be8 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -1660,6 +1660,16 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
|
||||||
if (allocate_instrumentation_data(code)) {
|
if (allocate_instrumentation_data(code)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
// If the local monitors are out of date, clear them up
|
||||||
|
_Py_LocalMonitors *local_monitors = &code->_co_monitoring->local_monitors;
|
||||||
|
for (int i = 0; i < PY_MONITORING_TOOL_IDS; i++) {
|
||||||
|
if (code->_co_monitoring->tool_versions[i] != interp->monitoring_tool_versions[i]) {
|
||||||
|
for (int j = 0; j < _PY_MONITORING_LOCAL_EVENTS; j++) {
|
||||||
|
local_monitors->tools[j] &= ~(1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_Py_LocalMonitors all_events = local_union(
|
_Py_LocalMonitors all_events = local_union(
|
||||||
interp->monitors,
|
interp->monitors,
|
||||||
code->_co_monitoring->local_monitors);
|
code->_co_monitoring->local_monitors);
|
||||||
|
@ -2004,6 +2014,8 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code->_co_monitoring->tool_versions[tool_id] = interp->monitoring_tool_versions[tool_id];
|
||||||
|
|
||||||
_Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
|
_Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
|
||||||
uint32_t existing_events = get_local_events(local, tool_id);
|
uint32_t existing_events = get_local_events(local, tool_id);
|
||||||
if (existing_events == events) {
|
if (existing_events == events) {
|
||||||
|
@ -2036,6 +2048,43 @@ _PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _PyMonitoring_ClearToolId(int tool_id)
|
||||||
|
{
|
||||||
|
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
|
||||||
|
for (int i = 0; i < _PY_MONITORING_EVENTS; i++) {
|
||||||
|
PyObject *func = _PyMonitoring_RegisterCallback(tool_id, i, NULL);
|
||||||
|
if (func != NULL) {
|
||||||
|
Py_DECREF(func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_PyMonitoring_SetEvents(tool_id, 0) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_PyEval_StopTheWorld(interp);
|
||||||
|
uint32_t version = global_version(interp) + MONITORING_VERSION_INCREMENT;
|
||||||
|
if (version == 0) {
|
||||||
|
PyErr_Format(PyExc_OverflowError, "events set too many times");
|
||||||
|
_PyEval_StartTheWorld(interp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitoring_tool_versions[tool_id] is set to latest global version here to
|
||||||
|
// 1. invalidate local events on all existing code objects
|
||||||
|
// 2. be ready for the next call to set local events
|
||||||
|
interp->monitoring_tool_versions[tool_id] = version;
|
||||||
|
|
||||||
|
// Set the new global version so all the code objects can refresh the
|
||||||
|
// instrumentation.
|
||||||
|
set_global_version(_PyThreadState_GET(), version);
|
||||||
|
int res = instrument_all_executing_code_objects(interp);
|
||||||
|
_PyEval_StartTheWorld(interp);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
module monitoring
|
module monitoring
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
@ -2083,6 +2132,33 @@ monitoring_use_tool_id_impl(PyObject *module, int tool_id, PyObject *name)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
monitoring.clear_tool_id
|
||||||
|
|
||||||
|
tool_id: int
|
||||||
|
/
|
||||||
|
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
monitoring_clear_tool_id_impl(PyObject *module, int tool_id)
|
||||||
|
/*[clinic end generated code: output=04defc23470b1be7 input=af643d6648a66163]*/
|
||||||
|
{
|
||||||
|
if (check_valid_tool(tool_id)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
|
||||||
|
if (interp->monitoring_tool_names[tool_id] != NULL) {
|
||||||
|
if (_PyMonitoring_ClearToolId(tool_id) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
monitoring.free_tool_id
|
monitoring.free_tool_id
|
||||||
|
|
||||||
|
@ -2099,6 +2175,13 @@ monitoring_free_tool_id_impl(PyObject *module, int tool_id)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
|
||||||
|
if (interp->monitoring_tool_names[tool_id] != NULL) {
|
||||||
|
if (_PyMonitoring_ClearToolId(tool_id) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Py_CLEAR(interp->monitoring_tool_names[tool_id]);
|
Py_CLEAR(interp->monitoring_tool_names[tool_id]);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
@ -2376,6 +2459,7 @@ monitoring__all_events_impl(PyObject *module)
|
||||||
|
|
||||||
static PyMethodDef methods[] = {
|
static PyMethodDef methods[] = {
|
||||||
MONITORING_USE_TOOL_ID_METHODDEF
|
MONITORING_USE_TOOL_ID_METHODDEF
|
||||||
|
MONITORING_CLEAR_TOOL_ID_METHODDEF
|
||||||
MONITORING_FREE_TOOL_ID_METHODDEF
|
MONITORING_FREE_TOOL_ID_METHODDEF
|
||||||
MONITORING_GET_TOOL_METHODDEF
|
MONITORING_GET_TOOL_METHODDEF
|
||||||
MONITORING_REGISTER_CALLBACK_METHODDEF
|
MONITORING_REGISTER_CALLBACK_METHODDEF
|
||||||
|
|
|
@ -654,6 +654,7 @@ init_interpreter(PyInterpreterState *interp,
|
||||||
interp->monitoring_callables[t][e] = NULL;
|
interp->monitoring_callables[t][e] = NULL;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
interp->monitoring_tool_versions[t] = 0;
|
||||||
}
|
}
|
||||||
interp->sys_profile_initialized = false;
|
interp->sys_profile_initialized = false;
|
||||||
interp->sys_trace_initialized = false;
|
interp->sys_trace_initialized = false;
|
||||||
|
|
Loading…
Reference in New Issue