Skip to content

Commit f5d0c6d

Browse files
committed
refactor(mem): libttak hint 기반 adaptive reclamation 도입 및 누수 방지
- libttak를 hint-driven adaptive reclamation 버전으로 업데이트 (31f2a09f: mem_tree/epoch_gc hint API, CLOCK_MONOTONIC, lazy thread) - memory_manager가 alloc/free/realloc/collect 시 epoch_gc에 hint 제공 → CPU를 갈구던 고정 tick 제거, 우아한 backoff/리셋 - context당 background thread 2개 → 1개 축소 (epoch_gc rotate thread가 mem_tree cleanup도 직접 주도) - sshc_alloc_map 해제를 ttak_destroy_map()으로 수정하여 SoA 배열 누수 방지 - global GC tuning: 5ms/250ms → 100ms/5s 완화
1 parent daab60c commit f5d0c6d

2 files changed

Lines changed: 20 additions & 18 deletions

File tree

src/memory_manager.c

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

157155
static 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

711711
void 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

Comments
 (0)