Skip to content

Commit b2aef25

Browse files
committed
Add atomic snapshot for dict/dict_keys in extract_elements
Add fast paths for dict and dict_keys types in extract_elements_with, matching _list_extend() in CPython Objects/listobject.c. Each branch takes an atomic snapshot under a single read lock, preventing race conditions from concurrent dict mutation without the GIL. Remove expectedFailure from test_thread_safety.
1 parent 5b77f1d commit b2aef25

2 files changed

Lines changed: 10 additions & 6 deletions

File tree

‎Lib/test/_test_multiprocessing.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4814,8 +4814,6 @@ def test_finalize(self):
48144814
self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e'])
48154815

48164816
@support.requires_resource('cpu')
4817-
# TODO: RUSTPYTHON; dict iteration races with concurrent GC mutations
4818-
@unittest.expectedFailure
48194817
def test_thread_safety(self):
48204818
# bpo-24484: _run_finalizers() should be thread-safe
48214819
def cb():

‎crates/vm/src/vm/mod.rs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
self, PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
2323
PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned, PyWeak,
2424
code::PyCode,
25-
dict::{PyDictItems, PyDictValues},
25+
dict::{PyDictItems, PyDictKeys, PyDictValues},
2626
pystr::AsPyStr,
2727
tuple::PyTuple,
2828
},
@@ -1822,24 +1822,30 @@ impl VirtualMachine {
18221822
where
18231823
F: Fn(PyObjectRef) -> PyResult<T>,
18241824
{
1825-
// Extract elements from item, if possible:
1825+
// Type-specific fast paths corresponding to _list_extend() in CPython
1826+
// Objects/listobject.c. Each branch takes an atomic snapshot to avoid
1827+
// race conditions from concurrent mutation (no GIL).
18261828
let cls = value.class();
18271829
let list_borrow;
18281830
let slice = if cls.is(self.ctx.types.tuple_type) {
18291831
value.downcast_ref::<PyTuple>().unwrap().as_slice()
18301832
} else if cls.is(self.ctx.types.list_type) {
18311833
list_borrow = value.downcast_ref::<PyList>().unwrap().borrow_vec();
18321834
&list_borrow
1835+
} else if cls.is(self.ctx.types.dict_type) {
1836+
let keys = value.downcast_ref::<PyDict>().unwrap().keys_vec();
1837+
return keys.into_iter().map(func).collect();
1838+
} else if cls.is(self.ctx.types.dict_keys_type) {
1839+
let keys = value.downcast_ref::<PyDictKeys>().unwrap().dict.keys_vec();
1840+
return keys.into_iter().map(func).collect();
18331841
} else if cls.is(self.ctx.types.dict_values_type) {
1834-
// Atomic snapshot of dict values - prevents race condition during iteration
18351842
let values = value
18361843
.downcast_ref::<PyDictValues>()
18371844
.unwrap()
18381845
.dict
18391846
.values_vec();
18401847
return values.into_iter().map(func).collect();
18411848
} else if cls.is(self.ctx.types.dict_items_type) {
1842-
// Atomic snapshot of dict items - prevents race condition during iteration
18431849
let items = value
18441850
.downcast_ref::<PyDictItems>()
18451851
.unwrap()

0 commit comments

Comments
 (0)