Skip to content

Commit c94e5c4

Browse files
committed
Fix some data race issues.
Need to do a merge before reporting (I lost that bit of code on a re-factor). Fix various issues with data races. When merging from all threads, we need to stop-the-world to avoid races. When toggling on or off, also need to stop-the-world. Remove the need for locking for _PyStats_Attach().
1 parent df1c5d4 commit c94e5c4

File tree

3 files changed

+53
-43
lines changed

3 files changed

+53
-43
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ struct _is {
973973
#ifdef Py_STATS
974974
// true if recording of pystats is on, this is used when new threads
975975
// are created to decide if recording should be on for them
976-
bool pystats_enabled;
976+
int pystats_enabled;
977977
// allocated when (and if) stats are first enabled
978978
PyStats *pystats_struct;
979979
#ifdef Py_GIL_DISABLED

Objects/object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,7 @@ _PyObject_InitState(PyInterpreterState *interp)
24232423
if (interp->config._pystats) {
24242424
// start with pystats enabled, can be disabled via sys._stats_off()
24252425
// this needs to be set before the first tstate is created
2426-
interp->pystats_enabled = true;
2426+
interp->pystats_enabled = 1;
24272427
}
24282428
#endif
24292429
return _PyStatus_OK();

Python/pystats.c

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#include "Python.h"
22

33
#include "pycore_opcode_metadata.h" // _PyOpcode_Caches
4+
#include "pycore_pyatomic_ft_wrappers.h"
5+
#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock()
46
#include "pycore_uop_metadata.h" // _PyOpcode_uop_name
57
#include "pycore_uop_ids.h" // MAX_UOP_ID
6-
#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock()
78
#include "pycore_pystate.h" // _PyThreadState_GET()
89
#include "pycore_runtime.h" // NUM_GENERATIONS
910

@@ -105,7 +106,7 @@ add_stat_dict(
105106
PyObject*
106107
_Py_GetSpecializationStats(void) {
107108
PyThreadState *tstate = _PyThreadState_GET();
108-
PyStats *src = tstate->interp->pystats_struct;
109+
PyStats *src = FT_ATOMIC_LOAD_PTR_RELAXED(tstate->interp->pystats_struct);
109110
if (src == NULL) {
110111
Py_RETURN_NONE;
111112
}
@@ -554,11 +555,30 @@ stats_merge_thread(_PyThreadStateImpl *tstate, bool zero)
554555
#endif // Py_GIL_DISABLED
555556

556557
// toggle stats collection on or off for all threads
557-
static void
558-
stats_toggle_on_off(PyThreadState *tstate)
558+
static int
559+
stats_toggle_on_off(PyThreadState *tstate, int on)
559560
{
561+
bool changed = false;
560562
PyInterpreterState *interp = tstate->interp;
561-
_Py_FOR_EACH_TSTATE_BEGIN(interp, ts) {
563+
STATS_LOCK(interp);
564+
if (on && interp->pystats_struct == NULL) {
565+
PyStats *s = PyMem_RawCalloc(1, sizeof(PyStats));
566+
if (s == NULL) {
567+
STATS_UNLOCK(interp);
568+
return -1;
569+
}
570+
FT_ATOMIC_STORE_PTR_RELAXED(interp->pystats_struct, s);
571+
}
572+
if (tstate->interp->pystats_enabled != on) {
573+
FT_ATOMIC_STORE_INT_RELAXED(tstate->interp->pystats_enabled, on);
574+
changed = true;
575+
}
576+
STATS_UNLOCK(interp);
577+
if (!changed) {
578+
return 0;
579+
}
580+
_PyEval_StopTheWorld(interp);
581+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, ts) {
562582
if (!ts->_status.active) {
563583
continue;
564584
}
@@ -577,65 +597,53 @@ stats_toggle_on_off(PyThreadState *tstate)
577597
// write to the tss variable for the 'ts' thread
578598
*ts_impl->pystats_tss = s;
579599
}
580-
_Py_FOR_EACH_TSTATE_END(interp);
600+
_PyEval_StartTheWorld(interp);
601+
return 0;
581602
}
582603

583-
// merge stats for all threads into the global structure
604+
// merge stats for all threads into the per-interpreter structure
605+
// if 'zero' is true then the per-interpreter stats are zeroed after merging
584606
static void
585-
stats_merge_all(void)
607+
stats_merge_all(bool zero)
586608
{
587-
#ifdef Py_GIL_DISABLED
588609
PyThreadState *tstate = _PyThreadState_GET();
589610
if (tstate == NULL) {
590611
return;
591612
}
613+
if (FT_ATOMIC_LOAD_PTR_RELAXED(tstate->interp->pystats_struct) == NULL) {
614+
return;
615+
}
592616
PyInterpreterState *interp = tstate->interp;
593-
_Py_FOR_EACH_TSTATE_BEGIN(interp, ts) {
594-
stats_merge_thread((_PyThreadStateImpl*)ts, true);
617+
_PyEval_StopTheWorld(interp);
618+
#ifdef Py_GIL_DISABLED
619+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, ts) {
620+
stats_merge_thread((_PyThreadStateImpl *)ts, true);
595621
}
596-
_Py_FOR_EACH_TSTATE_END(interp);
597622
#endif
623+
if (zero) {
624+
memset(interp->pystats_struct, 0, sizeof(PyStats));
625+
}
626+
_PyEval_StartTheWorld(interp);
598627
}
599628

600629
int
601630
_Py_StatsOn(void)
602631
{
603632
PyThreadState *tstate = _PyThreadState_GET();
604-
PyInterpreterState *interp = tstate->interp;
605-
STATS_LOCK(interp);
606-
tstate->interp->pystats_enabled = true;
607-
if (interp->pystats_struct == NULL) {
608-
interp->pystats_struct = PyMem_RawCalloc(1, sizeof(PyStats));
609-
if (interp->pystats_struct == NULL) {
610-
STATS_UNLOCK(interp);
611-
return -1;
612-
}
613-
}
614-
stats_toggle_on_off(tstate);
615-
STATS_UNLOCK(interp);
616-
return 0;
633+
return stats_toggle_on_off(tstate, 1);
617634
}
618635

619636
void
620637
_Py_StatsOff(void)
621638
{
622639
PyThreadState *tstate = _PyThreadState_GET();
623-
STATS_LOCK(tstate->interp);
624-
tstate->interp->pystats_enabled = false;
625-
stats_toggle_on_off(tstate);
626-
STATS_UNLOCK(tstate->interp);
640+
stats_toggle_on_off(tstate, 0);
627641
}
628642

629643
void
630644
_Py_StatsClear(void)
631645
{
632-
PyThreadState *tstate = _PyThreadState_GET();
633-
STATS_LOCK(tstate->interp);
634-
if (tstate->interp->pystats_struct != NULL) {
635-
stats_merge_all();
636-
memset(tstate->interp->pystats_struct, 0, sizeof(PyStats));
637-
}
638-
STATS_UNLOCK(tstate->interp);
646+
stats_merge_all(true);
639647
}
640648

641649
static int
@@ -654,6 +662,7 @@ int
654662
_Py_PrintSpecializationStats(int to_file)
655663
{
656664
assert(to_file);
665+
stats_merge_all(false);
657666
PyThreadState *tstate = _PyThreadState_GET();
658667
STATS_LOCK(tstate->interp);
659668
PyStats *stats = tstate->interp->pystats_struct;
@@ -721,11 +730,12 @@ _PyStats_ThreadInit(PyInterpreterState *interp, _PyThreadStateImpl *tstate)
721730
{
722731
STATS_LOCK(interp);
723732
if (interp->pystats_enabled) {
724-
interp->pystats_struct = PyMem_RawCalloc(1, sizeof(PyStats));
725-
if (interp->pystats_struct == NULL) {
733+
PyStats *s = PyMem_RawCalloc(1, sizeof(PyStats));
734+
if (s == NULL) {
726735
STATS_UNLOCK(interp);
727736
return false;
728737
}
738+
FT_ATOMIC_STORE_PTR_RELAXED(interp->pystats_struct, s);
729739
}
730740
STATS_UNLOCK(interp);
731741
#ifdef Py_GIL_DISABLED
@@ -741,7 +751,9 @@ void
741751
_PyStats_ThreadFini(_PyThreadStateImpl *tstate)
742752
{
743753
#ifdef Py_GIL_DISABLED
754+
STATS_LOCK(((PyThreadState *)tstate)->interp);
744755
stats_merge_thread(tstate, false);
756+
STATS_UNLOCK(((PyThreadState *)tstate)->interp);
745757
PyMem_RawFree(tstate->pystats_struct);
746758
#endif
747759
}
@@ -751,8 +763,7 @@ _PyStats_Attach(_PyThreadStateImpl *tstate)
751763
{
752764
PyStats *s;
753765
PyInterpreterState *interp = ((PyThreadState *)tstate)->interp;
754-
STATS_LOCK(interp);
755-
if (interp->pystats_enabled) {
766+
if (FT_ATOMIC_LOAD_INT_RELAXED(interp->pystats_enabled)) {
756767
#ifdef Py_GIL_DISABLED
757768
s = tstate->pystats_struct;
758769
#else
@@ -762,7 +773,6 @@ _PyStats_Attach(_PyThreadStateImpl *tstate)
762773
else {
763774
s = NULL;
764775
}
765-
STATS_UNLOCK(interp);
766776
// use correct TSS variable for thread
767777
tstate->pystats_tss = &_Py_tss_stats;
768778
// write to the tss variable for the 'ts' thread

0 commit comments

Comments
 (0)