--- id: "bug-1483" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "controller" severity: "leak" timestamp: "2026-07-29T10:20:00Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: ["src/controller/ha.c (ha_arm_heartbeat_timeout_vtask", "~8805-8848)"] tags: ["ha.c", "ha_arm_heartbeat_timeout_vtask", "s_liveness_ctx", "test-mode-leak", "c-is-null-path", "raft-adapter-conformance", "task-3.2", "found-not-yet-fixed", "pre-existing", "prevote-lost-path"] related: [] legacy: true --- # bug-1483: ha_arm_heartbeat_timeout_vtask (ha.c ~8805-8848), when called with c==NULL (unit-test mode driving… ## Description ha_arm_heartbeat_timeout_vtask (ha.c ~8805-8848), when called with c==NULL (unit-test mode driving a PreVote round to LOST without stubbing ha_prevote_rearm_liveness_hook), callocs a liveness_poll_ctx_t and stores it into the static s_liveness_ctx, but the guard skips scheduling anything to poll or free it -- the context leaks/dangles permanently in that code path. ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause Test-mode (c==NULL) entry into a production helper allocates state but has no corresponding free/cleanup path when the delaymgr-scheduling guard short-circuits -- a latent leak specific to the c==NULL unit-test invocation shape, not exercised by any live production path (a real cluster_t is never NULL there). ## Fix FIXED (2026-08-01). ha_arm_heartbeat_timeout_vtask (ha.c) published a calloc'd liveness_poll_ctx_t into the static s_liveness_ctx unconditionally, but only freed it via liveness_poll_fn's cancelled-check path -- which never runs if delay_task_ms was never called (c==NULL, delaymgr not running, or delay_task_ms itself failing). Fixed by clearing s_liveness_ctx and freeing ctx immediately in all three no-schedule cases (the pre-existing c==NULL/!running else-branch, plus the previously-unhandled delay_task_ms rc<0 branch, which had the identical defect shape and was found while fixing this). Establishes the invariant: s_liveness_ctx is non-NULL only when a delaymgr task actually holds a live reference to it. Regression test: test_bug1483_lost_round_without_hook_does_not_leak_liveness_ctx (test_ha_prevote.c) drives a real LOST PreVote round WITHOUT stubbing ha_prevote_rearm_liveness_hook (every other LOST-path test in that file stubs it specifically to avoid this code path) and asserts the new liveness_ctx_get_for_test() accessor returns NULL afterward. Full suite: 1071 Tests 0 Failures (verified via the direct-binary recipe, ASAN_OPTIONS=detect_leaks=0). ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._