Skip to content

Commit a9c619b

Browse files
committed
GIL monitoring for Python 3.10.12
1 parent b4e48a4 commit a9c619b

File tree

5 files changed

+153
-13
lines changed

5 files changed

+153
-13
lines changed

Include/cpython/ceval.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *);
66
PyAPI_DATA(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg);
7+
PyAPI_FUNC(void) PyEval_SetProfileAllThreads(Py_tracefunc func, PyObject *arg);
78
PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *);
89
PyAPI_FUNC(int) _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg);
910
PyAPI_FUNC(int) _PyEval_GetCoroutineOriginTrackingDepth(void);

Include/cpython/pystate.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);
2525
#define PyTrace_C_EXCEPTION 5
2626
#define PyTrace_C_RETURN 6
2727
#define PyTrace_OPCODE 7
28-
28+
#define PyTrace_LOCK_ACQUIRE 8
29+
#define PyTrace_LOCK_RELEASE 9
30+
#define PyTrace_THREAD_PREEMPT 10
31+
#define PyTrace_THREAD_RESUME 11
32+
#define PyTrace_THREAD_ACQUIRE 12
33+
#define PyTrace_THREAD_RELEASE 13
34+
#define PyTrace_THREAD_SAVE 14
35+
#define PyTrace_THREAD_RESTORE 15
2936

3037
typedef struct _cframe {
3138
/* This struct will be threaded through the C stack

Include/internal/pycore_ceval.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ PyAPI_FUNC(void) _PyEval_SetCoroutineOriginTrackingDepth(
3131
PyThreadState *tstate,
3232
int new_depth);
3333

34+
extern int _PyEval_NotifyThreadStateChange(PyThreadState *tstate, int event);
35+
3436
void _PyEval_Fini(void);
3537

3638

Python/ceval.c

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ static int maybe_call_line_trace(Py_tracefunc, PyObject *,
8181
static void maybe_dtrace_line(PyFrameObject *, PyTraceInfo *, int);
8282
static void dtrace_function_entry(PyFrameObject *);
8383
static void dtrace_function_return(PyFrameObject *);
84+
static int notify_thread_state_change(PyThreadState *tstate, int event, long arg);
8485

8586
static PyObject * import_name(PyThreadState *, PyFrameObject *,
8687
PyObject *, PyObject *, PyObject *);
@@ -415,18 +416,31 @@ _PyEval_Fini(void)
415416
void
416417
PyEval_AcquireLock(void)
417418
{
419+
struct timespec start, end;
420+
clock_gettime(CLOCK_MONOTONIC, &start);
421+
418422
_PyRuntimeState *runtime = &_PyRuntime;
419423
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
420424
_Py_EnsureTstateNotNULL(tstate);
421425

422426
take_gil(tstate);
427+
428+
clock_gettime(CLOCK_MONOTONIC, &end);
429+
long timediff = ((long)end.tv_sec - (long)start.tv_sec) * (long)1000000
430+
+ ((long)end.tv_nsec - (long)start.tv_nsec) / 1000;
431+
notify_thread_state_change(tstate, PyTrace_LOCK_ACQUIRE, timediff);
423432
}
424433

425434
void
426435
PyEval_ReleaseLock(void)
427436
{
428437
_PyRuntimeState *runtime = &_PyRuntime;
429438
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
439+
440+
if (tstate != NULL) {
441+
notify_thread_state_change(tstate, PyTrace_LOCK_RELEASE, -1);
442+
}
443+
430444
/* This function must succeed when the current thread state is NULL.
431445
We therefore avoid PyThreadState_Get() which dumps a fatal error
432446
in debug mode. */
@@ -438,6 +452,8 @@ PyEval_ReleaseLock(void)
438452
void
439453
_PyEval_ReleaseLock(PyThreadState *tstate)
440454
{
455+
notify_thread_state_change(tstate, PyTrace_LOCK_RELEASE, -1);
456+
441457
struct _ceval_runtime_state *ceval = &tstate->interp->runtime->ceval;
442458
struct _ceval_state *ceval2 = &tstate->interp->ceval;
443459
drop_gil(ceval, ceval2, tstate);
@@ -448,6 +464,9 @@ PyEval_AcquireThread(PyThreadState *tstate)
448464
{
449465
_Py_EnsureTstateNotNULL(tstate);
450466

467+
struct timespec start, end;
468+
clock_gettime(CLOCK_MONOTONIC, &start);
469+
451470
take_gil(tstate);
452471

453472
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
@@ -458,13 +477,20 @@ PyEval_AcquireThread(PyThreadState *tstate)
458477
Py_FatalError("non-NULL old thread state");
459478
}
460479
#endif
480+
481+
clock_gettime(CLOCK_MONOTONIC, &end);
482+
long timediff = ((long)end.tv_sec - (long)start.tv_sec) * (long)1000000
483+
+ ((long)end.tv_nsec - (long)start.tv_nsec) / 1000;
484+
notify_thread_state_change(tstate, PyTrace_THREAD_ACQUIRE, timediff);
461485
}
462486

463487
void
464488
PyEval_ReleaseThread(PyThreadState *tstate)
465489
{
466490
assert(is_tstate_valid(tstate));
467491

492+
notify_thread_state_change(tstate, PyTrace_THREAD_RELEASE, -1);
493+
468494
_PyRuntimeState *runtime = tstate->interp->runtime;
469495
PyThreadState *new_tstate = _PyThreadState_Swap(&runtime->gilstate, NULL);
470496
if (new_tstate != tstate) {
@@ -519,12 +545,16 @@ _PyEval_SignalAsyncExc(PyInterpreterState *interp)
519545
PyThreadState *
520546
PyEval_SaveThread(void)
521547
{
548+
PyThreadState *tstate = PyGILState_GetThisThreadState();
549+
if (tstate != NULL) {
550+
notify_thread_state_change(tstate, PyTrace_THREAD_SAVE, -1);
551+
}
522552
_PyRuntimeState *runtime = &_PyRuntime;
523553
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
524554
PyThreadState *old_tstate = _PyThreadState_GET();
525-
PyThreadState *tstate = _PyThreadState_Swap(&runtime->gilstate, old_tstate);
555+
tstate = _PyThreadState_Swap(&runtime->gilstate, old_tstate);
526556
#else
527-
PyThreadState *tstate = _PyThreadState_Swap(&runtime->gilstate, NULL);
557+
tstate = _PyThreadState_Swap(&runtime->gilstate, NULL);
528558
#endif
529559
_Py_EnsureTstateNotNULL(tstate);
530560

@@ -544,10 +574,18 @@ PyEval_RestoreThread(PyThreadState *tstate)
544574
{
545575
_Py_EnsureTstateNotNULL(tstate);
546576

577+
struct timespec start, end;
578+
clock_gettime(CLOCK_MONOTONIC, &start);
579+
547580
take_gil(tstate);
548581

549582
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
550583
_PyThreadState_Swap(gilstate, tstate);
584+
585+
clock_gettime(CLOCK_MONOTONIC, &end);
586+
long timediff = ((long)end.tv_sec - (long)start.tv_sec) * (long)1000000
587+
+ ((long)end.tv_nsec - (long)start.tv_nsec) / 1000;
588+
notify_thread_state_change(tstate, PyTrace_THREAD_RESTORE, timediff);
551589
}
552590

553591

@@ -1177,7 +1215,9 @@ eval_frame_handle_pending(PyThreadState *tstate)
11771215
}
11781216

11791217
/* GIL drop request */
1180-
if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {
1218+
if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request) && !tstate->tracing) {
1219+
1220+
notify_thread_state_change(tstate, PyTrace_THREAD_PREEMPT, -1);
11811221
/* Give another thread a chance */
11821222
if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
11831223
Py_FatalError("tstate mix-up");
@@ -1186,6 +1226,9 @@ eval_frame_handle_pending(PyThreadState *tstate)
11861226

11871227
/* Other threads may run now */
11881228

1229+
struct timespec start, end;
1230+
clock_gettime(CLOCK_MONOTONIC, &start);
1231+
11891232
take_gil(tstate);
11901233

11911234
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
@@ -1195,6 +1238,11 @@ eval_frame_handle_pending(PyThreadState *tstate)
11951238
Py_FatalError("orphan tstate");
11961239
}
11971240
#endif
1241+
1242+
clock_gettime(CLOCK_MONOTONIC, &end);
1243+
long timediff = ((long)end.tv_sec - (long)start.tv_sec) * (long)1000000
1244+
+ ((long)end.tv_nsec - (long)start.tv_nsec) / 1000;
1245+
notify_thread_state_change(tstate, PyTrace_THREAD_RESUME, timediff);
11981246
}
11991247

12001248
/* Check for asynchronous exception. */
@@ -5572,6 +5620,27 @@ PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
55725620
}
55735621
}
55745622

5623+
void
5624+
PyEval_SetProfileAllThreads(Py_tracefunc func, PyObject *arg)
5625+
{
5626+
PyThreadState *this_tstate = _PyThreadState_GET();
5627+
PyInterpreterState* interp = this_tstate->interp;
5628+
5629+
/* unused var _PyRuntimeState *runtime = &_PyRuntime; */
5630+
/* missing call to HEAD_LOCK(runtime); */
5631+
PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
5632+
/* missing call to HEAD_UNLOCK(runtime); */
5633+
5634+
while (ts) {
5635+
if (_PyEval_SetProfile(ts, func, arg) < 0) {
5636+
_PyErr_WriteUnraisableMsg("Exception ignored in PyEval_SetProfileAllThreads", NULL);
5637+
}
5638+
/* missing call to HEAD_LOCK(runtime); */
5639+
ts = PyThreadState_Next(ts);
5640+
/* missing call to HEAD_UNLOCK(runtime); */
5641+
}
5642+
}
5643+
55755644
int
55765645
_PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
55775646
{
@@ -6512,6 +6581,31 @@ maybe_dtrace_line(PyFrameObject *frame,
65126581
}
65136582
}
65146583

6584+
static int
6585+
notify_thread_state_change(PyThreadState *tstate, int event, long arg)
6586+
{
6587+
if (tstate->c_profilefunc == NULL)
6588+
return 0;
6589+
PyObject *arg_obj = Py_None;
6590+
if (arg >= 0) {
6591+
arg_obj = PyLong_FromLong(arg);
6592+
}
6593+
if (arg_obj == NULL) {
6594+
return -1;
6595+
}
6596+
PyTraceInfo trace_info;
6597+
/* Mark trace_info as uninitialized */
6598+
trace_info.code = NULL;
6599+
int res = call_trace_protected(tstate->c_profilefunc,
6600+
tstate->c_profileobj,
6601+
tstate, tstate->frame,
6602+
&trace_info,
6603+
event, arg_obj);
6604+
if (arg_obj != Py_None) {
6605+
Py_DECREF(arg_obj);
6606+
}
6607+
return res;
6608+
}
65156609

65166610
/* Implement Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as functions
65176611
for the limited API. */

Python/sysmodule.c

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -924,19 +924,24 @@ sys_intern_impl(PyObject *module, PyObject *s)
924924
* Cached interned string objects used for calling the profile and
925925
* trace functions. Initialized by trace_init().
926926
*/
927-
static PyObject *whatstrings[8] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
927+
static PyObject *whatstrings[16] = {
928+
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
929+
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
930+
};
928931

929932
static int
930933
trace_init(void)
931934
{
932-
static const char * const whatnames[8] = {
935+
static const char * const whatnames[16] = {
933936
"call", "exception", "line", "return",
934937
"c_call", "c_exception", "c_return",
935-
"opcode"
938+
"opcode", "lock_acquire", "lock_release",
939+
"thread_preempt", "thread_resume", "thread_acquire",
940+
"thread_release", "thread_save", "thread_restore"
936941
};
937942
PyObject *name;
938943
int i;
939-
for (i = 0; i < 8; ++i) {
944+
for (i = 0; i < 16; ++i) {
940945
if (whatstrings[i] == NULL) {
941946
name = PyUnicode_InternFromString(whatnames[i]);
942947
if (name == NULL)
@@ -952,21 +957,23 @@ static PyObject *
952957
call_trampoline(PyThreadState *tstate, PyObject* callback,
953958
PyFrameObject *frame, int what, PyObject *arg)
954959
{
955-
if (PyFrame_FastToLocalsWithError(frame) < 0) {
960+
if (frame != NULL && PyFrame_FastToLocalsWithError(frame) < 0) {
956961
return NULL;
957962
}
958963

959964
PyObject *stack[3];
960-
stack[0] = (PyObject *)frame;
965+
stack[0] = (frame != NULL) ? (PyObject *)frame : Py_None;
961966
stack[1] = whatstrings[what];
962967
stack[2] = (arg != NULL) ? arg : Py_None;
963968

964969
/* call the Python-level function */
965970
PyObject *result = _PyObject_FastCallTstate(tstate, callback, stack, 3);
966971

967-
PyFrame_LocalsToFast(frame, 1);
968-
if (result == NULL) {
969-
PyTraceBack_Here(frame);
972+
if (frame != NULL) {
973+
PyFrame_LocalsToFast(frame, 1);
974+
if (result == NULL) {
975+
PyTraceBack_Here(frame);
976+
}
970977
}
971978

972979
return result;
@@ -1100,6 +1107,34 @@ Set the profiling function. It will be called on each function call\n\
11001107
and return. See the profiler chapter in the library manual."
11011108
);
11021109

1110+
static PyObject *
1111+
sys__setprofileallthreads(PyObject *module, PyObject *arg)
1112+
{
1113+
PyObject* argument = NULL;
1114+
Py_tracefunc func = NULL;
1115+
1116+
if (trace_init() == -1) {
1117+
return NULL;
1118+
}
1119+
1120+
if (arg != Py_None) {
1121+
func = profile_trampoline;
1122+
argument = arg;
1123+
}
1124+
1125+
PyEval_SetProfileAllThreads(func, argument);
1126+
1127+
Py_RETURN_NONE;
1128+
}
1129+
1130+
PyDoc_STRVAR(_setprofileallthreads_doc,
1131+
"_setprofileallthreads(function)\n\
1132+
\n\
1133+
Set the profiling function for current and all other running threads.\n\
1134+
It will be called on each function calland return. See the profiler\n\
1135+
chapter in the library manual."
1136+
);
1137+
11031138
/*[clinic input]
11041139
sys.getprofile
11051140
@@ -2055,6 +2090,7 @@ static PyMethodDef sys_methods[] = {
20552090
SYS_GETSWITCHINTERVAL_METHODDEF
20562091
SYS_SETDLOPENFLAGS_METHODDEF
20572092
{"setprofile", sys_setprofile, METH_O, setprofile_doc},
2093+
{"_setprofileallthreads", sys__setprofileallthreads, METH_O, _setprofileallthreads_doc},
20582094
SYS_GETPROFILE_METHODDEF
20592095
SYS_SETRECURSIONLIMIT_METHODDEF
20602096
{"settrace", sys_settrace, METH_O, settrace_doc},

0 commit comments

Comments
 (0)