Skip to content

Commit e414a2d

Browse files
authored
gh-127266: avoid data races when updating type slots (gh-131174)
In the free-threaded build, avoid data races caused by updating type slots or type flags after the type was initially created. For those (typically rare) cases, use the stop-the-world mechanism. Remove the use of atomics when reading or writing type flags. The use of atomics is not sufficient to avoid races (since flags are sometimes read without a lock and without atomics) and are no longer required.
1 parent 22f0730 commit e414a2d

File tree

10 files changed

+233
-112
lines changed

10 files changed

+233
-112
lines changed

Include/internal/pycore_interp_structs.h

+3
Original file line numberDiff line numberDiff line change
@@ -667,8 +667,11 @@ struct _Py_interp_cached_objects {
667667

668668
/* object.__reduce__ */
669669
PyObject *objreduce;
670+
#ifndef Py_GIL_DISABLED
671+
/* resolve_slotdups() */
670672
PyObject *type_slots_pname;
671673
pytype_slotdef *type_slots_ptrs[MAX_EQUIV];
674+
#endif
672675

673676
/* TypeVar and related types */
674677
PyTypeObject *generic_type;

Include/internal/pycore_object.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
313313
// Fast inlined version of PyType_HasFeature()
314314
static inline int
315315
_PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
316-
return ((FT_ATOMIC_LOAD_ULONG_RELAXED(type->tp_flags) & feature) != 0);
316+
return ((type->tp_flags) & feature) != 0;
317317
}
318318

319319
extern void _PyType_InitCache(PyInterpreterState *interp);

Include/internal/pycore_typeobject.h

-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ extern int _PyType_AddMethod(PyTypeObject *, PyMethodDef *);
134134
extern void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask,
135135
unsigned long flags);
136136

137-
extern unsigned int _PyType_GetVersionForCurrentState(PyTypeObject *tp);
138137
PyAPI_FUNC(void) _PyType_SetVersion(PyTypeObject *tp, unsigned int version);
139138
PyTypeObject *_PyType_LookupByVersion(unsigned int version);
140139

Include/object.h

+7-5
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,12 @@ given type object has a specified feature.
620620
#define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0)
621621
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
622622

623+
// Flag values for ob_flags (16 bits available, if SIZEOF_VOID_P > 4).
624+
#define _Py_IMMORTAL_FLAGS (1 << 0)
625+
#define _Py_STATICALLY_ALLOCATED_FLAG (1 << 2)
626+
#if defined(Py_GIL_DISABLED) && defined(Py_DEBUG)
627+
#define _Py_TYPE_REVEALED_FLAG (1 << 3)
628+
#endif
623629

624630
#define Py_CONSTANT_NONE 0
625631
#define Py_CONSTANT_FALSE 1
@@ -776,11 +782,7 @@ PyType_HasFeature(PyTypeObject *type, unsigned long feature)
776782
// PyTypeObject is opaque in the limited C API
777783
flags = PyType_GetFlags(type);
778784
#else
779-
# ifdef Py_GIL_DISABLED
780-
flags = _Py_atomic_load_ulong_relaxed(&type->tp_flags);
781-
# else
782-
flags = type->tp_flags;
783-
# endif
785+
flags = type->tp_flags;
784786
#endif
785787
return ((flags & feature) != 0);
786788
}

Include/refcount.h

-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ immortal. The latter should be the only instances that require
1919
cleanup during runtime finalization.
2020
*/
2121

22-
#define _Py_STATICALLY_ALLOCATED_FLAG 4
23-
#define _Py_IMMORTAL_FLAGS 1
24-
2522
#if SIZEOF_VOID_P > 4
2623
/*
2724
In 64+ bit systems, any object whose 32 bit reference count is >= 2**31

Lib/test/test_opcache.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ class TestRacesDoNotCrash(TestBase):
576576
# Careful with these. Bigger numbers have a higher chance of catching bugs,
577577
# but you can also burn through a *ton* of type/dict/function versions:
578578
ITEMS = 1000
579+
SMALL_ITEMS = 100
579580
LOOPS = 4
580581
WRITERS = 2
581582

@@ -619,7 +620,7 @@ class C:
619620
__getitem__ = lambda self, item: None
620621

621622
items = []
622-
for _ in range(self.ITEMS):
623+
for _ in range(self.SMALL_ITEMS):
623624
item = C()
624625
items.append(item)
625626
return items
@@ -790,7 +791,7 @@ class C:
790791
__getattribute__ = lambda self, name: None
791792

792793
items = []
793-
for _ in range(self.ITEMS):
794+
for _ in range(self.SMALL_ITEMS):
794795
item = C()
795796
items.append(item)
796797
return items
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
In the free-threaded build, avoid data races caused by updating type slots
2+
or type flags after the type was initially created. For those (typically
3+
rare) cases, use the stop-the-world mechanism. Remove the use of atomics
4+
when reading or writing type flags. The use of atomics is not sufficient to
5+
avoid races (since flags are sometimes read without a lock and without
6+
atomics) and are no longer required.

0 commit comments

Comments
 (0)