VM lifecycle: suspend, resume, idle timer, suspend cap, eight-hour cap - #35
Merged
Merged
Conversation
lex00
added a commit
that referenced
this pull request
Jul 31, 2026
main gained the mkdocs site and the strict docs gate (#27) and the floci architecture findings (#28) after this branch was cut, and the two sides had both reformatted 60-connectors.json. The conflict is a genuine union rather than a pick. main added the subset:floci tag, correcting which cases the floci subset covers. This branch added the live-recorded request members the connector create and update actually need — AssociatedComputeResourceTypes, NetworkProtocol, ClientToken and OperatorRole, with the operatorRoleArn param behind them. Both are kept; dropping either would lose a real finding. This also unblocks CI on the PR. A conflicting pull request has no mergeable ref for pull_request workflows to run against, which is why #34 showed no checks at all while #35 and #36 were green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6T4MeDN1RBiWaNtud6x77
SuspendMicrovm walks RUNNING to SUSPENDED through SUSPENDING on the clock. ResumeMicrovm goes SUSPENDED straight back to RUNNING with no state between, per the recorded asymmetry: the same five-second poll that caught PENDING on the initial launch saw nothing at all on the way back, and the enum has no RESUMING to occupy anyway. Three timers bound a VM's life. maxIdleDurationSeconds without endpoint traffic moves RUNNING to SUSPENDING; suspendedDurationSeconds in SUSPENDED reclaims the VM; eight hours ends the session whatever state it is in. clock.Clock has no cancel, by design, so each VM carries a generation counter bumped on every state change: a timer captures it when armed and does nothing if the VM has moved on. That is what makes a resume during SUSPENDING safe, and what stops the first suspension's cap from reclaiming a VM that has since suspended a second time. Traffic resets use the same shape — the idle timer fires, finds activity newer than it expected, and re-arms for the remainder. The state marker is a monotonic counter that Touch bumps and nothing resets, so #12's endpoint stub can serve it back across a suspend and resume to prove the VM was not rebuilt. It is deliberately absent from the GetMicrovm body: it is m80's own instrumentation, and putting it on a modeled response would be an invented member on the wire. A test pins that. Also serializes VM state. Transitions run on clock callbacks, which under clock.Real are separate goroutines, while handlers read the same fields to build a response. Every test used clock.Test, whose callbacks run on the test goroutine, so -race could never see the collision; probing the unmodified tree under -race shows it plainly, Run's transition writing vm.State against detail reading it. Handlers now render a snapshot taken under the service mutex, and TestTransitionsAndHandlersDoNotRace runs the real clock so the detector has something to look at. #11 adds three more timers per VM, so this could not wait. Suspending an already suspended VM, or one on its way to TERMINATED, is a no-op answered 200; PENDING is allowed through. None of that was recorded, and idempotence is the safer guess than an invented error for a reconciler that may re-issue the call. The one recorded case is pinned: any mutation of a TERMINATED VM is a plain 400 ValidationException, neither modeled conflict type, which is what case 82 asserts. Conformance against a fresh m80: 47 pass, 0 fail, up from 43 on the parent branch. Coverage stays at 22 of 29 operations exercised, because ResumeMicrovm is still never sent — auth-token-while-suspended is #12's operation and sits between suspend and resume in case 40, so its 501 halts the case first. The unit tests cover the transition meanwhile. Closes #11 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6T4MeDN1RBiWaNtud6x77
A failing step halts its scenario because the state the later steps assume is no longer trustworthy. An unimplemented one halted it too, which is wrong for a step nothing downstream depends on. CreateMicrovmAuthToken sits between suspend and resume in vm-suspend-resume for a good reason — that is the only place a live recording can observe a token issued against a suspended VM — but while it was unimplemented it took ResumeMicrovm off the coverage report with it, and there was nothing wrong with ResumeMicrovm. #11's acceptance asks that the resume transition match the recorded fixture, and the suite had no way to show it. "optional": true exempts a step from halting the scenario when the target answers 501, and nothing else. A step that genuinely fails still halts however it is marked. A step carrying capture should not be optional: the vars it would have set go missing and the failure resurfaces several steps later, a long way from its cause. Both halves are pinned by tests. vm-suspend-resume now runs end to end. resume and get-until-running-again pass against their recorded fixtures, which is the first time the suite has checked the transition rather than the unit tests alone. 47 pass becomes 52, and 22 of 29 operations exercised becomes 23. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6T4MeDN1RBiWaNtud6x77
lex00
force-pushed
the
feature/11-suspend-resume
branch
from
July 31, 2026 04:52
6802911 to
f2076c2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #34 — base is
feature/6-recording, so review that one first.SuspendMicrovmwalks RUNNING to SUSPENDED through SUSPENDING on the clock.ResumeMicrovmgoes SUSPENDED straight back to RUNNING with no state between, per the recorded asymmetry: the same five-second poll that caught PENDING on the initial launch saw nothing at all on the way back, and the enum has no RESUMING to occupy anyway.The generation counter
clock.Clockhas no cancel, by design. Each VM carries a counter bumped on every state change; a timer captures it when armed and does nothing if the VM has moved on. That is what makes a resume during SUSPENDING safe — the in-flight transition finds a changed generation and declines to land — and what stops the first suspension's cap from reclaiming a VM that has since suspended a second time. Traffic resets reuse the shape: the idle timer fires, finds activity newer than it expected, and re-arms for the remainder.The three timers:
maxIdleDurationSecondswithout endpoint traffic moves RUNNING to SUSPENDING,suspendedDurationSecondsin SUSPENDED reclaims the VM, and eight hours ends the session whatever state it is in.The state marker
A monotonic counter
Touchbumps and nothing resets, so #12's endpoint stub can serve it back across a suspend and resume to prove the VM was not rebuilt. Deliberately absent from theGetMicrovmbody — it is m80's own instrumentation, and putting it on a modeled response would be an invented member on the wire.TestMarkerIsNotOnTheWirepins that.Scope note: VM state is now serialized
Transitions run on clock callbacks, which under
clock.Realare separate goroutines, while handlers read the same fields to build a response. Every test usedclock.Test, whose callbacks run on the test goroutine, so-racecould never see the collision. Probing the unmodified tree under-raceshows it plainly:Handlers now render a snapshot taken under the service mutex, and
TestTransitionsAndHandlersDoNotRaceruns the real clock so the detector has something to look at. #11 adds three more timers per VM, so this could not wait.Unrecorded behavior, and what I guessed
Suspending an already suspended VM, or one on its way to TERMINATED, is a no-op answered 200; PENDING is allowed through. None of that was recorded. Idempotence is the safer guess than an invented error for a reconciler that may re-issue the call. The one recorded case is pinned: any mutation of a TERMINATED VM is a plain 400 ValidationException, neither modeled conflict type, which is what case 82 asserts.
Cap-driven termination reports
stateReason: "Success.", same as an explicit one. The service's wording for those paths was never recorded and a different string would be invention.Optional steps
ResumeMicrovmwas never sent by the suite at all:auth-token-while-suspendedis #12's operation and sits between suspend and resume in case 40, so its 501 halted the case first. #11's acceptance asks that the resume transition match the recorded fixture, and there was no way to show it."optional": trueon a step exempts it from halting its scenario when the target answers 501, and nothing else — a step that genuinely fails still halts however it is marked, and a step carryingcaptureshould never be marked. Both halves are pinned by tests.vm-suspend-resumenow runs end to end, andresumeandget-until-running-againpass against their recorded fixtures.Result
52 pass, 0 fail against a fresh m80, up from 43 on the parent. 23/29 operations exercised, up from 22.
go build,go vet,gofmtandgo test -raceclean.Closes #11