soul: per-tick arena bracketing in awareness_run + hand-patched dist/soul.c
awareness_run's while-loop ran outside any request arena, so every allocation in every 1s tick (search JSON, heartbeat payloads, curiosity activations) was treated as permanent by the runtime — 7.5GB RSS in under a minute. Bracket each iteration with el_arena_push/el_arena_pop (same pattern the compiler emits for scoped blocks; state_set/state_get persist separately via el_strdup_persist and are unaffected). dist/soul.c carries the same change hand-patched at the compiled awareness_run site — elc is currently unsafe to run locally (pathological memory on sessions.el), so the generated C was patched to match the source, verified line-for-line against the compiler's own conventions. MUST be paired with el repo PR #64 (el_strdup_persist for stored engram fields): per-tick arena reclamation widens the write-corruption window without it. Verified together: 5h live soak on the recovered production snapshot, flat RSS, write-field-integrity clean. Note: dist/soul.c still needs a full elc regen to pick up PR #73's source changes (consent tiers) — tracked separately; this patch does not regress that (those changes were never in dist).
This commit is contained in:
@@ -527,9 +527,27 @@ fn awareness_run() -> Void {
|
||||
let scan_ms: Int = beat_ms / 2
|
||||
|
||||
while true {
|
||||
// Arena-scope each tick: awareness_run() is a background loop, not an
|
||||
// HTTP request, so nothing ever called el_request_start/el_request_end
|
||||
// for this thread. Per the runtime's own convention (el_runtime.c),
|
||||
// any thread that never enters a request/arena scope is treated as a
|
||||
// one-shot CLI program whose allocations are intentionally permanent —
|
||||
// so every el_strdup/el_strbuf/jb_finish string built during perceive(),
|
||||
// emit_heartbeat(), and proactive_curiosity() (JSON payloads, search
|
||||
// results, string concatenation via +) leaked forever, once per tick.
|
||||
// el_arena_push()/el_arena_pop() are the same builtins the EL compiler
|
||||
// itself uses to scope allocations per function/statement (see
|
||||
// codegen.el's fn_arena_mark / stmt_mark usage) — mirroring that here
|
||||
// reclaims everything allocated in one tick as soon as the tick ends.
|
||||
// Safe: state_set/state_get persist through a separate global table
|
||||
// (el_strdup_persist, outside the arena) — state_get's return value is
|
||||
// only an arena-tracked *copy* of the persisted value, scoped to this
|
||||
// tick's use, which is exactly what should be reclaimed here.
|
||||
let tick_mark: Any = el_arena_push()
|
||||
let running: String = state_get("soul.running")
|
||||
if str_eq(running, "false") {
|
||||
println("[awareness] exiting")
|
||||
el_arena_pop(tick_mark)
|
||||
return ""
|
||||
}
|
||||
let did_work: Bool = one_cycle()
|
||||
@@ -593,6 +611,7 @@ fn awareness_run() -> Void {
|
||||
}
|
||||
|
||||
sleep_ms(tick_ms)
|
||||
el_arena_pop(tick_mark)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user