Skip to content

Commit 3bb0e14

Browse files
committed
gh-116738: Make cProfile module thread-safe
1 parent ef4dd1d commit 3bb0e14

File tree

4 files changed

+104
-15
lines changed

4 files changed

+104
-15
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import unittest
2+
3+
from test.support import threading_helper
4+
5+
import cProfile
6+
import pstats
7+
8+
9+
NTHREADS = 10
10+
INSERT_PER_THREAD = 1000
11+
12+
13+
@threading_helper.requires_working_threading()
14+
class TestCProfile(unittest.TestCase):
15+
def test_cprofile_racing_list_insert(self):
16+
def list_insert(lst):
17+
for i in range(INSERT_PER_THREAD):
18+
lst.insert(0, i)
19+
20+
lst = []
21+
22+
with cProfile.Profile() as pr:
23+
threading_helper.run_concurrently(
24+
worker_func=list_insert, nthreads=NTHREADS, args=(lst,)
25+
)
26+
pr.create_stats()
27+
ps = pstats.Stats(pr)
28+
stats_profile = ps.get_stats_profile()
29+
list_insert_profile = stats_profile.func_profiles[
30+
"<method 'insert' of 'list' objects>"
31+
]
32+
# Even though there is no explicit recursive call to insert,
33+
# cProfile may record some calls as recursive due to limitations
34+
# in its handling of multithreaded programs. This issue is not
35+
# directly related to FT Python itself; however, it tends to be
36+
# more noticeable when using FT Python. Therefore, consider only
37+
# the calls section and disregard the recursive part.
38+
list_insert_ncalls = list_insert_profile.ncalls.split("/")[0]
39+
self.assertEqual(
40+
int(list_insert_ncalls), NTHREADS * INSERT_PER_THREAD
41+
)
42+
43+
self.assertEqual(len(lst), NTHREADS * INSERT_PER_THREAD)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make :mod:`cProfile` thread-safe on the :term:`free threaded <free
2+
threading>` build

Modules/_lsprof.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ static int statsForEntry(rotating_node_t *node, void *arg)
534534
}
535535

536536
/*[clinic input]
537+
@critical_section self
537538
_lsprof.Profiler.getstats
538539
539540
cls: defining_class
@@ -565,7 +566,7 @@ profiler_subentry objects:
565566

566567
static PyObject *
567568
_lsprof_Profiler_getstats_impl(ProfilerObject *self, PyTypeObject *cls)
568-
/*[clinic end generated code: output=1806ef720019ee03 input=445e193ef4522902]*/
569+
/*[clinic end generated code: output=1806ef720019ee03 input=77dc00ff84d1ed5e]*/
569570
{
570571
statscollector_t collect;
571572
collect.state = _PyType_GetModuleState(cls);
@@ -613,6 +614,7 @@ setBuiltins(ProfilerObject *pObj, int nvalue)
613614
}
614615

615616
/*[clinic input]
617+
@critical_section self
616618
_lsprof.Profiler._pystart_callback
617619
618620
code: object
@@ -624,14 +626,15 @@ _lsprof.Profiler._pystart_callback
624626
static PyObject *
625627
_lsprof_Profiler__pystart_callback_impl(ProfilerObject *self, PyObject *code,
626628
PyObject *instruction_offset)
627-
/*[clinic end generated code: output=5fec8b7ad5ed25e8 input=b166e6953c579cda]*/
629+
/*[clinic end generated code: output=5fec8b7ad5ed25e8 input=c9ced1fe7182f358]*/
628630
{
629631
ptrace_enter_call((PyObject*)self, (void *)code, code);
630632

631633
Py_RETURN_NONE;
632634
}
633635

634636
/*[clinic input]
637+
@critical_section self
635638
_lsprof.Profiler._pythrow_callback
636639
637640
code: object
@@ -645,14 +648,15 @@ static PyObject *
645648
_lsprof_Profiler__pythrow_callback_impl(ProfilerObject *self, PyObject *code,
646649
PyObject *instruction_offset,
647650
PyObject *exception)
648-
/*[clinic end generated code: output=0a32988919dfb94c input=fd728fc2c074f5e6]*/
651+
/*[clinic end generated code: output=0a32988919dfb94c input=3cd9554a8ebb13d7]*/
649652
{
650653
ptrace_enter_call((PyObject*)self, (void *)code, code);
651654

652655
Py_RETURN_NONE;
653656
}
654657

655658
/*[clinic input]
659+
@critical_section self
656660
_lsprof.Profiler._pyreturn_callback
657661
658662
code: object
@@ -667,7 +671,7 @@ _lsprof_Profiler__pyreturn_callback_impl(ProfilerObject *self,
667671
PyObject *code,
668672
PyObject *instruction_offset,
669673
PyObject *retval)
670-
/*[clinic end generated code: output=9e2f6fc1b882c51e input=667ffaeb2fa6fd1f]*/
674+
/*[clinic end generated code: output=9e2f6fc1b882c51e input=4d33df394ab00b42]*/
671675
{
672676
ptrace_leave_call((PyObject*)self, (void *)code);
673677

@@ -703,6 +707,7 @@ PyObject* get_cfunc_from_callable(PyObject* callable, PyObject* self_arg, PyObje
703707
}
704708

705709
/*[clinic input]
710+
@critical_section self
706711
_lsprof.Profiler._ccall_callback
707712
708713
code: object
@@ -717,7 +722,7 @@ static PyObject *
717722
_lsprof_Profiler__ccall_callback_impl(ProfilerObject *self, PyObject *code,
718723
PyObject *instruction_offset,
719724
PyObject *callable, PyObject *self_arg)
720-
/*[clinic end generated code: output=152db83cabd18cad input=0e66687cfb95c001]*/
725+
/*[clinic end generated code: output=152db83cabd18cad input=4c52bc9026278a19]*/
721726
{
722727
if (self->flags & POF_BUILTINS) {
723728
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
@@ -733,6 +738,7 @@ _lsprof_Profiler__ccall_callback_impl(ProfilerObject *self, PyObject *code,
733738
}
734739

735740
/*[clinic input]
741+
@critical_section self
736742
_lsprof.Profiler._creturn_callback
737743
738744
code: object
@@ -748,7 +754,7 @@ _lsprof_Profiler__creturn_callback_impl(ProfilerObject *self, PyObject *code,
748754
PyObject *instruction_offset,
749755
PyObject *callable,
750756
PyObject *self_arg)
751-
/*[clinic end generated code: output=1e886dde8fed8fb0 input=b18afe023746923a]*/
757+
/*[clinic end generated code: output=1e886dde8fed8fb0 input=7ef849de89bacd71]*/
752758
{
753759
if (self->flags & POF_BUILTINS) {
754760
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
@@ -780,6 +786,7 @@ static const struct {
780786

781787

782788
/*[clinic input]
789+
@critical_section self
783790
_lsprof.Profiler.enable
784791
785792
subcalls: bool = True
@@ -796,7 +803,7 @@ Start collecting profiling information.
796803
static PyObject *
797804
_lsprof_Profiler_enable_impl(ProfilerObject *self, int subcalls,
798805
int builtins)
799-
/*[clinic end generated code: output=1e747f9dc1edd571 input=9ab81405107ab7f1]*/
806+
/*[clinic end generated code: output=1e747f9dc1edd571 input=b005b7cb7edcca88]*/
800807
{
801808
int all_events = 0;
802809
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0) {
@@ -869,14 +876,15 @@ flush_unmatched(ProfilerObject *pObj)
869876

870877

871878
/*[clinic input]
879+
@critical_section self
872880
_lsprof.Profiler.disable
873881
874882
Stop collecting profiling information.
875883
[clinic start generated code]*/
876884

877885
static PyObject *
878886
_lsprof_Profiler_disable_impl(ProfilerObject *self)
879-
/*[clinic end generated code: output=838cffef7f651870 input=05700b3fc68d1f50]*/
887+
/*[clinic end generated code: output=838cffef7f651870 input=456ec13900aff222]*/
880888
{
881889
if (self->flags & POF_EXT_TIMER) {
882890
PyErr_SetString(PyExc_RuntimeError,
@@ -928,14 +936,15 @@ _lsprof_Profiler_disable_impl(ProfilerObject *self)
928936
}
929937

930938
/*[clinic input]
939+
@critical_section self
931940
_lsprof.Profiler.clear
932941
933942
Clear all profiling information collected so far.
934943
[clinic start generated code]*/
935944

936945
static PyObject *
937946
_lsprof_Profiler_clear_impl(ProfilerObject *self)
938-
/*[clinic end generated code: output=dd1c668fb84b1335 input=fbe1f88c28be4f98]*/
947+
/*[clinic end generated code: output=dd1c668fb84b1335 input=bdb218890fae64d7]*/
939948
{
940949
if (self->flags & POF_EXT_TIMER) {
941950
PyErr_SetString(PyExc_RuntimeError,
@@ -977,6 +986,7 @@ profiler_dealloc(PyObject *op)
977986
}
978987

979988
/*[clinic input]
989+
@critical_section self
980990
_lsprof.Profiler.__init__ as profiler_init
981991
982992
timer: object(c_default='NULL') = None
@@ -995,7 +1005,7 @@ is, in seconds).
9951005
static int
9961006
profiler_init_impl(ProfilerObject *self, PyObject *timer, double timeunit,
9971007
int subcalls, int builtins)
998-
/*[clinic end generated code: output=ac523803ec9f9df2 input=8285ca746f96a414]*/
1008+
/*[clinic end generated code: output=ac523803ec9f9df2 input=7022aae930773bba]*/
9991009
{
10001010
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0) {
10011011
return -1;

Modules/clinic/_lsprof.c.h

Lines changed: 39 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)