Skip to content

fix(kernel/goals): close goal-run self-cleanup race + termination test coverage (follow-up #5840)#5848

Merged
houko merged 1 commit into
mainfrom
fix/goal-runner-followup
May 29, 2026
Merged

fix(kernel/goals): close goal-run self-cleanup race + termination test coverage (follow-up #5840)#5848
houko merged 1 commit into
mainfrom
fix/goal-runner-followup

Conversation

@houko

@houko houko commented May 29, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #5840 (autonomous goal runner), addressing the three items from review.

1. Goal-run self-cleanup race (correctness)

The spawned loop's trailing runs.remove(&goal_id) ran unconditionally when run_loop completed naturally. If a concurrent start() for the same goal had already replaced the run (aborting the old task and inserting a new handle), the old task — finishing naturally at nearly the same instant — would remove the new run's handle, orphaning a live loop: unstoppable via stop(), invisible to state(), and only self-terminating at the iteration cap.

Fix: each run is tagged with a monotonic generation (AtomicU64 on the runner), and the task self-cleans via runs.remove_if(&goal_id, |_, h| h.generation == generation) so it only ever drops its own entry. Abort was already safe (an aborted future never reaches the cleanup); this closes the natural-completion case.

2. Remove dead is_running

pub fn is_running had no callers (routes use state()); removed.

3. Termination test coverage

#5840 shipped tests only for GOAL_DONE and max_iterations. Added run_loop tests for the paths that matter most for an unattended loop:

  • operator stop flag (no tick runs)
  • kernel shutdown signal (no tick runs)
  • GOAL_BLOCKED (ends as Stopped, goal not marked completed)
  • consecutive-rate-limit circuit breaker (start_paused skips the inter-tick sleeps, asserts it trips before the iteration cap)

Verification

  • cargo clippy -p librefang-kernel --lib -- -D warnings — clean (dev container)
  • cargo test -p librefang-kernel --lib goal_runner::tests — 7 passed (4 new + 3 existing)

…ning; cover termination paths

Follow-up to #5840.

Self-cleanup race: the spawned loop's trailing runs.remove(goal_id) ran unconditionally on natural completion.
If a concurrent start() had already replaced the run, that remove deleted the NEW run's handle, orphaning a live loop (unstoppable and invisible until it self-terminated at the iteration cap).
Tag each run with a monotonic generation and self-clean via remove_if so a task only drops its own entry.

Remove the dead pub is_running (no callers; routes use state()).

Add run_loop termination tests for the paths #5840 shipped without coverage: operator stop flag, kernel shutdown signal, GOAL_BLOCKED, and the consecutive-rate-limit circuit breaker (start_paused to skip tick sleeps).
@houko
houko merged commit 8bbc311 into main May 29, 2026
17 checks passed
@houko
houko deleted the fix/goal-runner-followup branch May 29, 2026 00:18
@github-actions github-actions Bot added area/kernel Core kernel (scheduling, RBAC, workflows) size/M 50-249 lines changed labels May 29, 2026
houko added a commit that referenced this pull request May 29, 2026
#5853)

rustfmt flags several blocks merged via #5835, #5839, #5848, and #5849
(method chains and multi-line statements rustfmt collapses/reflows). main
was therefore red on the Quality "Check formatting" step, which fails the
Quality job on every open PR. Pure `cargo fmt` output — no logic change.

Co-authored-by: Evan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kernel Core kernel (scheduling, RBAC, workflows) size/M 50-249 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant