@@ -136,6 +136,21 @@ impl GcGeneration {
136136#[ derive( Clone , Copy , PartialEq , Eq , Hash ) ]
137137struct GcPtr ( NonNull < PyObject > ) ;
138138
139+ fn clear_weakrefs_and_invoke_callbacks ( objects : & [ PyObjectRef ] ) {
140+ let mut all_callbacks = Vec :: new ( ) ;
141+ for obj_ref in objects {
142+ let callbacks = obj_ref. gc_clear_weakrefs_collect_callbacks ( ) ;
143+ all_callbacks. extend ( callbacks) ;
144+ }
145+ for ( wr, cb) in all_callbacks {
146+ if let Some ( Err ( e) ) = crate :: vm:: thread:: with_vm ( & cb, |vm| cb. call ( ( wr. clone ( ) , ) , vm) ) {
147+ crate :: vm:: thread:: with_vm ( & cb, |vm| {
148+ vm. run_unraisable ( e. clone ( ) , Some ( "weakref callback" . to_owned ( ) ) , cb. clone ( ) ) ;
149+ } ) ;
150+ }
151+ }
152+ }
153+
139154/// Global GC state
140155pub struct GcState {
141156 /// 3 generations (0 = youngest, 2 = oldest)
@@ -398,6 +413,11 @@ impl GcState {
398413 _ => std:: time:: Instant :: now( ) ,
399414 } ;
400415
416+ // Keep a CDPT guard during collection, following origin/gc's
417+ // coarse-grained guarded collection experiment.
418+ #[ cfg( not( target_arch = "wasm32" ) ) ]
419+ let _cdpt_guard = cdpt:: pin ( ) ;
420+
401421 // Memory barrier to ensure visibility of all reference count updates
402422 // from other threads before we start analyzing the object graph.
403423 core:: sync:: atomic:: fence ( Ordering :: SeqCst ) ;
@@ -602,20 +622,10 @@ impl GcState {
602622 } )
603623 . collect ( ) ;
604624
605- // 6c: Clear existing weakrefs BEFORE calling __del__
606- let mut all_callbacks: Vec < ( crate :: PyRef < crate :: object:: PyWeak > , crate :: PyObjectRef ) > =
607- Vec :: new ( ) ;
608- for obj_ref in & unreachable_refs {
609- let callbacks = obj_ref. gc_clear_weakrefs_collect_callbacks ( ) ;
610- all_callbacks. extend ( callbacks) ;
611- }
612- for ( wr, cb) in all_callbacks {
613- if let Some ( Err ( e) ) = crate :: vm:: thread:: with_vm ( & cb, |vm| cb. call ( ( wr. clone ( ) , ) , vm) ) {
614- crate :: vm:: thread:: with_vm ( & cb, |vm| {
615- vm. run_unraisable ( e. clone ( ) , Some ( "weakref callback" . to_owned ( ) ) , cb. clone ( ) ) ;
616- } ) ;
617- }
618- }
625+ // 6c: Clear weakrefs that existed before finalizers. This prevents a
626+ // later tp_clear side effect from observing callback-free weakrefs to
627+ // garbage in this generation.
628+ clear_weakrefs_and_invoke_callbacks ( & unreachable_refs) ;
619629
620630 // 6d: Call __del__ on unreachable objects (skip already-finalized).
621631 // try_call_finalizer() internally checks gc_finalized() and sets it,
@@ -624,6 +634,11 @@ impl GcState {
624634 obj_ref. try_call_finalizer ( ) ;
625635 }
626636
637+ // 6e: Clear weakrefs after finalizers, but before tp_clear. Finalizers
638+ // can create weakrefs to other unreachable objects, and those must not
639+ // reveal an object while its clear function is running.
640+ clear_weakrefs_and_invoke_callbacks ( & unreachable_refs) ;
641+
627642 // Detect resurrection
628643 let mut resurrected_set: HashSet < GcPtr > = HashSet :: new ( ) ;
629644 let unreachable_set: HashSet < GcPtr > = unreachable. iter ( ) . copied ( ) . collect ( ) ;
0 commit comments