[3.14] gh-116738: make cProfile module thread-safe (GH-138229) (#138575)

gh-116738: make `cProfile` module thread-safe (GH-138229)
(cherry picked from commit 8554c0917e)

Co-authored-by: Alper <alperyoney@fb.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
Miss Islington (bot) 2025-10-07 20:51:22 +02:00 committed by GitHub
parent e51acb3fa6
commit 4429554223
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 100 additions and 14 deletions

View file

@ -0,0 +1,43 @@
import unittest
from test.support import threading_helper
import cProfile
import pstats
NTHREADS = 10
INSERT_PER_THREAD = 1000
@threading_helper.requires_working_threading()
class TestCProfile(unittest.TestCase):
def test_cprofile_racing_list_insert(self):
def list_insert(lst):
for i in range(INSERT_PER_THREAD):
lst.insert(0, i)
lst = []
with cProfile.Profile() as pr:
threading_helper.run_concurrently(
worker_func=list_insert, nthreads=NTHREADS, args=(lst,)
)
pr.create_stats()
ps = pstats.Stats(pr)
stats_profile = ps.get_stats_profile()
list_insert_profile = stats_profile.func_profiles[
"<method 'insert' of 'list' objects>"
]
# Even though there is no explicit recursive call to insert,
# cProfile may record some calls as recursive due to limitations
# in its handling of multithreaded programs. This issue is not
# directly related to FT Python itself; however, it tends to be
# more noticeable when using FT Python. Therefore, consider only
# the calls section and disregard the recursive part.
list_insert_ncalls = list_insert_profile.ncalls.split("/")[0]
self.assertEqual(
int(list_insert_ncalls), NTHREADS * INSERT_PER_THREAD
)
self.assertEqual(len(lst), NTHREADS * INSERT_PER_THREAD)

View file

@ -0,0 +1,2 @@
Make :mod:`cProfile` thread-safe on the :term:`free threaded <free
threading>` build.

View file

@ -534,6 +534,7 @@ static int statsForEntry(rotating_node_t *node, void *arg)
}
/*[clinic input]
@critical_section
_lsprof.Profiler.getstats
cls: defining_class
@ -565,7 +566,7 @@ profiler_subentry objects:
static PyObject *
_lsprof_Profiler_getstats_impl(ProfilerObject *self, PyTypeObject *cls)
/*[clinic end generated code: output=1806ef720019ee03 input=445e193ef4522902]*/
/*[clinic end generated code: output=1806ef720019ee03 input=3dc69eb85ed73d91]*/
{
statscollector_t collect;
collect.state = _PyType_GetModuleState(cls);
@ -613,6 +614,7 @@ setBuiltins(ProfilerObject *pObj, int nvalue)
}
/*[clinic input]
@critical_section
_lsprof.Profiler._pystart_callback
code: object
@ -624,7 +626,7 @@ _lsprof.Profiler._pystart_callback
static PyObject *
_lsprof_Profiler__pystart_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *instruction_offset)
/*[clinic end generated code: output=5fec8b7ad5ed25e8 input=b166e6953c579cda]*/
/*[clinic end generated code: output=5fec8b7ad5ed25e8 input=b61a0e79cf1f8499]*/
{
ptrace_enter_call((PyObject*)self, (void *)code, code);
@ -632,6 +634,7 @@ _lsprof_Profiler__pystart_callback_impl(ProfilerObject *self, PyObject *code,
}
/*[clinic input]
@critical_section
_lsprof.Profiler._pythrow_callback
code: object
@ -645,7 +648,7 @@ static PyObject *
_lsprof_Profiler__pythrow_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *instruction_offset,
PyObject *exception)
/*[clinic end generated code: output=0a32988919dfb94c input=fd728fc2c074f5e6]*/
/*[clinic end generated code: output=0a32988919dfb94c input=60c7f272206d3758]*/
{
ptrace_enter_call((PyObject*)self, (void *)code, code);
@ -653,6 +656,7 @@ _lsprof_Profiler__pythrow_callback_impl(ProfilerObject *self, PyObject *code,
}
/*[clinic input]
@critical_section
_lsprof.Profiler._pyreturn_callback
code: object
@ -667,7 +671,7 @@ _lsprof_Profiler__pyreturn_callback_impl(ProfilerObject *self,
PyObject *code,
PyObject *instruction_offset,
PyObject *retval)
/*[clinic end generated code: output=9e2f6fc1b882c51e input=667ffaeb2fa6fd1f]*/
/*[clinic end generated code: output=9e2f6fc1b882c51e input=0ddcc1ec53faa928]*/
{
ptrace_leave_call((PyObject*)self, (void *)code);
@ -703,6 +707,7 @@ PyObject* get_cfunc_from_callable(PyObject* callable, PyObject* self_arg, PyObje
}
/*[clinic input]
@critical_section
_lsprof.Profiler._ccall_callback
code: object
@ -717,7 +722,7 @@ static PyObject *
_lsprof_Profiler__ccall_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *instruction_offset,
PyObject *callable, PyObject *self_arg)
/*[clinic end generated code: output=152db83cabd18cad input=0e66687cfb95c001]*/
/*[clinic end generated code: output=152db83cabd18cad input=2fc1e0630ee5e32b]*/
{
if (self->flags & POF_BUILTINS) {
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
@ -733,6 +738,7 @@ _lsprof_Profiler__ccall_callback_impl(ProfilerObject *self, PyObject *code,
}
/*[clinic input]
@critical_section
_lsprof.Profiler._creturn_callback
code: object
@ -748,7 +754,7 @@ _lsprof_Profiler__creturn_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *instruction_offset,
PyObject *callable,
PyObject *self_arg)
/*[clinic end generated code: output=1e886dde8fed8fb0 input=b18afe023746923a]*/
/*[clinic end generated code: output=1e886dde8fed8fb0 input=bdc246d6b5b8714a]*/
{
if (self->flags & POF_BUILTINS) {
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
@ -780,6 +786,7 @@ static const struct {
/*[clinic input]
@critical_section
_lsprof.Profiler.enable
subcalls: bool = True
@ -796,7 +803,7 @@ Start collecting profiling information.
static PyObject *
_lsprof_Profiler_enable_impl(ProfilerObject *self, int subcalls,
int builtins)
/*[clinic end generated code: output=1e747f9dc1edd571 input=9ab81405107ab7f1]*/
/*[clinic end generated code: output=1e747f9dc1edd571 input=0b88115b1c796173]*/
{
int all_events = 0;
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0) {
@ -869,6 +876,7 @@ flush_unmatched(ProfilerObject *pObj)
/*[clinic input]
@critical_section
_lsprof.Profiler.disable
Stop collecting profiling information.
@ -876,7 +884,7 @@ Stop collecting profiling information.
static PyObject *
_lsprof_Profiler_disable_impl(ProfilerObject *self)
/*[clinic end generated code: output=838cffef7f651870 input=05700b3fc68d1f50]*/
/*[clinic end generated code: output=838cffef7f651870 input=f7e4787cae20f7f6]*/
{
if (self->flags & POF_EXT_TIMER) {
PyErr_SetString(PyExc_RuntimeError,
@ -928,6 +936,7 @@ _lsprof_Profiler_disable_impl(ProfilerObject *self)
}
/*[clinic input]
@critical_section
_lsprof.Profiler.clear
Clear all profiling information collected so far.
@ -935,7 +944,7 @@ Clear all profiling information collected so far.
static PyObject *
_lsprof_Profiler_clear_impl(ProfilerObject *self)
/*[clinic end generated code: output=dd1c668fb84b1335 input=fbe1f88c28be4f98]*/
/*[clinic end generated code: output=dd1c668fb84b1335 input=4aab219d5d7a9bec]*/
{
if (self->flags & POF_EXT_TIMER) {
PyErr_SetString(PyExc_RuntimeError,

View file

@ -6,6 +6,7 @@ preserve
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_lsprof_Profiler_getstats__doc__,
@ -45,11 +46,18 @@ _lsprof_Profiler_getstats_impl(ProfilerObject *self, PyTypeObject *cls);
static PyObject *
_lsprof_Profiler_getstats(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "getstats() takes no arguments");
return NULL;
goto exit;
}
return _lsprof_Profiler_getstats_impl((ProfilerObject *)self, cls);
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler_getstats_impl((ProfilerObject *)self, cls);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
}
PyDoc_STRVAR(_lsprof_Profiler__pystart_callback__doc__,
@ -76,7 +84,9 @@ _lsprof_Profiler__pystart_callback(PyObject *self, PyObject *const *args, Py_ssi
}
code = args[0];
instruction_offset = args[1];
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler__pystart_callback_impl((ProfilerObject *)self, code, instruction_offset);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
@ -109,7 +119,9 @@ _lsprof_Profiler__pythrow_callback(PyObject *self, PyObject *const *args, Py_ssi
code = args[0];
instruction_offset = args[1];
exception = args[2];
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler__pythrow_callback_impl((ProfilerObject *)self, code, instruction_offset, exception);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
@ -143,7 +155,9 @@ _lsprof_Profiler__pyreturn_callback(PyObject *self, PyObject *const *args, Py_ss
code = args[0];
instruction_offset = args[1];
retval = args[2];
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler__pyreturn_callback_impl((ProfilerObject *)self, code, instruction_offset, retval);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
@ -178,7 +192,9 @@ _lsprof_Profiler__ccall_callback(PyObject *self, PyObject *const *args, Py_ssize
instruction_offset = args[1];
callable = args[2];
self_arg = args[3];
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler__ccall_callback_impl((ProfilerObject *)self, code, instruction_offset, callable, self_arg);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
@ -215,7 +231,9 @@ _lsprof_Profiler__creturn_callback(PyObject *self, PyObject *const *args, Py_ssi
instruction_offset = args[1];
callable = args[2];
self_arg = args[3];
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler__creturn_callback_impl((ProfilerObject *)self, code, instruction_offset, callable, self_arg);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
@ -299,7 +317,9 @@ _lsprof_Profiler_enable(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
goto exit;
}
skip_optional_pos:
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler_enable_impl((ProfilerObject *)self, subcalls, builtins);
Py_END_CRITICAL_SECTION();
exit:
return return_value;
@ -320,7 +340,13 @@ _lsprof_Profiler_disable_impl(ProfilerObject *self);
static PyObject *
_lsprof_Profiler_disable(PyObject *self, PyObject *Py_UNUSED(ignored))
{
return _lsprof_Profiler_disable_impl((ProfilerObject *)self);
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler_disable_impl((ProfilerObject *)self);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(_lsprof_Profiler_clear__doc__,
@ -338,7 +364,13 @@ _lsprof_Profiler_clear_impl(ProfilerObject *self);
static PyObject *
_lsprof_Profiler_clear(PyObject *self, PyObject *Py_UNUSED(ignored))
{
return _lsprof_Profiler_clear_impl((ProfilerObject *)self);
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _lsprof_Profiler_clear_impl((ProfilerObject *)self);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(profiler_init__doc__,
@ -444,4 +476,4 @@ skip_optional_pos:
exit:
return return_value;
}
/*[clinic end generated code: output=9e46985561166c37 input=a9049054013a1b77]*/
/*[clinic end generated code: output=af26a0b0ddcc3351 input=a9049054013a1b77]*/