Skip to content

fix(chatv3): stop and join the standing ticker on process close - #1256

Closed
santoshkumarradha wants to merge 2 commits into
santos/devfrom
task/c324-standing-ticker-pr
Closed

santoshkumarradha wants to merge 2 commits into
santos/devfrom
task/c324-standing-ticker-pr

Conversation

@santoshkumarradha

Copy link
Copy Markdown
Member

What

startStandingTicks launched a process-global endless ticker via a package sync.Once, and closeAll never stopped or joined it. After Close returned, the next pass created files under <home>/v3/standing (and appended standing.log), a writer outliving the process that left <home>/v3 non-empty (the <tmp>/v3 directory not empty leftover class).

Fix

The ticker is owned by v3Process: stop and done channels taken under p.mu, a select loop that exits on stop, and closeAll calls stopStandingTicks (closes stop, waits on done). The package sync.Once is removed, so a later process in the same test binary starts its own ticker. Behavior preserved: same interval, silence, at-most-once per process, ErrHeld silence.

Interaction with #1250's settle-at-stop path (asked at review)

No adverse interaction. closeAll calls stopStandingTicks AFTER stopPoolErrands and BEFORE the agent-close loop where #1250's settle path runs. stopStandingTicks takes p.mu only briefly to lift the stop/done channels, then waits on <-done OUTSIDE the lock, so it neither runs concurrently with nor shares a lock with the main-agent settle path; the standing pass builds a separate standing session, not one of p.agents. Worst case, closeAll waits one bounded standing TickWindow for an in-flight pass, which is the intended no-writer-outlives-close guarantee.

Verification (Spark, off eae7055, all via scripts/one-suite.sh for the heavy suite)

  • Focused new test TestCloseAllJoinsStandingTickerAndLaterProcessCanStart -count=8: pass (zero writes under <home>/v3/standing after Close; second process starts its ticker).
  • Whole cmd/codeaf + internal/standing -count=5 under the one-suite lock: ok (459s, 0 FAIL).
  • go build ./...; gofmt -l ./cmd ./internal (empty); go run ./cmd/codeaf-changes check; go test ./internal/guard ./internal/namelaw (ok). No em dash.

Flake note

One earlier cmd/codeaf failure occurred on the prior base via a direct go test at load ~26 (not through one-suite); its goroutine dump pointed at chat.go:4622, the design-intentional detached worker-leaf flush goroutine, not this change's standing/close path. It did not recur across 5 one-suite runs on eae7055, and this change's own close-path tests pass 5x.

startStandingTicks launched a process-global endless ticker through a package
sync.Once, and closeAll never stopped or joined it. After Close returned the
ticker was still armed, so the next pass created lock, item, run and log files
under <home>/v3/standing (and appended standing.log), a writer that outlived
the process and left <home>/v3 non-empty for a caller trying to remove it.

The ticker is now owned by the v3Process: a stop and a done channel taken
under p.mu, a select loop that exits on stop, and closeAll calls
stopStandingTicks, which closes stop and waits on done so an in-flight pass
finishes before Close returns. The package sync.Once is gone, so a second
process opened later in the same test binary starts its own ticker; the old
standingTicks gauge is cleared on stop.

A real-close-path test opens a process, lets it tick, closes it, removes the
standing root, waits several intervals, and asserts nothing was recreated,
then shows a second process can start and stop its own ticker.
…it out

closeAll joined the ticker by waiting on done, but the pass in flight ran
under context.WithTimeout(context.Background(), TickWindow), which stop did
not cancel, so a person who quit while a pass was out could wait up to the
120s window. Quitting must not scale with background work.

The ticker now holds one context that stop cancels: runStandingTick derives
the pass ceiling from it (a child of that context and the 120s window), and a
guarded watcher cancels it when stop closes, so an in-flight pass ends at
once and the join is prompt. The 120s stays the pass's own upper bound when
nobody is quitting.

A cancelled pass leaves no half-written file: standing writes are temp+rename
or single short appends, and Tick checks ctx between steps, so it stops at the
next boundary having only completed atomic writes.

A failing-first test holds a pass in flight, closes, and asserts Close returns
promptly and the pass saw its ctx cancelled; ordering is by channels with a
generous sanity bound. The zero-writes-after-close test stays.
@santoshkumarradha

Copy link
Copy Markdown
Member Author

Landed on santos/dev as 7ab8820, inside a two-PR stack whose top is 7cefdf1 (tree 020ebd23a). Full make check on Spark on that exact commit: EXIT 0, 114 packages ok, first run on a quiet box, all six shipped targets cross-built in 66 s, log ~/src/trees/check-sdev-7cefdf1f4.log.An earlier run on this PR alone (7ab8820) failed one test in cmd/codeaf, TestAConversationThatFailsHalfOpenClosesItsAgent, with a temp directory not empty at cleanup, under a box load of about 30; that test opens a conversation through the seam and never reaches the launch road, which is the only caller of the ticker start, and the new stop returns at once when no ticker was started, so the failure is outside this change. Its log is kept as check-sdev-7ab882003.run1-flake.log. Squashed by hand, so GitHub shows this as closed, not merged; the branch is level with dev 9258f70.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants