From 8f8ca161c35449e6e87cd475db55b3a2a0e0f8f8 Mon Sep 17 00:00:00 2001 From: Chaimaa Abi <118536453+Cha-Imaa@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:42:28 +0400 Subject: [PATCH] gh-159117: Preserve sys.setprofile callbacks when destroying a tracing profiler --- Lib/test/test_profiling/test_tracing_profiler.py | 16 ++++++++++++++++ ...026-10-18-05-48-00.gh-issue-159117.i4Jk8P.rst | 2 ++ Modules/_lsprof.c | 9 --------- 3 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-18-05-48-00.gh-issue-159117.i4Jk8P.rst diff --git a/Lib/test/test_profiling/test_tracing_profiler.py b/Lib/test/test_profiling/test_tracing_profiler.py index 50de9af86e86d0..184b3e867e4e21 100644 --- a/Lib/test/test_profiling/test_tracing_profiler.py +++ b/Lib/test/test_profiling/test_tracing_profiler.py @@ -118,6 +118,22 @@ def test_profile_enable_disable(self): prof.disable() self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None) + def test_dealloc_does_not_disable_sys_profile(self): + self.addCleanup(sys.setprofile, sys.getprofile()) + self.addCleanup(sys.monitoring.free_tool_id, sys.monitoring.PROFILER_ID) + + def callback(frame, event, arg): + pass + + prof = self.profilerclass() + prof.enable() + sys.setprofile(callback) + # Release the monitoring callbacks so the enabled profiler can be + # destroyed without an explicit disable(). + sys.monitoring.free_tool_id(sys.monitoring.PROFILER_ID) + del prof + self.assertIs(sys.getprofile(), callback) + def test_profile_as_context_manager(self): prof = self.profilerclass() # Make sure we clean ourselves up if the test fails for some reason. diff --git a/Misc/NEWS.d/next/Library/2026-10-18-05-48-00.gh-issue-159117.i4Jk8P.rst b/Misc/NEWS.d/next/Library/2026-10-18-05-48-00.gh-issue-159117.i4Jk8P.rst new file mode 100644 index 00000000000000..2e4636c3fd9587 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-18-05-48-00.gh-issue-159117.i4Jk8P.rst @@ -0,0 +1,2 @@ +Prevent deallocation of a :mod:`profiling.tracing` profiler from disabling an +unrelated :func:`sys.setprofile` callback. diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 65857c495f2339..c241dfbc04d9a1 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -4,8 +4,6 @@ #include "Python.h" #include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_SetProfile() -#include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_time.h" // _PyTime_FromSecondsObject() #include "pycore_typeobject.h" // _PyType_GetModuleState() #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() @@ -982,13 +980,6 @@ profiler_dealloc(PyObject *op) { ProfilerObject *self = ProfilerObject_CAST(op); PyObject_GC_UnTrack(self); - if (self->flags & POF_ENABLED) { - PyThreadState *tstate = _PyThreadState_GET(); - if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) { - PyErr_FormatUnraisable("Exception ignored while " - "destroying _lsprof profiler"); - } - } /* Drop the external timer before flushing: it is Python code, and the profiler can be deallocated by the garbage collector. */