diff options
author | Neil Schemenauer <nas-github@arctrix.com> | 2025-04-28 18:52:08 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-28 18:52:08 (GMT) |
commit | 31d1342de9489f95384dbc748130c2ae6f092e84 (patch) | |
tree | 681eb214dad66436eccd860ee3a2aea1a14fad08 /Objects/typeobject.c | |
parent | fe462f5a9122e1c2641b5369cbb88c4a5e822816 (diff) | |
download | cpython-31d1342de9489f95384dbc748130c2ae6f092e84.zip cpython-31d1342de9489f95384dbc748130c2ae6f092e84.tar.gz cpython-31d1342de9489f95384dbc748130c2ae6f092e84.tar.bz2 |
gh-132942: Fix races in type lookup cache (gh-133032)
Two races related to the type lookup cache, when used in the
free-threaded build. This caused test_opcache to sometimes fail (as
well as other hard to re-produce failures).
Diffstat (limited to 'Objects/typeobject.c')
-rw-r--r-- | Objects/typeobject.c | 14 |
1 files changed, 11 insertions, 3 deletions
diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a720a98..4e614da 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5678,7 +5678,6 @@ is_dunder_name(PyObject *name) static PyObject * update_cache(struct type_cache_entry *entry, PyObject *name, unsigned int version_tag, PyObject *value) { - _Py_atomic_store_uint32_relaxed(&entry->version, version_tag); _Py_atomic_store_ptr_relaxed(&entry->value, value); /* borrowed */ assert(_PyASCIIObject_CAST(name)->hash != -1); OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name); @@ -5686,6 +5685,12 @@ update_cache(struct type_cache_entry *entry, PyObject *name, unsigned int versio // exact unicode object or Py_None so it's safe to do so. PyObject *old_name = entry->name; _Py_atomic_store_ptr_relaxed(&entry->name, Py_NewRef(name)); + // We must write the version last to avoid _Py_TryXGetStackRef() + // operating on an invalid (already deallocated) value inside + // _PyType_LookupRefAndVersion(). If we write the version first then a + // reader could pass the "entry_version == type_version" check but could + // be using the old entry value. + _Py_atomic_store_uint32_release(&entry->version, version_tag); return old_name; } @@ -5762,7 +5767,7 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef // synchronize-with other writing threads by doing an acquire load on the sequence while (1) { uint32_t sequence = _PySeqLock_BeginRead(&entry->sequence); - uint32_t entry_version = _Py_atomic_load_uint32_relaxed(&entry->version); + uint32_t entry_version = _Py_atomic_load_uint32_acquire(&entry->version); uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag); if (entry_version == type_version && _Py_atomic_load_ptr_relaxed(&entry->name) == name) { @@ -5809,11 +5814,14 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef int has_version = 0; unsigned int assigned_version = 0; BEGIN_TYPE_LOCK(); - res = find_name_in_mro(type, name, &error); + // We must assign the version before doing the lookup. If + // find_name_in_mro() blocks and releases the critical section + // then the type version can change. if (MCACHE_CACHEABLE_NAME(name)) { has_version = assign_version_tag(interp, type); assigned_version = type->tp_version_tag; } + res = find_name_in_mro(type, name, &error); END_TYPE_LOCK(); /* Only put NULL results into cache if there was no error. */ |