Skip to content

Confirm conntrack state before auto-standby - #319

Open
sjmiller609 wants to merge 6 commits into
mainfrom
hypeship/autostandby-confirm-conntrack-before-standby
Open

Confirm conntrack state before auto-standby#319
sjmiller609 wants to merge 6 commits into
mainfrom
hypeship/autostandby-confirm-conntrack-before-standby

Conversation

@sjmiller609

@sjmiller609 sjmiller609 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

The auto-standby idle countdown is driven entirely by the conntrack event stream. state.activeInbound is built from NEW/DESTROY events, and once it drains the countdown timer arms — after that nothing re-reads the conntrack table before the timer fires. So a single dropped NEW event (receive buffer overrun, or the reconnect gap after a stream error, where the restored subscription previously did no resync) is enough to standby an instance that still has a live connection. The only correction was periodicSnapshotSync at 5 minutes, which never runs in time for a shorter idle_timeout.

Two changes, both in lib/autostandby/controller.go:

  • handleStandbyTimer now dumps the conntrack table and classifies it with the instance's policy before committing to standby. If matching inbound connections are still present, it repopulates activeInbound, clears idleSince, arms the reconcile timer, and skips the attempt. If the dump or classification fails, the instance is treated as busy and the countdown restarts rather than risking standby on an unverified instance. One dump per standby attempt, on the same event-loop goroutine that already dumps every 2s via handleActiveReconcile.
  • The streamReady case in Run calls startupResync after a subscription is restored, so state is rebuilt from the table immediately instead of staying stale for up to a full snapshot interval.

Tests

go test ./lib/autostandby/ -race passes, including three new cases: standby skipped when conntrack reports a connection the event stream missed, countdown re-armed when the confirmation dump fails, and a Run-level test that fails the stream and asserts the instance flips back to active after the reconnect resync.

./lib/instances and ./cmd/api/api do not build in this checkout (missing embedded cloud-hypervisor / caddy / guest-agent binaries, unrelated to this change), so their auto-standby tests were not run.


Note

Medium Risk
Changes when VMs enter standby (snapshot IO) based on authoritative conntrack reads; behavior is conservative on errors but affects production instance lifecycle timing.

Overview
Fixes a case where auto-standby could snapshot a VM that still had live inbound traffic when conntrack NEW events were missed (stream gaps or buffer overruns) and idle state was driven only by the event stream.

Before standby, handleStandbyTimer now runs confirmIdleBeforeStandby: a conntrack table dump and policy match. If connections remain, it restores active state and skips standby; if the dump fails, it restarts the idle countdown instead of standing by on unverified idle. Deadline checks are factored into standbyDeadlineElapsedLocked, with a second pass after confirmation for holds/re-arms that happen while the lock was released.

On conntrack subscription restore, Run calls startupResync before marking the observer connected, so state is reseeded from the table immediately rather than staying stale until periodic sync; observer “connected” is set after resync so a failed dump does not look like a dead subscription.

Tests cover missed events, failed confirmation, races (hold/re-arm during confirmation), and Run-level stream restore behavior.

Reviewed by Cursor Bugbot for commit 571b961. Bugbot is set up for automated code reviews on this repo. Configure here.

The idle countdown is driven entirely by the conntrack event stream, so a
dropped NEW event (receive buffer overrun, or the reconnect gap after a
stream error) leaves activeInbound empty while the guest still holds a live
connection. Nothing re-read the conntrack table between the countdown
starting and the timer firing, so an instance could enter standby with a
connection open; the only correction was the 5 minute snapshot sync, which
never runs in time for a shorter idle timeout.

Dump the conntrack table in handleStandbyTimer and abort the attempt when
the instance still has matching inbound connections, treating a failed dump
as busy. Also resync on stream restore so state does not stay stale for a
full snapshot interval after a reconnect.
@sjmiller609
sjmiller609 marked this pull request as ready for review July 28, 2026 15:44
Comment thread lib/autostandby/controller.go
Comment thread lib/autostandby/controller.go
The confirmation added in the previous commit changed in-memory state without
writing it back, so a restart could resume a stale idle_since and standby with
no fresh countdown. Persist the runtime on both confirmation outcomes.

Mark the observer connected after the post-restore resync instead of before:
a failed conntrack dump inside the resync records an observer error, which
otherwise left a live subscription reporting StatusError until the next
reconnect.
Comment thread lib/autostandby/controller.go
Activity can be processed between the idle timer firing and the confirmation
running, and it already owns idleSince and the reconcile loop. Bail out in that
case instead of overwriting the active state with a fresh idle countdown when
the conntrack dump fails.
Comment thread lib/autostandby/controller.go
The confirmation dump releases the lock, so a standby worker on its failure
path can arm a replacement idle timer in that window. Assigning state.timer =
nil orphaned that AfterFunc, leaving a stray firing queued behind the standby
that was about to run. Cancel the timer instead, which stops it and clears
nextStandbyAt the same way.
@sjmiller609
sjmiller609 requested a review from hiroTamada July 28, 2026 19:01
…standby

Conflict in handleStandbyTimer: main added a stale-delivery check that re-arms
when the current deadline has not elapsed, and this branch replaced the timer
teardown with cancelTimerLocked. Kept both, with the stale check ahead of the
conntrack confirmation so a held or re-armed instance is not dumped or given a
restarted countdown, and the confirmation only runs once the deadline is real.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5816ec3. Configure here.

Comment thread lib/autostandby/controller.go
Comment thread lib/autostandby/controller.go
The confirmation releases the lock, so a hold placed while it runs extends the
deadline without the standby path noticing, putting the instance into standby
in spite of the hold. Check the deadline on both sides of the confirmation via
standbyDeadlineElapsedLocked, which also replaces the spent timer.
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