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
|
||||
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)
|
||||
|
||||
Reset all information in a thread state object. The global interpreter lock
|
||||
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
|
||||
This function now calls the :c:member:`PyThreadState.on_delete` callback.
|
||||
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
|
||||
ignored.
|
||||
|
||||
.. audit-event:: _thread.start_new_thread function,args,kwargs start_new_thread
|
||||
|
||||
.. versionchanged:: 3.8
|
||||
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
|
||||
|
||||
|
|
|
@ -419,6 +419,48 @@ def test_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():
|
||||
import _wmi
|
||||
|
||||
|
|
|
@ -186,6 +186,31 @@ class AuditTest(unittest.TestCase):
|
|||
|
||||
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):
|
||||
import_helper.import_module("_wmi")
|
||||
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;
|
||||
}
|
||||
|
||||
if (PySys_Audit("_thread.start_new_thread", "OOO",
|
||||
func, args, kwargs ? kwargs : Py_None) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||
if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
|
@ -1160,7 +1165,10 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
|
|||
boot->tstate = _PyThreadState_Prealloc(boot->interp);
|
||||
if (boot->tstate == NULL) {
|
||||
PyMem_Free(boot);
|
||||
return PyErr_NoMemory();
|
||||
if (!PyErr_Occurred()) {
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
boot->runtime = runtime;
|
||||
boot->func = Py_NewRef(func);
|
||||
|
|
|
@ -873,14 +873,29 @@ PyThreadState *
|
|||
PyThreadState_New(PyInterpreterState *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;
|
||||
}
|
||||
|
||||
PyThreadState *
|
||||
_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.
|
||||
|
@ -1028,6 +1043,10 @@ _PyInterpreterState_ClearModules(PyInterpreterState *interp)
|
|||
void
|
||||
PyThreadState_Clear(PyThreadState *tstate)
|
||||
{
|
||||
if (PySys_Audit("cpython.PyThreadState_Clear", "K", tstate->id) < 0) {
|
||||
PyErr_WriteUnraisable(NULL);
|
||||
}
|
||||
|
||||
int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose;
|
||||
|
||||
if (verbose && tstate->cframe->current_frame != NULL) {
|
||||
|
@ -1545,16 +1564,16 @@ _PyGILState_Init(_PyRuntimeState *runtime)
|
|||
PyStatus
|
||||
_PyGILState_SetTstate(PyThreadState *tstate)
|
||||
{
|
||||
/* must init with valid states */
|
||||
assert(tstate != NULL);
|
||||
assert(tstate->interp != NULL);
|
||||
|
||||
if (!_Py_IsMainInterpreter(tstate->interp)) {
|
||||
/* Currently, PyGILState is shared by all interpreters. The main
|
||||
* interpreter is responsible to initialize it. */
|
||||
return _PyStatus_OK();
|
||||
}
|
||||
|
||||
/* must init with valid states */
|
||||
assert(tstate != NULL);
|
||||
assert(tstate->interp != NULL);
|
||||
|
||||
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
|
||||
|
||||
gilstate->autoInterpreterState = tstate->interp;
|
||||
|
|
Loading…
Reference in New Issue