Skip to content

Commit 20bbd1f

Browse files
feat(serverless-init): add MicroVM lifecycle user-app forwarder pass-through (#53087)
## Summary This wires the previously-added `Forwarder` (`DD_AWS_MICROVM_USER_APP_PORT`) into the lifecycle `Server` so the agent can optionally pass each lifecycle hook through to the user's own application instead of answering it directly. The motivation is to let a customer's own application observe and react to MicroVM lifecycle events end-to-end (e.g. to run its own readiness check, or do custom work on suspend/terminate), rather than only the agent doing so — while preserving today's default (no forwarder configured) behavior for everyone else. When `DD_AWS_MICROVM_USER_APP_PORT` is set: `/ready` and `/validate` wait for TCP reachability before forwarding (so a not-yet-listening user app doesn't fail these platform-facing checks); `/run`, `/resume`, `/suspend`, and `/terminate` run the agent's own work (metric emission, and for `/suspend`/`/terminate`, the telemetry flush) in a goroutine in parallel with the pass-through to the user app, then mirror the user app's response (status, body, `Content-Type`) back to the platform. `/suspend` flushes in parallel with the forward (Firecracker's snapshot preserves any residual buffered telemetry, recoverable on the next resume); `/terminate` flushes sequentially after the forward completes, since a torn-down VM has no recovery path for anything left unflushed. When the env var is unset, behavior is unchanged from the prior PR in this stack. ### Fix: response body could be truncated on `/suspend` A [Codex review comment](#53087 (comment)) flagged that `/suspend`'s parallel-flush path could mirror an empty or truncated user-app response to the platform. `Forwarder.PassThrough`'s returned `resp.Body` is tied to a `forwardTimeout`-bounded context (default 1s). The handler only buffered the body upfront for the sequential-flush path (`/terminate`); the parallel-flush path (`/suspend`) instead waited for the concurrent flush (default `flushTimeout` of 5s) before ever reading `resp.Body`. Since `forwardTimeout < flushTimeout` by default, the forward's context had already expired by the time `mirrorResponse` read the body — so a successful user-app response could reach the platform as empty or truncated. Fixed by buffering the body unconditionally right after `PassThrough` returns, before waiting on the flush, so every mode (`/run`, `/resume`, `/suspend`, `/terminate`) is covered. Added `TestHandleSuspend_WithForwarder_BodyBufferedBeforeFlush`, verified to fail with `context deadline exceeded` against the pre-fix code. ## Stack This is **PR 4 of 4** (the final PR) in a split of #53035 ("feat(serverless-init): add MicroVM lifecycle HTTP server and env-var wiring"), which was too large to review as a single PR. The full stack, in merge order: 1. wire MicroVM lifecycle server config from env vars 2. add standalone MicroVM lifecycle HTTP server 3. propagate MicroVM ID to logs and trace tags 4. **(this PR)** add MicroVM lifecycle user-app forwarder pass-through This PR's tip started out byte-for-byte identical to the original `tianning.li/microvm-06-lifecycle-server-wire` branch, confirming the split stack reconstituted the original change exactly; it has since diverged from that original branch due to the response-body-buffering fix above. **Reviewer note:** this PR is larger than the ~300-line target used for the rest of the split. The forwarder pass-through logic (`handleWithForwarder`, `mirrorResponse`, the TCP-wait pass-through for `/ready` and `/validate`) and its test coverage form one cohesive, hard-to-subdivide unit, so it's kept as a single PR rather than being split further — this exception was already discussed and agreed on before starting this split. ## Test plan ``` bazel test //cmd/serverless-init/lifecycle:lifecycle_test ``` - [x] `bazel build //cmd/serverless-init/lifecycle:lifecycle` - [x] `bazel test //cmd/serverless-init/lifecycle:lifecycle_test` - [x] `dda inv linter.go --targets=./cmd/serverless-init/lifecycle` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent e06fefd commit 20bbd1f

2 files changed

Lines changed: 923 additions & 66 deletions

File tree

0 commit comments

Comments
 (0)