mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
gh-127266: avoid data races when updating type slots (gh-133177)
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.
This commit is contained in:
parent
7ca6d79fa3
commit
fbbbc10055
10 changed files with 575 additions and 133 deletions
|
@ -677,8 +677,11 @@ struct _Py_interp_cached_objects {
|
||||||
|
|
||||||
/* object.__reduce__ */
|
/* object.__reduce__ */
|
||||||
PyObject *objreduce;
|
PyObject *objreduce;
|
||||||
|
#ifndef Py_GIL_DISABLED
|
||||||
|
/* resolve_slotdups() */
|
||||||
PyObject *type_slots_pname;
|
PyObject *type_slots_pname;
|
||||||
pytype_slotdef *type_slots_ptrs[MAX_EQUIV];
|
pytype_slotdef *type_slots_ptrs[MAX_EQUIV];
|
||||||
|
#endif
|
||||||
|
|
||||||
/* TypeVar and related types */
|
/* TypeVar and related types */
|
||||||
PyTypeObject *generic_type;
|
PyTypeObject *generic_type;
|
||||||
|
|
|
@ -313,7 +313,7 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
|
||||||
// Fast inlined version of PyType_HasFeature()
|
// Fast inlined version of PyType_HasFeature()
|
||||||
static inline int
|
static inline int
|
||||||
_PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
|
_PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
|
||||||
return ((FT_ATOMIC_LOAD_ULONG_RELAXED(type->tp_flags) & feature) != 0);
|
return ((type->tp_flags) & feature) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void _PyType_InitCache(PyInterpreterState *interp);
|
extern void _PyType_InitCache(PyInterpreterState *interp);
|
||||||
|
|
|
@ -134,7 +134,6 @@ extern int _PyType_AddMethod(PyTypeObject *, PyMethodDef *);
|
||||||
extern void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask,
|
extern void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask,
|
||||||
unsigned long flags);
|
unsigned long flags);
|
||||||
|
|
||||||
extern unsigned int _PyType_GetVersionForCurrentState(PyTypeObject *tp);
|
|
||||||
PyAPI_FUNC(void) _PyType_SetVersion(PyTypeObject *tp, unsigned int version);
|
PyAPI_FUNC(void) _PyType_SetVersion(PyTypeObject *tp, unsigned int version);
|
||||||
PyTypeObject *_PyType_LookupByVersion(unsigned int version);
|
PyTypeObject *_PyType_LookupByVersion(unsigned int version);
|
||||||
|
|
||||||
|
|
|
@ -620,6 +620,12 @@ given type object has a specified feature.
|
||||||
#define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0)
|
#define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0)
|
||||||
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
|
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
|
||||||
|
|
||||||
|
// Flag values for ob_flags (16 bits available, if SIZEOF_VOID_P > 4).
|
||||||
|
#define _Py_IMMORTAL_FLAGS (1 << 0)
|
||||||
|
#define _Py_STATICALLY_ALLOCATED_FLAG (1 << 2)
|
||||||
|
#if defined(Py_GIL_DISABLED) && defined(Py_DEBUG)
|
||||||
|
#define _Py_TYPE_REVEALED_FLAG (1 << 3)
|
||||||
|
#endif
|
||||||
|
|
||||||
#define Py_CONSTANT_NONE 0
|
#define Py_CONSTANT_NONE 0
|
||||||
#define Py_CONSTANT_FALSE 1
|
#define Py_CONSTANT_FALSE 1
|
||||||
|
@ -776,11 +782,7 @@ PyType_HasFeature(PyTypeObject *type, unsigned long feature)
|
||||||
// PyTypeObject is opaque in the limited C API
|
// PyTypeObject is opaque in the limited C API
|
||||||
flags = PyType_GetFlags(type);
|
flags = PyType_GetFlags(type);
|
||||||
#else
|
#else
|
||||||
# ifdef Py_GIL_DISABLED
|
flags = type->tp_flags;
|
||||||
flags = _Py_atomic_load_ulong_relaxed(&type->tp_flags);
|
|
||||||
# else
|
|
||||||
flags = type->tp_flags;
|
|
||||||
# endif
|
|
||||||
#endif
|
#endif
|
||||||
return ((flags & feature) != 0);
|
return ((flags & feature) != 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,6 @@ immortal. The latter should be the only instances that require
|
||||||
cleanup during runtime finalization.
|
cleanup during runtime finalization.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _Py_STATICALLY_ALLOCATED_FLAG 4
|
|
||||||
#define _Py_IMMORTAL_FLAGS 1
|
|
||||||
|
|
||||||
#if SIZEOF_VOID_P > 4
|
#if SIZEOF_VOID_P > 4
|
||||||
/*
|
/*
|
||||||
In 64+ bit systems, any object whose 32 bit reference count is >= 2**31
|
In 64+ bit systems, any object whose 32 bit reference count is >= 2**31
|
||||||
|
|
|
@ -4114,6 +4114,34 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.fail("shouldn't be able to create inheritance cycles")
|
self.fail("shouldn't be able to create inheritance cycles")
|
||||||
|
|
||||||
|
def test_assign_bases_many_subclasses(self):
|
||||||
|
# This is intended to check that typeobject.c:queue_slot_update() can
|
||||||
|
# handle updating many subclasses when a slot method is re-assigned.
|
||||||
|
class A:
|
||||||
|
x = 'hello'
|
||||||
|
def __call__(self):
|
||||||
|
return 123
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class X:
|
||||||
|
x = 'bye'
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
subclasses = []
|
||||||
|
for i in range(1000):
|
||||||
|
sc = type(f'Sub{i}', (B,), {})
|
||||||
|
subclasses.append(sc)
|
||||||
|
|
||||||
|
self.assertEqual(subclasses[0]()(), 123)
|
||||||
|
self.assertEqual(subclasses[0]().x, 'hello')
|
||||||
|
B.__bases__ = (X,)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
subclasses[0]()()
|
||||||
|
self.assertEqual(subclasses[0]().x, 'bye')
|
||||||
|
|
||||||
def test_builtin_bases(self):
|
def test_builtin_bases(self):
|
||||||
# Make sure all the builtin types can have their base queried without
|
# Make sure all the builtin types can have their base queried without
|
||||||
# segfaulting. See issue #5787.
|
# segfaulting. See issue #5787.
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
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.
|
File diff suppressed because it is too large
Load diff
|
@ -139,6 +139,19 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
check_invalid_reentrancy(void)
|
||||||
|
{
|
||||||
|
#if defined(Py_DEBUG) && defined(Py_GIL_DISABLED)
|
||||||
|
// In the free-threaded build, the interpreter must not be re-entered if
|
||||||
|
// the world-is-stopped. If so, that's a bug somewhere (quite likely in
|
||||||
|
// the painfully complex typeobject code).
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
assert(!interp->stoptheworld.world_stopped);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef Py_DEBUG
|
#ifdef Py_DEBUG
|
||||||
static void
|
static void
|
||||||
dump_item(_PyStackRef item)
|
dump_item(_PyStackRef item)
|
||||||
|
@ -996,6 +1009,7 @@ PyObject* _Py_HOT_FUNCTION DONT_SLP_VECTORIZE
|
||||||
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
|
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
|
||||||
{
|
{
|
||||||
_Py_EnsureTstateNotNULL(tstate);
|
_Py_EnsureTstateNotNULL(tstate);
|
||||||
|
check_invalid_reentrancy();
|
||||||
CALL_STAT_INC(pyeval_calls);
|
CALL_STAT_INC(pyeval_calls);
|
||||||
|
|
||||||
#if USE_COMPUTED_GOTOS && !Py_TAIL_CALL_INTERP
|
#if USE_COMPUTED_GOTOS && !Py_TAIL_CALL_INTERP
|
||||||
|
|
|
@ -12,15 +12,12 @@
|
||||||
|
|
||||||
# These warnings trigger directly in a CPython function.
|
# These warnings trigger directly in a CPython function.
|
||||||
|
|
||||||
race_top:assign_version_tag
|
|
||||||
race_top:_Py_slot_tp_getattr_hook
|
|
||||||
race_top:dump_traceback
|
race_top:dump_traceback
|
||||||
race_top:fatal_error
|
race_top:fatal_error
|
||||||
race_top:_PyFrame_GetCode
|
race_top:_PyFrame_GetCode
|
||||||
race_top:_PyFrame_Initialize
|
race_top:_PyFrame_Initialize
|
||||||
race_top:_PyObject_TryGetInstanceAttribute
|
race_top:_PyObject_TryGetInstanceAttribute
|
||||||
race_top:PyUnstable_InterpreterFrame_GetLine
|
race_top:PyUnstable_InterpreterFrame_GetLine
|
||||||
race_top:type_modified_unlocked
|
|
||||||
race_top:write_thread_id
|
race_top:write_thread_id
|
||||||
|
|
||||||
# gh-129068: race on shared range iterators (test_free_threading.test_zip.ZipThreading.test_threading)
|
# gh-129068: race on shared range iterators (test_free_threading.test_zip.ZipThreading.test_threading)
|
||||||
|
@ -29,9 +26,6 @@ race_top:rangeiter_next
|
||||||
# gh-129748: test.test_free_threading.test_slots.TestSlots.test_object
|
# gh-129748: test.test_free_threading.test_slots.TestSlots.test_object
|
||||||
race_top:mi_block_set_nextx
|
race_top:mi_block_set_nextx
|
||||||
|
|
||||||
# gh-127266: type slot updates are not thread-safe (test_opcache.test_load_attr_method_lazy_dict)
|
|
||||||
race_top:update_one_slot
|
|
||||||
|
|
||||||
# https://gist.github.com/mpage/6962e8870606cfc960e159b407a0cb40
|
# https://gist.github.com/mpage/6962e8870606cfc960e159b407a0cb40
|
||||||
thread:pthread_create
|
thread:pthread_create
|
||||||
|
|
||||||
|
@ -49,7 +43,6 @@ race:list_inplace_repeat_lock_held
|
||||||
race:PyObject_Realloc
|
race:PyObject_Realloc
|
||||||
|
|
||||||
# gh-133467. Some of these could be hard to trigger.
|
# gh-133467. Some of these could be hard to trigger.
|
||||||
race_top:update_one_slot
|
|
||||||
race_top:_Py_slot_tp_getattr_hook
|
race_top:_Py_slot_tp_getattr_hook
|
||||||
race_top:slot_tp_descr_get
|
race_top:slot_tp_descr_get
|
||||||
race_top:type_set_name
|
race_top:type_set_name
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue