mirror of https://github.com/python/cpython
gh-99377: Add audit events for thread creation and clear (GH-99378)
This commit is contained in:
parent
01fa907aa8
commit
19c1462e8d
|
@ -1239,12 +1239,25 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
|
||||||
The global interpreter lock need not be held, but may be held if it is
|
The global interpreter lock need not be held, but may be held if it is
|
||||||
necessary to serialize calls to this function.
|
necessary to serialize calls to this function.
|
||||||
|
|
||||||
|
.. audit-event:: cpython.PyThreadState_New id c.PyThreadState_New
|
||||||
|
|
||||||
|
Raise an auditing event ``cpython.PyThreadState_New`` with Python's thread
|
||||||
|
id as the argument. The event will be raised from the thread creating the new
|
||||||
|
``PyThreadState``, which may not be the new thread.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: void PyThreadState_Clear(PyThreadState *tstate)
|
.. c:function:: void PyThreadState_Clear(PyThreadState *tstate)
|
||||||
|
|
||||||
Reset all information in a thread state object. The global interpreter lock
|
Reset all information in a thread state object. The global interpreter lock
|
||||||
must be held.
|
must be held.
|
||||||
|
|
||||||
|
.. audit-event:: cpython.PyThreadState_Clear id c.PyThreadState_Clear
|
||||||
|
|
||||||
|
Raise an auditing event ``cpython.PyThreadState_Clear`` with Python's
|
||||||
|
thread id as the argument. The event may be raised from a different thread
|
||||||
|
than the one being cleared. Exceptions raised from a hook will be treated
|
||||||
|
as unraisable and will not abort the operation.
|
||||||
|
|
||||||
.. versionchanged:: 3.9
|
.. versionchanged:: 3.9
|
||||||
This function now calls the :c:member:`PyThreadState.on_delete` callback.
|
This function now calls the :c:member:`PyThreadState.on_delete` callback.
|
||||||
Previously, that happened in :c:func:`PyThreadState_Delete`.
|
Previously, that happened in :c:func:`PyThreadState_Delete`.
|
||||||
|
|
|
@ -57,6 +57,8 @@ This module defines the following constants and functions:
|
||||||
When the function raises a :exc:`SystemExit` exception, it is silently
|
When the function raises a :exc:`SystemExit` exception, it is silently
|
||||||
ignored.
|
ignored.
|
||||||
|
|
||||||
|
.. audit-event:: _thread.start_new_thread function,args,kwargs start_new_thread
|
||||||
|
|
||||||
.. versionchanged:: 3.8
|
.. versionchanged:: 3.8
|
||||||
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
|
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
|
||||||
|
|
||||||
|
|
|
@ -419,6 +419,48 @@ def test_sys_getframe():
|
||||||
sys._getframe()
|
sys._getframe()
|
||||||
|
|
||||||
|
|
||||||
|
def test_threading():
|
||||||
|
import _thread
|
||||||
|
|
||||||
|
def hook(event, args):
|
||||||
|
if event.startswith(("_thread.", "cpython.PyThreadState", "test.")):
|
||||||
|
print(event, args)
|
||||||
|
|
||||||
|
sys.addaudithook(hook)
|
||||||
|
|
||||||
|
lock = _thread.allocate_lock()
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
class test_func:
|
||||||
|
def __repr__(self): return "<test_func>"
|
||||||
|
def __call__(self):
|
||||||
|
sys.audit("test.test_func")
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
i = _thread.start_new_thread(test_func(), ())
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
|
||||||
|
def test_threading_abort():
|
||||||
|
# Ensures that aborting PyThreadState_New raises the correct exception
|
||||||
|
import _thread
|
||||||
|
|
||||||
|
class ThreadNewAbortError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def hook(event, args):
|
||||||
|
if event == "cpython.PyThreadState_New":
|
||||||
|
raise ThreadNewAbortError()
|
||||||
|
|
||||||
|
sys.addaudithook(hook)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_thread.start_new_thread(lambda: None, ())
|
||||||
|
except ThreadNewAbortError:
|
||||||
|
# Other exceptions are raised and the test will fail
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_wmi_exec_query():
|
def test_wmi_exec_query():
|
||||||
import _wmi
|
import _wmi
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,31 @@ class AuditTest(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_threading(self):
|
||||||
|
returncode, events, stderr = self.run_python("test_threading")
|
||||||
|
if returncode:
|
||||||
|
self.fail(stderr)
|
||||||
|
|
||||||
|
if support.verbose:
|
||||||
|
print(*events, sep='\n')
|
||||||
|
actual = [(ev[0], ev[2]) for ev in events]
|
||||||
|
expected = [
|
||||||
|
("_thread.start_new_thread", "(<test_func>, (), None)"),
|
||||||
|
("cpython.PyThreadState_New", "(2,)"),
|
||||||
|
("test.test_func", "()"),
|
||||||
|
("cpython.PyThreadState_Clear", "(2,)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_threading_abort(self):
|
||||||
|
# Ensures that aborting PyThreadState_New raises the correct exception
|
||||||
|
returncode, events, stderr = self.run_python("test_threading_abort")
|
||||||
|
if returncode:
|
||||||
|
self.fail(stderr)
|
||||||
|
|
||||||
|
|
||||||
def test_wmi_exec_query(self):
|
def test_wmi_exec_query(self):
|
||||||
import_helper.import_module("_wmi")
|
import_helper.import_module("_wmi")
|
||||||
returncode, events, stderr = self.run_python("test_wmi_exec_query")
|
returncode, events, stderr = self.run_python("test_wmi_exec_query")
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add audit events for thread creation and clear operations.
|
|
@ -1145,6 +1145,11 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PySys_Audit("_thread.start_new_thread", "OOO",
|
||||||
|
func, args, kwargs ? kwargs : Py_None) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
|
if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
|
||||||
PyErr_SetString(PyExc_RuntimeError,
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
@ -1160,7 +1165,10 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
|
||||||
boot->tstate = _PyThreadState_Prealloc(boot->interp);
|
boot->tstate = _PyThreadState_Prealloc(boot->interp);
|
||||||
if (boot->tstate == NULL) {
|
if (boot->tstate == NULL) {
|
||||||
PyMem_Free(boot);
|
PyMem_Free(boot);
|
||||||
return PyErr_NoMemory();
|
if (!PyErr_Occurred()) {
|
||||||
|
return PyErr_NoMemory();
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
boot->runtime = runtime;
|
boot->runtime = runtime;
|
||||||
boot->func = Py_NewRef(func);
|
boot->func = Py_NewRef(func);
|
||||||
|
|
|
@ -873,14 +873,29 @@ PyThreadState *
|
||||||
PyThreadState_New(PyInterpreterState *interp)
|
PyThreadState_New(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = new_threadstate(interp);
|
PyThreadState *tstate = new_threadstate(interp);
|
||||||
_PyThreadState_SetCurrent(tstate);
|
if (tstate) {
|
||||||
|
_PyThreadState_SetCurrent(tstate);
|
||||||
|
if (PySys_Audit("cpython.PyThreadState_New", "K", tstate->id) < 0) {
|
||||||
|
PyThreadState_Clear(tstate);
|
||||||
|
_PyThreadState_DeleteCurrent(tstate);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
return tstate;
|
return tstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyThreadState *
|
PyThreadState *
|
||||||
_PyThreadState_Prealloc(PyInterpreterState *interp)
|
_PyThreadState_Prealloc(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
return new_threadstate(interp);
|
PyThreadState *tstate = new_threadstate(interp);
|
||||||
|
if (tstate) {
|
||||||
|
if (PySys_Audit("cpython.PyThreadState_New", "K", tstate->id) < 0) {
|
||||||
|
PyThreadState_Clear(tstate);
|
||||||
|
_PyThreadState_Delete(tstate, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We keep this around for (accidental) stable ABI compatibility.
|
// We keep this around for (accidental) stable ABI compatibility.
|
||||||
|
@ -1028,6 +1043,10 @@ _PyInterpreterState_ClearModules(PyInterpreterState *interp)
|
||||||
void
|
void
|
||||||
PyThreadState_Clear(PyThreadState *tstate)
|
PyThreadState_Clear(PyThreadState *tstate)
|
||||||
{
|
{
|
||||||
|
if (PySys_Audit("cpython.PyThreadState_Clear", "K", tstate->id) < 0) {
|
||||||
|
PyErr_WriteUnraisable(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose;
|
int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose;
|
||||||
|
|
||||||
if (verbose && tstate->cframe->current_frame != NULL) {
|
if (verbose && tstate->cframe->current_frame != NULL) {
|
||||||
|
@ -1545,16 +1564,16 @@ _PyGILState_Init(_PyRuntimeState *runtime)
|
||||||
PyStatus
|
PyStatus
|
||||||
_PyGILState_SetTstate(PyThreadState *tstate)
|
_PyGILState_SetTstate(PyThreadState *tstate)
|
||||||
{
|
{
|
||||||
|
/* must init with valid states */
|
||||||
|
assert(tstate != NULL);
|
||||||
|
assert(tstate->interp != NULL);
|
||||||
|
|
||||||
if (!_Py_IsMainInterpreter(tstate->interp)) {
|
if (!_Py_IsMainInterpreter(tstate->interp)) {
|
||||||
/* Currently, PyGILState is shared by all interpreters. The main
|
/* Currently, PyGILState is shared by all interpreters. The main
|
||||||
* interpreter is responsible to initialize it. */
|
* interpreter is responsible to initialize it. */
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* must init with valid states */
|
|
||||||
assert(tstate != NULL);
|
|
||||||
assert(tstate->interp != NULL);
|
|
||||||
|
|
||||||
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
|
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
|
||||||
|
|
||||||
gilstate->autoInterpreterState = tstate->interp;
|
gilstate->autoInterpreterState = tstate->interp;
|
||||||
|
|
Loading…
Reference in New Issue