@@ -146,12 +146,10 @@ static void sshc_memory_context_init(sshc_memory_context_t *ctx,
146146 /* EpochGC: per-context generational collector (local gc init<->destroy cycle). */
147147 ttak_epoch_gc_init (& ctx -> epoch_gc );
148148
149- /* Reclamation: slightly tighter cadence to keep retired generations short. */
150- ttak_mem_tree_set_manual_cleanup (& ctx -> epoch_gc .tree , false);
151- ttak_mem_tree_set_cleaning_intervals (& ctx -> epoch_gc .tree ,
152- TT_MILLI_SECOND (5 ),
153- TT_MILLI_SECOND (100 ));
154-
149+ /* EpochGC: per-context generational collector.
150+ * The rotate thread drives cleanup manually; we do NOT enable the
151+ * mem_tree's own background thread here to avoid spawning two
152+ * threads per context. */
155153}
156154
157155static sshc_memory_context_t * sshc_memory_context_global (void )
@@ -167,11 +165,12 @@ void sshc_memory_runtime_init(void)
167165 GC_set_free_space_divisor (10 );
168166 GC_init ();
169167#endif
170- /* Global TTAK tuning: slightly denser cleanup cadence and lower
171- * pressure threshold to reduce deferred-epoch buildup. */
168+ /* Global TTAK tuning: graceful cadence with generous backoff.
169+ * The memory manager now provides hints (alloc/free/realloc)
170+ * so the background threads do not have to poll aggressively. */
172171 ttak_mem_set_trace (
173172 sshc_env_truthy (getenv ("SSH_CHATTER_MEM_TRACE" )) ? 1 : 0 );
174- ttak_mem_configure_gc (TT_MILLI_SECOND (5 ), TT_MILLI_SECOND ( 250 ), 6 );
173+ ttak_mem_configure_gc (TT_MILLI_SECOND (100 ), TT_SECOND ( 5 ), 64 );
175174
176175 /* Hash map for O(1) ptr → allocation* lookup (initial capacity 1024). */
177176 sshc_alloc_map = ttak_create_map (1024 , ttak_get_tick_count ());
@@ -271,9 +270,9 @@ void sshc_memory_runtime_shutdown(void)
271270 if (sshc_global_context .owner ) ttak_owner_destroy (sshc_global_context .owner );
272271 pthread_mutex_destroy (& sshc_global_context .mutex );
273272
274- /* Release the allocation hash map (free SoA arrays then the struct). */
273+ /* Release the allocation hash map properly ( SoA arrays + struct). */
275274 if (sshc_alloc_map != nullptr ) {
276- ttak_mem_free (sshc_alloc_map );
275+ ttak_destroy_map (sshc_alloc_map );
277276 sshc_alloc_map = nullptr ;
278277 }
279278
@@ -295,12 +294,8 @@ sshc_memory_context_t *sshc_memory_context_create(const char *label)
295294 }
296295 sshc_memory_context_init (ctx , label );
297296
298- /* Session context: keep generations shorter under reconnect churn. */
299- ttak_mem_tree_set_manual_cleanup (& ctx -> epoch_gc .tree , false);
300- ttak_mem_tree_set_cleaning_intervals (& ctx -> epoch_gc .tree ,
301- TT_MILLI_SECOND (5 ),
302- TT_MILLI_SECOND (100 ));
303- ttak_mem_tree_set_pressure_threshold (& ctx -> epoch_gc .tree , 3 );
297+ /* Session context: the epoch GC rotate thread handles cleanup.
298+ * Do NOT enable the mem_tree's own auto-cleanup thread here. */
304299
305300 /* Vertical Hierarchy: Register this session owner as a child of the global owner.
306301 * This ensures that if the global context is destroyed, all session contexts are audited. */
@@ -460,6 +455,7 @@ void *sshc_gc_malloc(size_t size)
460455 SSH_CHATTER_DEFAULT_LIFETIME ,
461456 ttak_get_tick_count ());
462457 if (ptr == nullptr ) return nullptr ;
458+ ttak_epoch_gc_hint (& ctx -> epoch_gc , TTAK_EPOCH_GC_HINT_ALLOC );
463459
464460 sshc_memory_allocation_t * allocation =
465461 (sshc_memory_allocation_t * )ttak_mem_alloc (
@@ -555,6 +551,7 @@ void *sshc_gc_realloc(void *ptr, size_t size)
555551 } else {
556552 ttak_mem_free (ptr );
557553 }
554+ ttak_epoch_gc_hint (& old_allocation -> context -> epoch_gc , TTAK_EPOCH_GC_HINT_REALLOC );
558555 }
559556
560557 sshc_memory_allocation_t * allocation = old_allocation ;
@@ -590,6 +587,7 @@ void *sshc_gc_realloc(void *ptr, size_t size)
590587
591588 sshc_memory_context_register_allocation (allocation_ctx , allocation );
592589 sshc_memory_registry_add (allocation );
590+ ttak_epoch_gc_hint (& allocation_ctx -> epoch_gc , TTAK_EPOCH_GC_HINT_ALLOC );
593591 return new_ptr ;
594592}
595593
@@ -669,6 +667,7 @@ void sshc_gc_free(void *ptr)
669667 ttak_mem_free (ptr );
670668 }
671669 ttak_mem_free (allocation );
670+ ttak_epoch_gc_hint (& allocation -> context -> epoch_gc , TTAK_EPOCH_GC_HINT_FREE );
672671 } else {
673672 /* Not tracked – direct free. */
674673 ttak_mem_free (ptr );
@@ -705,12 +704,14 @@ void sshc_memory_context_reset(sshc_memory_context_t *ctx)
705704 }
706705
707706 /* Force a rotation to flush released nodes through the cleanup pass. */
707+ ttak_epoch_gc_hint (& ctx -> epoch_gc , TTAK_EPOCH_GC_HINT_COLLECT_NOW );
708708 ttak_epoch_gc_rotate (& ctx -> epoch_gc );
709709}
710710
711711void sshc_memory_context_epoch_gc_rotate (sshc_memory_context_t * ctx )
712712{
713713 if (ctx == nullptr ) return ;
714+ ttak_epoch_gc_hint (& ctx -> epoch_gc , TTAK_EPOCH_GC_HINT_COLLECT_NOW );
714715 ttak_epoch_gc_rotate (& ctx -> epoch_gc );
715716}
716717
@@ -725,6 +726,7 @@ void sshc_memory_context_collect(sshc_memory_context_t *ctx,
725726 rotate_passes = 1U ;
726727 }
727728
729+ ttak_epoch_gc_hint (& ctx -> epoch_gc , TTAK_EPOCH_GC_HINT_COLLECT_NOW );
728730 for (unsigned int pass = 0U ; pass < rotate_passes ; ++ pass ) {
729731 ttak_epoch_gc_rotate (& ctx -> epoch_gc );
730732 ttak_epoch_reclaim ();
0 commit comments