mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[GH-ISSUE #102] Snapshot restore may stall when AgentENV reuses an envd bootstrap connection across sandbox generations #36
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @leether on GitHub (Aug 1, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/102
Affected component
Orchestrator / sandbox lifecycle
AgentENV version
leether/AgentENV@105fbbc, based onkvcache-ai/AgentENV@8174bd9src/sandbox/envd.rswas unchanged from upstream at the affected shared-client code path.kvcache-ai/AgentENV@8f028b1(initial open-source release; not separately runtime-tested for this report)0.6.13, built from source treee2b-dev/infra@bfdbb24Host environment
uname -a, hostname redacted):Linux <dgx-host> 6.17.0-1021-nvidia #21-Ubuntu SMP PREEMPT_DYNAMIC Wed May 27 19:14:05 UTC 2026 aarch64 aarch64 aarch64 GNU/Linuxaarch64, 4 KiB pages/dev/ublk-controlpresent; realuvm-ublk-daemonand OverlayBD paths used/dev/nvme0n1p2)kvcache-ai/firecracker-nextv1.17.0-next.1lineage, exact downstream buildb792de1c4618092b0d712c59e5d2a195c97ff5bdOperation and configuration
The failure was found by running the repository snapshot benchmark repeatedly in fresh processes. The benchmark uses a 1-vCPU, 128-MiB guest and calls
FirecrackerSandbox::resume_from_snapshot_config()directly.Representative invocation shape, with isolated paths and test CIDRs:
Relevant configuration characteristics:
The test used dedicated home/runtime/work/serial/cache directories, an isolated ublk daemon and metrics port, and dedicated network CIDRs. Production AgentENV was not stopped or restarted.
Steps to reproduce
ENVD_HTTP_CLIENTfromsrc/sandbox/envd.rs.snapshotbenchmark reaches this state through normal slot rotation; a one-slot/deterministic test setup can force it directly.In three fresh benchmark processes, each cold and hot resume group contained one approximately 30-second sample. A separate deterministic packet-capture run reproduced the same generation sequence with timings of
163.237 ms,30,995.648 ms, and160.390 ms.Expected behavior
Each sandbox runtime generation should bootstrap envd through a TCP connection that belongs to that generation. Restoring a snapshot into a reused interaction IP should not allow the host HTTP client to send health or init over an idle connection retained from a previous VM.
If an old flow is unusable, bootstrap should establish a fresh connection immediately rather than waiting for the TCP user timeout.
Actual behavior
The process-wide reqwest client pools by HTTP origin (
scheme + IP + port) and outlives individual sandbox runtimes. When the same interaction IP is reused, it can check out an idle TCP connection from the previous runtime generation.If the restored guest snapshot contains the matching TCP tuple, the guest does not immediately reject the packet. Instead, guest and host are in different TCP sequence spaces and make no forward progress. The bootstrap request waits approximately 30 seconds before reqwest opens a new connection.
Across three unforced benchmark rounds:
30.91–31.04 s; the other 27 were91.70–118.71 ms;30.52–30.54 s; the other 27 were61.55–85.06 ms.An ordinary-template control ran 10 starts in
95–179 ms. In that case the immutable snapshot did not contain the pooled connection's tuple, so the guest returned RST and reqwest reconnected quickly. Reusing the IP alone is therefore not sufficient; the snapshot must also contain the matching guest-side TCP state.Logs and diagnostics
The deterministic trace used this tuple:
Before the snapshot, the connection was at guest/host state
G0/H0. The first restore reused the tuple successfully and advanced the host side toH1:The second restore rewound only the guest to
G0, while the pooled host socket remained atH1:The host sequence is ahead by exactly 219 bytes, matching the first restore's 61-byte health request plus 158-byte init request. Both sides continued ACK/SACK and retransmission without advancing the cumulative ACK.
The first stuck packet was captured at
13:32:46.595216; the fresh-connection SYN was sent at13:33:17.457549, a30.862333 sinterval. The built reqwest0.13.3has a 30-second TCP user timeout. As a control, changing only that timeout to five seconds shortened the stall to approximately 5.5 seconds.Further controls:
Reproduction frequency
Intermittently
With the unforced repository benchmark and normal slot rotation, it appeared in 3/30 cold and 3/30 hot samples. Once the same-slot generation sequence was made deterministic, that sequence reproduced it every time.
Regression information
kvcache-ai/AgentENV@8f028b1(not separately runtime-tested)Additional context
Root cause
The connection identity used by the host pool is the HTTP origin, but the actual endpoint identity is
(origin, sandbox runtime generation). Snapshot restore rewinds guest TCP state; the host process and its idle connection pool are not part of the snapshot and continue forward. Warm network-slot reuse then assigns the same origin to a later guest runtime.The resulting state transition is:
PR #56 addressed the same origin-versus-runtime-generation mismatch for the sandbox HTTP proxy client. The envd bootstrap path uses a separate generated reqwest client and was not covered by that change.
This was discovered while following up the unexplained approximately 30-second snapshot benchmark samples reported in #101. ARM64 is the environment in which it was found, but the mechanism is not ARM-specific: it requires snapshot restore, a host client that outlives the guest runtime, and reuse of the same interaction address.
Narrow fix evaluated
We tested keeping the shared bootstrap client configuration while preventing its connections from entering the idle pool:
The scope is only envd
GET /healthandPOST /init. Command execution continues to use a separately created gRPCProcessClient; sandbox proxy traffic is unchanged.A behavioral regression test keeps the health socket open and requires init to arrive through a second
accept(). Withpool_max_idle_per_host(1)the negative control times out because init reuses the first socket; with0it passes.Validation of the narrow fix on the same isolated ARM64 host:
130.00 ms, p99152.91 ms, max158.41 ms,0/1,000 > 1 s;The evaluated patch intentionally does not add
.no_proxy()or.http1_only(), because proxy routing and protocol negotiation are unrelated behavior changes.Would maintainers be open to a focused PR that applies
pool_max_idle_per_host(0)only to the envd bootstrap client and adds the behavioral regression test? I can prepare it on a clean branch from current upstreammain.Pre-submission checklist