Fix dict race condition (#6720)

This commit is contained in:
Jeong, YunWon
2026-01-13 21:27:24 +09:00
committed by GitHub
parent 4d7a289971
commit e91bc11b0a
3 changed files with 62 additions and 1 deletions

View File

@@ -62,6 +62,24 @@ impl PyDict {
&self.entries
}
/// Returns all keys as a Vec, atomically under a single read lock.
/// Thread-safe: prevents "dictionary changed size during iteration" errors.
pub fn keys_vec(&self) -> Vec<PyObjectRef> {
self.entries.keys()
}
/// Returns all values as a Vec, atomically under a single read lock.
/// Thread-safe: prevents "dictionary changed size during iteration" errors.
pub fn values_vec(&self) -> Vec<PyObjectRef> {
self.entries.values()
}
/// Returns all items as a Vec, atomically under a single read lock.
/// Thread-safe: prevents "dictionary changed size during iteration" errors.
pub fn items_vec(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
self.entries.items()
}
// Used in update and ior.
pub(crate) fn merge_object(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let casted: Result<PyRefExact<Self>, _> = other.downcast_exact(vm);

View File

@@ -553,6 +553,22 @@ impl<T: Clone> Dict<T> {
.collect()
}
pub fn values(&self) -> Vec<T> {
self.read()
.entries
.iter()
.filter_map(|v| v.as_ref().map(|v| v.value.clone()))
.collect()
}
pub fn items(&self) -> Vec<(PyObjectRef, T)> {
self.read()
.entries
.iter()
.filter_map(|v| v.as_ref().map(|v| (v.key.clone(), v.value.clone())))
.collect()
}
pub fn try_fold_keys<Acc, Fold>(&self, init: Acc, f: Fold) -> PyResult<Acc>
where
Fold: FnMut(Acc, &PyObject) -> PyResult<Acc>,

View File

@@ -20,7 +20,11 @@ use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
builtins::{
PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
PyStrRef, PyTypeRef, code::PyCode, pystr::AsPyStr, tuple::PyTuple,
PyStrRef, PyTypeRef,
code::PyCode,
dict::{PyDictItems, PyDictKeys, PyDictValues},
pystr::AsPyStr,
tuple::PyTuple,
},
codecs::CodecsRegistry,
common::{hash::HashSecret, lock::PyMutex, rc::PyRc},
@@ -808,6 +812,29 @@ impl VirtualMachine {
} else if cls.is(self.ctx.types.list_type) {
list_borrow = value.downcast_ref::<PyList>().unwrap().borrow_vec();
&list_borrow
} else if cls.is(self.ctx.types.dict_keys_type) {
// Atomic snapshot of dict keys - prevents race condition during iteration
let keys = value.downcast_ref::<PyDictKeys>().unwrap().dict.keys_vec();
return keys.into_iter().map(func).collect();
} else if cls.is(self.ctx.types.dict_values_type) {
// Atomic snapshot of dict values - prevents race condition during iteration
let values = value
.downcast_ref::<PyDictValues>()
.unwrap()
.dict
.values_vec();
return values.into_iter().map(func).collect();
} else if cls.is(self.ctx.types.dict_items_type) {
// Atomic snapshot of dict items - prevents race condition during iteration
let items = value
.downcast_ref::<PyDictItems>()
.unwrap()
.dict
.items_vec();
return items
.into_iter()
.map(|(k, v)| func(self.ctx.new_tuple(vec![k, v]).into()))
.collect();
} else {
return self.map_py_iter(value, func);
};