Skip to content

Commit 8554c09

Browse files
gh-116738: make cProfile module thread-safe (#138229)
Co-authored-by: Kumar Aditya <[email protected]>
1 parent 7e652f4 commit 8554c09

File tree

4 files changed

+100
-14
lines changed

4 files changed

+100
-14
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: 18 additions & 9 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
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=3dc69eb85ed73d91]*/
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
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=b61a0e79cf1f8499]*/
628630
{
629631
ptrace_enter_call((PyObject*)self, (void *)code, code);
630632

631633
Py_RETURN_NONE;
632634
}
633635

634636
/*[clinic input]
637+
@critical_section
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=60c7f272206d3758]*/
649652
{
650653
ptrace_enter_call((PyObject*)self, (void *)code, code);
651654

652655
Py_RETURN_NONE;
653656
}
654657

655658
/*[clinic input]
659+
@critical_section
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=0ddcc1ec53faa928]*/
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
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=2fc1e0630ee5e32b]*/
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
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=bdc246d6b5b8714a]*/
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
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=0b88115b1c796173]*/
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
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=f7e4787cae20f7f6]*/
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
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=4aab219d5d7a9bec]*/
939948
{
940949
if (self->flags & POF_EXT_TIMER) {
941950
PyErr_SetString(PyExc_RuntimeError,

Modules/clinic/_lsprof.c.h

Lines changed: 37 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)