@@ -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