Commit 20bbd1f
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
0 commit comments