Skip to content

fix(microvm-worker): suspend once, answer resends during a refusal - #842

Merged
ParidelPooya merged 7 commits into
mainfrom
fix/microvm-worker-suspend-retry-and-duplicates
Oct 26, 2026
Merged

ParidelPooya merged 7 commits into
mainfrom
fix/microvm-worker-suspend-retry-and-duplicates

Conversation

@ParidelPooya

@ParidelPooya ParidelPooya commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR fixes two defects in the MicroVM worker's auto-suspend. A retried SuspendMicrovm could suspend the MicroVM during the next job. A second delivery of a known job got HTTP 503 while the worker refused jobs.

Why

  1. The worker's default lambda-microvms client made up to 3 attempts.
  2. An attempt can suspend the MicroVM and lose its answer in the freeze.
  3. The durable function then resumes the MicroVM. The resume hook ends the worker's refusal, and the next job starts.
  4. botocore then retried. The retry suspended the MicroVM with the job inside. Nothing resumed it, so the job failed at its heartbeat timeout or its timeout.
  5. A live test showed that SuspendMicrovm succeeds on a MicroVM that is already suspending or suspended. So a retry gains nothing, and one attempt is enough.

The second defect:

  1. The durable function sends a job again when it lost the first 202, or when its request step runs again on replay.
  2. The worker answered such a delivery with HTTP 503 while it refused jobs for a suspend or a terminate hook.
  3. After the 503, the durable function resumed the MicroVM, sent the job again, and got 202. So the MicroVM ran again for nothing.

Both defects come before the planned suspend_after option on vm.invoke, which uses the same suspend path. The JavaScript SDK fixes the same defects in aws/aws-durable-execution-sdk-js#1100.

Changes

  • create_microvms_client uses total_max_attempts of 1.
  • suspend_microvm takes a resumed callable. The before-send handler raises SuspendResumedError when it returns True. So a client from create_microvms_client that retries starts no attempt after a resume hook. botocore cannot cancel an attempt in flight, so an attempt that started before the hook still runs to its end.
  • The worker counts resume hooks, and passes the count at the call's start to the handler. SuspendResumedError counts as an unknown outcome. The resume hook has already ended the refusal, so the worker starts no new one.
  • A resume hook during the call logs stopping the suspend call: the MicroVM resumed during it, as the JavaScript worker does.
  • With one attempt, a throttle from the default client now counts as refused for now. So the worker accepts jobs again at once, and tries the call again after at least 30 seconds.
  • The job handler checks for a known callback ID before the suspend and terminate refusals. A second delivery of a finished job gets HTTP 202 in every state. A second delivery of a running job gets HTTP 202, except during a suspend refusal. There it gets HTTP 503, because the running job may freeze, and the 503 makes the durable function check the state and resume the MicroVM. A new job still gets HTTP 503 during the refusal. The run hook already checked for a known job first.
  • README "Auto-suspend" and "Suspend and terminate hooks", and the docstrings, are updated.

Testing

  • New tests: the default client makes one attempt, for an error answer and after a connection error; a client that retries a throttle gets an unknown outcome; the handler starts no attempt after a resume hook, also before the first attempt, and starts the attempt while no hook arrived; the worker sends no attempt after the resume hook; a second delivery of a running or finished job gets 202 during a suspend refusal and after the terminate hook; a resume hook while the client is created stops the first attempt; a suspend hook after a stopped call keeps its refusal; a second resume hook logs the stop line once; a resend of a running job gets 503 during a suspend refusal, and a composed test with the extras request step resumes the MicroVM in that case.
  • hatch run dev-microvm-worker:cov: 473 tests pass, with 100% coverage of worker.py and self_suspend.py. typecheck, hatch fmt --check, and lintcommit.py pass.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

A SuspendMicrovm retry can run after the MicroVM resumed and freeze the
next job. The default client now makes one attempt, and the before-send
handler starts no attempt after a resume hook.
A resend of a known job got 503 while the worker refused jobs. The
durable function then resumed the MicroVM only to get the duplicate
answer. The worker now answers a known callback ID with 202 first.
The 503 note for a resent job said that nothing would suspend the
MicroVM again. The idle time restarts after the refusal, so the worker
does suspend it again. The real cost is a resume of the MicroVM for
nothing.

A resume hook can stop the first SuspendMicrovm attempt. Nothing was
sent then. The classification docs now say that an earlier attempt, if
there was one, may have suspended the MicroVM.

The README now states the cost of one attempt. A first-attempt 5xx or
dropped connection counts as unknown, and the worker refuses jobs for up
to 30 seconds on a MicroVM that did not suspend. It also states why a
resent job whose callback is gone gets 202 after the terminate hook.
The JavaScript worker logs an info line when a resume hook arrives
during its SuspendMicrovm call. This worker logged nothing until the
unknown warning at the end of the call. It now logs the same line, with
the same text and the call's microvmId.

The resume hook decides under the lock and logs after it. Only the
first resume hook during a call logs the line. A resume hook after the
call returned logs nothing, because no attempt is left to stop.

New listener tests cover a resume hook before the first attempt, a
suspend hook after that resume and before the call returns, a second
resume hook during the call, and a resume hook after the call returned.

The README documents the line. It also states that the JavaScript worker
cancels the attempt in flight, while this worker can only stop the next
attempt.
A resend of a running job during a suspend refusal got HTTP 202. The
running job can freeze with the MicroVM. send_job treats 202 as
success, so it did not check the state again and did not resume the
MicroVM. The job then waited for its heartbeat timeout or its timeout.

Such a resend now gets HTTP 503, as a new job does, and starts nothing.
The 503 makes the durable function check the state, resume the MicroVM,
and deliver the job again. A finished job still gets 202 in every
state. A known job still gets 202 after the terminate hook.

A composed test with the request step covers the sequence: a lost 202,
a recheck that sees RUNNING, a suspend hook, and the resend.

@yaythomas yaythomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@ParidelPooya
ParidelPooya marked this pull request as ready for review October 26, 2026 04:36
@ParidelPooya
ParidelPooya deployed to ai-pr-review-runtime October 26, 2026 04:36 — with GitHub Actions Active
@ParidelPooya
ParidelPooya deployed to ai-pr-review-runtime October 26, 2026 04:37 — with GitHub Actions Active
ParidelPooya added a commit to aws/aws-durable-execution-sdk-js that referenced this pull request Oct 26, 2026
…1100)

## Summary

This PR fixes two defects in the MicroVM worker's auto-suspend. A
retried `SuspendMicrovm` could suspend the MicroVM during the next job.
A second delivery of a known job got HTTP 503 while the worker refused
jobs.

## Why

1. The worker's own `SuspendMicrovm` client made up to 3 attempts.
2. An attempt can suspend the MicroVM and lose its answer in the freeze.
3. The durable function then resumes the MicroVM. The `resume` hook ends
the worker's refusal, and the next job starts.
4. The client then retried. The retry suspended the MicroVM with the job
inside. Nothing resumed it, so the job failed at its heartbeat timeout
or its timeout.
5. A live test showed that `SuspendMicrovm` succeeds on a MicroVM that
is already suspending or suspended. So a retry gains nothing, and one
attempt is enough.

The second defect:

1. The durable function sends a job again when it lost the first 202, or
when its request step runs again on replay.
2. The worker answered such a delivery with HTTP 503 while it refused
jobs for a suspend or a `terminate` hook.
3. After the 503, the durable function resumed the MicroVM, sent the job
again, and got 202. So the MicroVM ran again for nothing.

Both defects come before the planned `suspendAfter` option on
`vm.invoke`, which uses the same suspend path. The Python SDK fixes the
same defects in aws/aws-durable-execution-sdk-python#842.

## Changes

- The worker's own `lambda-microvms` client uses `maxAttempts: 1`.
- The call's signal combines the 10-second deadline with an abort
controller. The `resume` hook aborts a call that still runs. So a client
from `createMicrovmsClient` that retries sends no later attempt either.
- An aborted call counts as an unknown outcome. The `resume` hook has
already ended the refusal, so the worker starts no new one.
- With one attempt, a throttle or a clock-skew error from the worker's
own client now counts as refused for now. So the worker accepts jobs
again at once, and tries the call again after at least 30 seconds.
Before, the retried error counted as unknown, and the worker refused
jobs for up to 30 seconds.
- The job handler checks for a known callback ID before the suspend and
`terminate` refusals. A second delivery of a finished job gets HTTP 202
in every state. A second delivery of a running job gets HTTP 202, except
during a suspend refusal. There it gets HTTP 503, because the running
job may freeze, and the 503 makes the durable function check the state
and resume the MicroVM. A new job still gets HTTP 503 during the
refusal. The `run` hook already checked for a known job first.
- README "Auto-suspend" and "Suspend and terminate hooks", and
CHANGELOG, are updated.

## Testing

- New tests: the worker's own client makes one attempt; the `resume`
hook aborts a running call, so a client that retries sends no later
attempt; the idle time starts when the aborted call ends; a second
delivery of a running or finished job gets 202 during a refusal and
after the `terminate` hook, and starts nothing; a second `resume` hook
during the call aborts once; a `suspend` hook after the abort keeps its
refusal. a resend of a running job gets 503 during a suspend refusal,
and a composed test with the extras request step resumes the MicroVM in
that case. The first 7 tests fail on `main`.
- Worker `npm test`: 305 tests pass, with coverage of 96.81% statements
and 92.78% branches. `npm run lint` and `npm run format:check` pass.

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
@ParidelPooya
ParidelPooya merged commit c0a173b into main Oct 26, 2026
39 checks passed
@ParidelPooya
ParidelPooya deleted the fix/microvm-worker-suspend-retry-and-duplicates branch October 26, 2026 04:39
@github-actions

Copy link
Copy Markdown
Contributor

Codex AI review

No actionable findings in the PR changes. Tests were inspected but not executed under the review constraints; live MicroVM freeze/resume timing remains unverified by this review.

Reviewed commit 1488985f26e0fc2809ebc025807bec20870fe43b. Workflow run

This branch was successfully deployed

1 active deployment
ai-pr-review-runtime — 1488985f Deployed Oct 26, 2026 by ParidelPooya via ai-pr-review / Codex review / Generate Codex review #2458
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