@@ -28,12 +28,23 @@ bitflags::bitflags! {
2828 }
2929}
3030
31+ /// Result from a single collection run
32+ #[ derive( Debug , Default ) ]
33+ pub struct CollectResult {
34+ pub collected : usize ,
35+ pub uncollectable : usize ,
36+ pub candidates : usize ,
37+ pub duration : f64 ,
38+ }
39+
3140/// Statistics for a single generation (gc_generation_stats)
3241#[ derive( Debug , Default ) ]
3342pub struct GcStats {
3443 pub collections : usize ,
3544 pub collected : usize ,
3645 pub uncollectable : usize ,
46+ pub candidates : usize ,
47+ pub duration : f64 ,
3748}
3849
3950/// A single GC generation with intrusive linked list
@@ -55,6 +66,8 @@ impl GcGeneration {
5566 collections : 0 ,
5667 collected : 0 ,
5768 uncollectable : 0 ,
69+ candidates : 0 ,
70+ duration : 0.0 ,
5871 } ) ,
5972 }
6073 }
@@ -77,14 +90,24 @@ impl GcGeneration {
7790 collections : guard. collections ,
7891 collected : guard. collected ,
7992 uncollectable : guard. uncollectable ,
93+ candidates : guard. candidates ,
94+ duration : guard. duration ,
8095 }
8196 }
8297
83- pub fn update_stats ( & self , collected : usize , uncollectable : usize ) {
98+ pub fn update_stats (
99+ & self ,
100+ collected : usize ,
101+ uncollectable : usize ,
102+ candidates : usize ,
103+ duration : f64 ,
104+ ) {
84105 let mut guard = self . stats . lock ( ) ;
85106 guard. collections += 1 ;
86107 guard. collected += collected;
87108 guard. uncollectable += uncollectable;
109+ guard. candidates += candidates;
110+ guard. duration += duration;
88111 }
89112
90113 /// Reset the stats mutex to unlocked state after fork().
@@ -346,25 +369,27 @@ impl GcState {
346369 }
347370
348371 /// Perform garbage collection on the given generation
349- pub fn collect ( & self , generation : usize ) -> ( usize , usize ) {
372+ pub fn collect ( & self , generation : usize ) -> CollectResult {
350373 self . collect_inner ( generation, false )
351374 }
352375
353376 /// Force collection even if GC is disabled (for manual gc.collect() calls)
354- pub fn collect_force ( & self , generation : usize ) -> ( usize , usize ) {
377+ pub fn collect_force ( & self , generation : usize ) -> CollectResult {
355378 self . collect_inner ( generation, true )
356379 }
357380
358- fn collect_inner ( & self , generation : usize , force : bool ) -> ( usize , usize ) {
381+ fn collect_inner ( & self , generation : usize , force : bool ) -> CollectResult {
359382 if !force && !self . is_enabled ( ) {
360- return ( 0 , 0 ) ;
383+ return CollectResult :: default ( ) ;
361384 }
362385
363386 // Try to acquire the collecting lock
364387 let Some ( _guard) = self . collecting . try_lock ( ) else {
365- return ( 0 , 0 ) ;
388+ return CollectResult :: default ( ) ;
366389 } ;
367390
391+ let start_time = std:: time:: Instant :: now ( ) ;
392+
368393 // Memory barrier to ensure visibility of all reference count updates
369394 // from other threads before we start analyzing the object graph.
370395 core:: sync:: atomic:: fence ( Ordering :: SeqCst ) ;
@@ -392,11 +417,21 @@ impl GcState {
392417 }
393418
394419 if collecting. is_empty ( ) {
395- self . generations [ 0 ] . count . store ( 0 , Ordering :: SeqCst ) ;
396- self . generations [ generation] . update_stats ( 0 , 0 ) ;
397- return ( 0 , 0 ) ;
420+ for i in 0 ..=generation {
421+ self . generations [ i] . count . store ( 0 , Ordering :: SeqCst ) ;
422+ }
423+ let duration = start_time. elapsed ( ) . as_secs_f64 ( ) ;
424+ self . generations [ generation] . update_stats ( 0 , 0 , 0 , duration) ;
425+ return CollectResult {
426+ collected : 0 ,
427+ uncollectable : 0 ,
428+ candidates : 0 ,
429+ duration,
430+ } ;
398431 }
399432
433+ let candidates = collecting. len ( ) ;
434+
400435 if debug. contains ( GcDebugFlags :: STATS ) {
401436 eprintln ! (
402437 "gc: collecting {} objects from generations 0..={}" ,
@@ -495,9 +530,17 @@ impl GcState {
495530 if unreachable. is_empty ( ) {
496531 drop ( gen_locks) ;
497532 self . promote_survivors ( generation, & survivor_refs) ;
498- self . generations [ 0 ] . count . store ( 0 , Ordering :: SeqCst ) ;
499- self . generations [ generation] . update_stats ( 0 , 0 ) ;
500- return ( 0 , 0 ) ;
533+ for i in 0 ..=generation {
534+ self . generations [ i] . count . store ( 0 , Ordering :: SeqCst ) ;
535+ }
536+ let duration = start_time. elapsed ( ) . as_secs_f64 ( ) ;
537+ self . generations [ generation] . update_stats ( 0 , 0 , candidates, duration) ;
538+ return CollectResult {
539+ collected : 0 ,
540+ uncollectable : 0 ,
541+ candidates,
542+ duration,
543+ } ;
501544 }
502545
503546 // Release read locks before finalization phase.
@@ -507,9 +550,17 @@ impl GcState {
507550
508551 if unreachable_refs. is_empty ( ) {
509552 self . promote_survivors ( generation, & survivor_refs) ;
510- self . generations [ 0 ] . count . store ( 0 , Ordering :: SeqCst ) ;
511- self . generations [ generation] . update_stats ( 0 , 0 ) ;
512- return ( 0 , 0 ) ;
553+ for i in 0 ..=generation {
554+ self . generations [ i] . count . store ( 0 , Ordering :: SeqCst ) ;
555+ }
556+ let duration = start_time. elapsed ( ) . as_secs_f64 ( ) ;
557+ self . generations [ generation] . update_stats ( 0 , 0 , candidates, duration) ;
558+ return CollectResult {
559+ collected : 0 ,
560+ uncollectable : 0 ,
561+ candidates,
562+ duration,
563+ } ;
513564 }
514565
515566 // 6b: Record initial strong counts (for resurrection detection)
@@ -612,6 +663,16 @@ impl GcState {
612663 // Resurrected objects stay tracked — just drop our references
613664 drop ( resurrected) ;
614665
666+ if debug. contains ( GcDebugFlags :: COLLECTABLE ) {
667+ for obj in & truly_dead {
668+ eprintln ! (
669+ "gc: collectable <{} {:p}>" ,
670+ obj. class( ) . name( ) ,
671+ obj. as_ref( )
672+ ) ;
673+ }
674+ }
675+
615676 if debug. contains ( GcDebugFlags :: SAVEALL ) {
616677 let mut garbage_guard = self . garbage . lock ( ) ;
617678 for obj_ref in truly_dead. iter ( ) {
@@ -633,12 +694,20 @@ impl GcState {
633694 } ) ;
634695 }
635696
636- // Reset gen0 count
637- self . generations [ 0 ] . count . store ( 0 , Ordering :: SeqCst ) ;
697+ // Reset counts for all collected generations
698+ for i in 0 ..=generation {
699+ self . generations [ i] . count . store ( 0 , Ordering :: SeqCst ) ;
700+ }
638701
639- self . generations [ generation] . update_stats ( collected, 0 ) ;
702+ let duration = start_time. elapsed ( ) . as_secs_f64 ( ) ;
703+ self . generations [ generation] . update_stats ( collected, 0 , candidates, duration) ;
640704
641- ( collected, 0 )
705+ CollectResult {
706+ collected,
707+ uncollectable : 0 ,
708+ candidates,
709+ duration,
710+ }
642711 }
643712
644713 /// Promote surviving objects to the next generation.
0 commit comments