Skip to content

Commit b689eda

Browse files
committed
fix del
1 parent eb50246 commit b689eda

1 file changed

Lines changed: 107 additions & 12 deletions

File tree

crates/vm/src/object/core.rs

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ impl WeakRefList {
220220
}))
221221
});
222222
let mut inner = unsafe { inner_ptr.as_ref().lock() };
223+
// If obj was cleared by GC but object is still alive (e.g., new weakref
224+
// created during __del__), restore the obj pointer
225+
if inner.obj.is_none() {
226+
inner.obj = Some(NonNull::from(obj));
227+
}
223228
if is_generic && let Some(generic_weakref) = inner.generic_weakref {
224229
let generic_weakref = unsafe { generic_weakref.as_ref() };
225230
if generic_weakref.0.ref_count.get() != 0 {
@@ -243,14 +248,72 @@ impl WeakRefList {
243248
weak
244249
}
245250

251+
/// Clear all weakrefs and call their callbacks.
252+
/// This is the main clear function called when the owner object is being dropped.
253+
/// It decrements ref_count and deallocates if needed.
246254
fn clear(&self) {
255+
self.clear_inner(true, true)
256+
}
257+
258+
/// Clear all weakrefs but DON'T call callbacks. Instead, return them for later invocation.
259+
/// This is used by GC to ensure ALL weakrefs are cleared BEFORE any callbacks are invoked.
260+
/// Returns a vector of (PyRef<PyWeak>, callback) pairs.
261+
fn clear_for_gc_collect_callbacks(&self) -> Vec<(PyRef<PyWeak>, PyObjectRef)> {
262+
let ptr = match self.inner.get() {
263+
Some(ptr) => ptr,
264+
None => return vec![],
265+
};
266+
let mut inner = unsafe { ptr.as_ref().lock() };
267+
268+
// Clear the object reference
269+
inner.obj = None;
270+
271+
// Collect weakrefs with callbacks
272+
let mut callbacks = Vec::new();
273+
let mut v = Vec::with_capacity(16);
274+
loop {
275+
let inner2 = &mut *inner;
276+
let iter = inner2
277+
.list
278+
.drain_filter(|_| true)
279+
.filter_map(|wr| {
280+
let wr = ManuallyDrop::new(wr);
281+
282+
if Some(NonNull::from(&**wr)) == inner2.generic_weakref {
283+
inner2.generic_weakref = None
284+
}
285+
286+
// if strong_count == 0 there's some reentrancy going on
287+
(wr.as_object().strong_count() > 0).then(|| (*wr).clone())
288+
})
289+
.take(16);
290+
v.extend(iter);
291+
if v.is_empty() {
292+
break;
293+
}
294+
for wr in v.drain(..) {
295+
let cb = unsafe { wr.callback.get().replace(None) };
296+
if let Some(cb) = cb {
297+
callbacks.push((wr, cb));
298+
}
299+
}
300+
}
301+
callbacks
302+
}
303+
304+
fn clear_inner(&self, call_callbacks: bool, decrement_ref_count: bool) {
247305
let to_dealloc = {
248306
let ptr = match self.inner.get() {
249307
Some(ptr) => ptr,
250308
None => return,
251309
};
252310
let mut inner = unsafe { ptr.as_ref().lock() };
311+
312+
// If already cleared (obj is None), skip the ref_count decrement
313+
// to avoid double decrement when called by both GC and drop_slow_inner
314+
let already_cleared = inner.obj.is_none();
253315
inner.obj = None;
316+
254317
// TODO: can be an arrayvec
255318
let mut v = Vec::with_capacity(16);
256319
loop {
@@ -278,20 +341,33 @@ impl WeakRefList {
278341
if v.is_empty() {
279342
break;
280343
}
281-
PyMutexGuard::unlocked(&mut inner, || {
282-
for wr in v.drain(..) {
283-
let cb = unsafe { wr.callback.get().replace(None) };
284-
if let Some(cb) = cb {
285-
crate::vm::thread::with_vm(&cb, |vm| {
286-
// TODO: handle unraisable exception
287-
let _ = cb.call((wr.clone(),), vm);
288-
});
344+
if call_callbacks {
345+
PyMutexGuard::unlocked(&mut inner, || {
346+
for wr in v.drain(..) {
347+
let cb = unsafe { wr.callback.get().replace(None) };
348+
if let Some(cb) = cb {
349+
crate::vm::thread::with_vm(&cb, |vm| {
350+
// TODO: handle unraisable exception
351+
let _ = cb.call((wr.clone(),), vm);
352+
});
353+
}
289354
}
355+
})
356+
} else {
357+
// Just drain without calling callbacks
358+
for wr in v.drain(..) {
359+
let _ = unsafe { wr.callback.get().replace(None) };
290360
}
291-
})
361+
}
362+
}
363+
364+
// Only decrement ref_count if requested AND not already cleared
365+
if decrement_ref_count && !already_cleared {
366+
inner.ref_count -= 1;
367+
(inner.ref_count == 0).then_some(ptr)
368+
} else {
369+
None
292370
}
293-
inner.ref_count -= 1;
294-
(inner.ref_count == 0).then_some(ptr)
295371
};
296372
if let Some(ptr) = to_dealloc {
297373
unsafe { Self::dealloc(ptr) }
@@ -835,15 +911,34 @@ impl PyObject {
835911
slot_del: fn(&PyObject, &VirtualMachine) -> PyResult<()>,
836912
) -> Result<(), ()> {
837913
let ret = crate::vm::thread::with_vm(zelf, |vm| {
914+
// Note: inc() from 0 does a double increment (0→2) for thread safety.
915+
// This gives us "permission" to decrement twice.
838916
zelf.0.ref_count.inc();
917+
let after_inc = zelf.strong_count(); // Should be 2
918+
839919
if let Err(e) = slot_del(zelf, vm) {
840920
let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
841921
vm.run_unraisable(e, None, del_method);
842922
}
923+
924+
let after_del = zelf.strong_count();
925+
926+
// First decrement
927+
zelf.0.ref_count.dec();
928+
929+
// Check for resurrection: if ref_count increased beyond our expected 2,
930+
// then __del__ created new references (resurrection occurred).
931+
if after_del > after_inc {
932+
// Resurrected - don't do second decrement, leave object alive
933+
return false;
934+
}
935+
936+
// No resurrection - do second decrement to get back to 0
937+
// This matches the double increment from inc()
843938
zelf.0.ref_count.dec()
844939
});
845940
match ret {
846-
// the decref right above set ref_count back to 0
941+
// the decref set ref_count back to 0
847942
Some(true) => Ok(()),
848943
// we've been resurrected by __del__
849944
Some(false) => Err(()),

0 commit comments

Comments
 (0)