Skip to content

Commit 37ff5f7

Browse files
committed
Drop old PyObjectRef outside type lock to prevent deadlock
Dropping values inside with_type_lock can trigger weakref callbacks, which may access attributes (LOAD_ATTR specialization) and re-acquire the non-reentrant type mutex, causing deadlock. Return old values from lock closures so they drop after lock release.
1 parent 8bfcc6b commit 37ff5f7

File tree

4 files changed

+58
-67
lines changed

4 files changed

+58
-67
lines changed

crates/vm/src/builtins/type.rs

Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::{
1818
common::{
1919
ascii,
2020
borrow::BorrowedValue,
21-
lock::{PyMutex, PyRwLock, PyRwLockReadGuard},
21+
lock::{PyRwLock, PyRwLockReadGuard},
2222
},
2323
function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue},
2424
object::{Traverse, TraverseFn},
@@ -276,8 +276,6 @@ pub struct TypeSpecializationCache {
276276
pub init: PyAtomicRef<Option<PyFunction>>,
277277
pub getitem: PyAtomicRef<Option<PyFunction>>,
278278
pub getitem_version: AtomicU32,
279-
// Serialize cache writes/invalidation similar to CPython's BEGIN_TYPE_LOCK.
280-
write_lock: PyMutex<()>,
281279
retired: PyRwLock<Vec<PyObjectRef>>,
282280
}
283281

@@ -287,7 +285,6 @@ impl TypeSpecializationCache {
287285
init: PyAtomicRef::from(None::<PyRef<PyFunction>>),
288286
getitem: PyAtomicRef::from(None::<PyRef<PyFunction>>),
289287
getitem_version: AtomicU32::new(0),
290-
write_lock: PyMutex::new(()),
291288
retired: PyRwLock::new(Vec::new()),
292289
}
293290
}
@@ -328,9 +325,6 @@ impl TypeSpecializationCache {
328325

329326
#[inline]
330327
fn invalidate_for_type_modified(&self) {
331-
let _guard = self.write_lock.lock();
332-
// _spec_cache contract: type modification invalidates all cached
333-
// specialization functions.
334328
self.swap_init(None, None);
335329
self.swap_getitem(None, None);
336330
}
@@ -350,7 +344,6 @@ impl TypeSpecializationCache {
350344
}
351345

352346
fn clear_into(&self, out: &mut Vec<PyObjectRef>) {
353-
let _guard = self.write_lock.lock();
354347
let old_init = unsafe { self.init.swap(None) };
355348
if let Some(old_init) = old_init {
356349
out.push(old_init.into());
@@ -1009,7 +1002,6 @@ impl PyType {
10091002
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
10101003
return false;
10111004
}
1012-
let _guard = ext.specialization_cache.write_lock.lock();
10131005
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
10141006
return false;
10151007
}
@@ -1050,7 +1042,6 @@ impl PyType {
10501042
return false;
10511043
}
10521044
Self::with_type_lock(vm, || {
1053-
let _guard = ext.specialization_cache.write_lock.lock();
10541045
if self.tp_version_tag.load(Ordering::Acquire) != tp_version {
10551046
return false;
10561047
}
@@ -1569,18 +1560,19 @@ impl PyType {
15691560
drop(attrs);
15701561

15711562
let none = vm.ctx.none();
1572-
Ok(Self::with_type_lock(vm, || {
1563+
let (result, _prev) = Self::with_type_lock(vm, || {
15731564
let mut attrs = self.attributes.write();
15741565
if let Some(annotate) = attrs.get(annotate_key).cloned() {
1575-
return annotate;
1566+
return (annotate, None);
15761567
}
15771568
if let Some(annotate) = attrs.get(annotate_func_key).cloned() {
1578-
return annotate;
1569+
return (annotate, None);
15791570
}
15801571
self.modified_inner();
1581-
attrs.insert(annotate_func_key, none.clone());
1582-
none
1583-
}))
1572+
let prev = attrs.insert(annotate_func_key, none.clone());
1573+
(none, prev)
1574+
});
1575+
Ok(result)
15841576
}
15851577

15861578
#[pygetset(setter)]
@@ -1603,13 +1595,16 @@ impl PyType {
16031595
return Err(vm.new_type_error("__annotate__ must be callable or None"));
16041596
}
16051597

1606-
Self::with_type_lock(vm, || {
1598+
let _prev_values = Self::with_type_lock(vm, || {
16071599
self.modified_inner();
16081600
let mut attrs = self.attributes.write();
1609-
if !vm.is_none(&value) {
1610-
attrs.swap_remove(identifier!(vm, __annotations_cache__));
1611-
}
1612-
attrs.insert(identifier!(vm, __annotate_func__), value);
1601+
let removed = if !vm.is_none(&value) {
1602+
attrs.swap_remove(identifier!(vm, __annotations_cache__))
1603+
} else {
1604+
None
1605+
};
1606+
let prev = attrs.insert(identifier!(vm, __annotate_func__), value);
1607+
(removed, prev)
16131608
});
16141609

16151610
Ok(())
@@ -1672,20 +1667,21 @@ impl PyType {
16721667
vm.ctx.new_dict().into()
16731668
};
16741669

1675-
Ok(Self::with_type_lock(vm, || {
1670+
let (result, _prev) = Self::with_type_lock(vm, || {
16761671
let mut attrs = self.attributes.write();
16771672
if let Some(existing) = attrs.get(annotations_key).cloned()
16781673
&& !existing.class().is(vm.ctx.types.getset_type)
16791674
{
1680-
return existing;
1675+
return (existing, None);
16811676
}
16821677
if let Some(existing) = attrs.get(annotations_cache_key).cloned() {
1683-
return existing;
1678+
return (existing, None);
16841679
}
16851680
self.modified_inner();
1686-
attrs.insert(annotations_cache_key, annotations.clone());
1687-
annotations
1688-
}))
1681+
let prev = attrs.insert(annotations_cache_key, annotations.clone());
1682+
(annotations, prev)
1683+
});
1684+
Ok(result)
16891685
}
16901686

16911687
#[pygetset(setter)]
@@ -1701,44 +1697,42 @@ impl PyType {
17011697
)));
17021698
}
17031699

1704-
Self::with_type_lock(vm, || {
1700+
let _prev_values = Self::with_type_lock(vm, || {
17051701
self.modified_inner();
17061702
let mut attrs = self.attributes.write();
17071703
let has_annotations = attrs.contains_key(identifier!(vm, __annotations__));
17081704

1705+
let mut prev = Vec::new();
17091706
match value {
17101707
crate::function::PySetterValue::Assign(value) => {
17111708
let key = if has_annotations {
17121709
identifier!(vm, __annotations__)
17131710
} else {
17141711
identifier!(vm, __annotations_cache__)
17151712
};
1716-
attrs.insert(key, value);
1713+
prev.extend(attrs.insert(key, value));
17171714
if has_annotations {
1718-
attrs.swap_remove(identifier!(vm, __annotations_cache__));
1715+
prev.extend(attrs.swap_remove(identifier!(vm, __annotations_cache__)));
17191716
}
17201717
}
17211718
crate::function::PySetterValue::Delete => {
17221719
let removed = if has_annotations {
1723-
attrs
1724-
.swap_remove(identifier!(vm, __annotations__))
1725-
.is_some()
1720+
attrs.swap_remove(identifier!(vm, __annotations__))
17261721
} else {
1727-
attrs
1728-
.swap_remove(identifier!(vm, __annotations_cache__))
1729-
.is_some()
1722+
attrs.swap_remove(identifier!(vm, __annotations_cache__))
17301723
};
1731-
if !removed {
1724+
if removed.is_none() {
17321725
return Err(vm.new_attribute_error("__annotations__"));
17331726
}
1727+
prev.extend(removed);
17341728
if has_annotations {
1735-
attrs.swap_remove(identifier!(vm, __annotations_cache__));
1729+
prev.extend(attrs.swap_remove(identifier!(vm, __annotations_cache__)));
17361730
}
17371731
}
17381732
}
1739-
attrs.swap_remove(identifier!(vm, __annotate_func__));
1740-
attrs.swap_remove(identifier!(vm, __annotate__));
1741-
Ok(())
1733+
prev.extend(attrs.swap_remove(identifier!(vm, __annotate_func__)));
1734+
prev.extend(attrs.swap_remove(identifier!(vm, __annotate__)));
1735+
Ok(prev)
17421736
})?;
17431737

17441738
Ok(())
@@ -1772,11 +1766,12 @@ impl PyType {
17721766
#[pygetset(setter)]
17731767
fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
17741768
self.check_set_special_type_attr(identifier!(vm, __module__), vm)?;
1775-
Self::with_type_lock(vm, || {
1769+
let _prev_values = Self::with_type_lock(vm, || {
17761770
self.modified_inner();
1777-
self.attributes
1778-
.write()
1779-
.insert(identifier!(vm, __module__), value);
1771+
let mut attributes = self.attributes.write();
1772+
let removed = attributes.swap_remove(identifier!(vm, __firstlineno__));
1773+
let prev = attributes.insert(identifier!(vm, __module__), value);
1774+
(removed, prev)
17801775
});
17811776
Ok(())
17821777
}
@@ -1903,9 +1898,9 @@ impl PyType {
19031898
match value {
19041899
PySetterValue::Assign(val) => {
19051900
self.check_set_special_type_attr(key, vm)?;
1906-
Self::with_type_lock(vm, || {
1901+
let _prev_value = Self::with_type_lock(vm, || {
19071902
self.modified_inner();
1908-
self.attributes.write().insert(key, val.into());
1903+
self.attributes.write().insert(key, val.into())
19091904
});
19101905
}
19111906
PySetterValue::Delete => {
@@ -1915,9 +1910,9 @@ impl PyType {
19151910
self.slot_name()
19161911
)));
19171912
}
1918-
Self::with_type_lock(vm, || {
1913+
let _prev_value = Self::with_type_lock(vm, || {
19191914
self.modified_inner();
1920-
self.attributes.write().shift_remove(&key);
1915+
self.attributes.write().shift_remove(&key)
19211916
});
19221917
}
19231918
}
@@ -2542,11 +2537,11 @@ impl Py<PyType> {
25422537
// Check if we can set this special type attribute
25432538
self.check_set_special_type_attr(identifier!(vm, __doc__), vm)?;
25442539

2545-
PyType::with_type_lock(vm, || {
2540+
let _prev_value = PyType::with_type_lock(vm, || {
25462541
self.modified_inner();
25472542
self.attributes
25482543
.write()
2549-
.insert(identifier!(vm, __doc__), value);
2544+
.insert(identifier!(vm, __doc__), value)
25502545
});
25512546

25522547
Ok(())
@@ -2609,14 +2604,17 @@ impl SetAttr for PyType {
26092604
}
26102605
let assign = value.is_assign();
26112606

2612-
Self::with_type_lock(vm, || {
2607+
// Drop old value OUTSIDE the type lock to avoid deadlock:
2608+
// dropping may trigger weakref callbacks → method calls →
2609+
// LOAD_ATTR specialization → version_for_specialization → type lock.
2610+
let _prev_value = Self::with_type_lock(vm, || {
26132611
// Invalidate inline caches before modifying attributes.
26142612
// This ensures other threads see the version invalidation before
26152613
// any attribute changes, preventing use-after-free of cached descriptors.
26162614
zelf.modified_inner();
26172615

26182616
if let PySetterValue::Assign(value) = value {
2619-
zelf.attributes.write().insert(attr_name, value);
2617+
Ok(zelf.attributes.write().insert(attr_name, value))
26202618
} else {
26212619
let prev_value = zelf.attributes.write().shift_remove(attr_name); // TODO: swap_remove applicable?
26222620
if prev_value.is_none() {
@@ -2626,8 +2624,8 @@ impl SetAttr for PyType {
26262624
attr_name,
26272625
)));
26282626
}
2627+
Ok(prev_value)
26292628
}
2630-
Ok(())
26312629
})?;
26322630

26332631
if attr_name.as_wtf8().starts_with("__") && attr_name.as_wtf8().ends_with("__") {

crates/vm/src/stdlib/_ctypes/base.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2554,11 +2554,10 @@ fn make_fields(
25542554
}
25552555

25562556
let new_descr = super::PyCField::new_from_field(fdescr, index, offset);
2557-
cls.as_object().set_attr(
2557+
cls.set_attr(
25582558
vm.ctx.intern_str(fname.as_wtf8()),
25592559
new_descr.to_pyobject(vm),
2560-
vm,
2561-
)?;
2560+
);
25622561
}
25632562

25642563
Ok(())
@@ -2597,11 +2596,10 @@ pub(super) fn make_anon_fields(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResul
25972596

25982597
let mut new_descr = super::PyCField::new_from_field(descr, 0, 0);
25992598
new_descr.set_anonymous(true);
2600-
cls.as_object().set_attr(
2599+
cls.set_attr(
26012600
vm.ctx.intern_str(fname.as_wtf8()),
26022601
new_descr.to_pyobject(vm),
2603-
vm,
2604-
)?;
2602+
);
26052603

26062604
make_fields(cls, descr, descr.index, descr.offset, vm)?;
26072605
}

crates/vm/src/stdlib/_ctypes/structure.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,7 @@ impl PyCStructType {
498498
};
499499

500500
// Set the CField as a class attribute
501-
cls.as_object().set_attr(
502-
vm.ctx.intern_str(name.clone()),
503-
c_field.to_pyobject(vm),
504-
vm,
505-
)?;
501+
cls.set_attr(vm.ctx.intern_str(name.clone()), c_field.to_pyobject(vm));
506502

507503
// Update tracking - don't advance offset for packed bitfields
508504
if field_advances_offset {

crates/vm/src/stdlib/_ctypes/union.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,7 @@ impl PyCUnionType {
312312
PyCField::new(name.clone(), field_type_ref, 0, size as isize, index)
313313
};
314314

315-
cls.as_object()
316-
.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm), vm)?;
315+
cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm));
317316
}
318317

319318
// Calculate total_align and aligned_size

0 commit comments

Comments
 (0)