Skip to content

Commit a8fe4bb

Browse files
authored
gh-98257: Make _PyEval_SetTrace() reentrant (#98258)
Make sys.setprofile() and sys.settrace() functions reentrant. They can no long fail with: RuntimeError("Cannot install a trace function while another trace function is being installed"). Make _PyEval_SetTrace() and _PyEval_SetProfile() functions reentrant, rather than detecting and rejecting reentrant calls. Only delete the reference to function arguments once the new function is fully set, when a reentrant call is safe. Call also _PySys_Audit() earlier.
1 parent 4bd63f6 commit a8fe4bb

File tree

4 files changed

+20
-56
lines changed

4 files changed

+20
-56
lines changed

Lib/test/test_sys_setprofile.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -437,12 +437,8 @@ def __del__(self):
437437
sys.setprofile(bar)
438438

439439
sys.setprofile(A())
440-
with support.catch_unraisable_exception() as cm:
441-
sys.setprofile(foo)
442-
self.assertEqual(cm.unraisable.object, A.__del__)
443-
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
444-
445-
self.assertEqual(sys.getprofile(), foo)
440+
sys.setprofile(foo)
441+
self.assertEqual(sys.getprofile(), bar)
446442

447443

448444
def test_same_object(self):

Lib/test/test_sys_settrace.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -2796,12 +2796,8 @@ def __del__(self):
27962796
sys.settrace(bar)
27972797

27982798
sys.settrace(A())
2799-
with support.catch_unraisable_exception() as cm:
2800-
sys.settrace(foo)
2801-
self.assertEqual(cm.unraisable.object, A.__del__)
2802-
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
2803-
2804-
self.assertEqual(sys.gettrace(), foo)
2799+
sys.settrace(foo)
2800+
self.assertEqual(sys.gettrace(), bar)
28052801

28062802

28072803
def test_same_object(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Make :func:`sys.setprofile` and :func:`sys.settrace` functions reentrant. They
2+
can no long fail with: ``RuntimeError("Cannot install a trace function while
3+
another trace function is being installed")``. Patch by Victor Stinner.

Python/ceval.c

+13-44
Original file line numberDiff line numberDiff line change
@@ -6343,38 +6343,23 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
63436343
/* The caller must hold the GIL */
63446344
assert(PyGILState_Check());
63456345

6346-
static int reentrant = 0;
6347-
if (reentrant) {
6348-
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a profile function "
6349-
"while another profile function is being installed");
6350-
reentrant = 0;
6351-
return -1;
6352-
}
6353-
reentrant = 1;
6354-
63556346
/* Call _PySys_Audit() in the context of the current thread state,
63566347
even if tstate is not the current thread state. */
63576348
PyThreadState *current_tstate = _PyThreadState_GET();
63586349
if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
6359-
reentrant = 0;
63606350
return -1;
63616351
}
63626352

6363-
PyObject *profileobj = tstate->c_profileobj;
6364-
6365-
tstate->c_profilefunc = NULL;
6366-
tstate->c_profileobj = NULL;
6367-
/* Must make sure that tracing is not ignored if 'profileobj' is freed */
6368-
_PyThreadState_UpdateTracingState(tstate);
6369-
Py_XDECREF(profileobj);
6370-
6371-
Py_XINCREF(arg);
6372-
tstate->c_profileobj = arg;
63736353
tstate->c_profilefunc = func;
6374-
6354+
PyObject *old_profileobj = tstate->c_profileobj;
6355+
tstate->c_profileobj = Py_XNewRef(arg);
63756356
/* Flag that tracing or profiling is turned on */
63766357
_PyThreadState_UpdateTracingState(tstate);
6377-
reentrant = 0;
6358+
6359+
// gh-98257: Only call Py_XDECREF() once the new profile function is fully
6360+
// set, so it's safe to call sys.setprofile() again (reentrant call).
6361+
Py_XDECREF(old_profileobj);
6362+
63786363
return 0;
63796364
}
63806365

@@ -6416,39 +6401,23 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
64166401
/* The caller must hold the GIL */
64176402
assert(PyGILState_Check());
64186403

6419-
static int reentrant = 0;
6420-
6421-
if (reentrant) {
6422-
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a trace function "
6423-
"while another trace function is being installed");
6424-
reentrant = 0;
6425-
return -1;
6426-
}
6427-
reentrant = 1;
6428-
64296404
/* Call _PySys_Audit() in the context of the current thread state,
64306405
even if tstate is not the current thread state. */
64316406
PyThreadState *current_tstate = _PyThreadState_GET();
64326407
if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
6433-
reentrant = 0;
64346408
return -1;
64356409
}
64366410

6437-
PyObject *traceobj = tstate->c_traceobj;
6438-
6439-
tstate->c_tracefunc = NULL;
6440-
tstate->c_traceobj = NULL;
6441-
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
6442-
_PyThreadState_UpdateTracingState(tstate);
6443-
Py_XINCREF(arg);
6444-
Py_XDECREF(traceobj);
6445-
tstate->c_traceobj = arg;
64466411
tstate->c_tracefunc = func;
6447-
6412+
PyObject *old_traceobj = tstate->c_traceobj;
6413+
tstate->c_traceobj = Py_XNewRef(arg);
64486414
/* Flag that tracing or profiling is turned on */
64496415
_PyThreadState_UpdateTracingState(tstate);
64506416

6451-
reentrant = 0;
6417+
// gh-98257: Only call Py_XDECREF() once the new trace function is fully
6418+
// set, so it's safe to call sys.settrace() again (reentrant call).
6419+
Py_XDECREF(old_traceobj);
6420+
64526421
return 0;
64536422
}
64546423

0 commit comments

Comments
 (0)