Skip to content

Commit 9153aa3

Browse files
authored
fix(tests): deflake TestSandboxMemoryIntegrity with explicit resume timeout (#2988)
`TestSandboxMemoryIntegrity/tmpfs_hash` flakes ~52% on main. Service logs from failed runs show it is never an integrity failure (no hash mismatch ever): the resume call passes an empty body, so the sandbox gets the 15s `SandboxTimeoutDefault`. Hashing the ~480MB tmpfs after resume faults the whole file back through UFFD, which takes >15s on the compressed/dedup shards (zstd1 fails 12/15 recent main runs, lz4 4/15, uncompressed 0/15), so the sandbox is evicted with `kill_reason: timeout` mid-check and every retry gets 502 until the 3min `EventuallyWithT` window expires. Pass an explicit 300s timeout on resume, matching the sandbox creation timeout.
1 parent 1c73a49 commit 9153aa3

7 files changed

Lines changed: 49 additions & 26 deletions

File tree

‎tests/integration/internal/tests/api/sandboxes/sandbox_list_test.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ func TestSandboxListPaginationRunningLargerLimit(t *testing.T) { //nolint:tparal
321321
sbxsCount := 12
322322
sandboxes := make([]string, sbxsCount)
323323
for i := range sbxsCount {
324-
sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithMetadata(api.SandboxMetadata{metadataKey: metadataValue}))
324+
// Default 30s timeout is shorter than creating 12 sandboxes plus the
325+
// list assertions under load, so the sandboxes would expire mid-test.
326+
sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithTimeout(300), utils.WithMetadata(api.SandboxMetadata{metadataKey: metadataValue}))
325327
sandboxes[sbxsCount-i-1] = sbx.SandboxID
326328

327329
t.Logf("Created sandbox %d/%d: %s", i+1, sbxsCount, sbx.SandboxID)

‎tests/integration/internal/tests/envd/process_test.go‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/stretchr/testify/require"
1111

1212
"github.com/e2b-dev/infra/packages/shared/pkg/grpc/envd/process"
13+
"github.com/e2b-dev/infra/tests/integration/internal/api"
1314
"github.com/e2b-dev/infra/tests/integration/internal/setup"
1415
"github.com/e2b-dev/infra/tests/integration/internal/utils"
1516
)
@@ -20,15 +21,28 @@ func TestCommandKillNextApp(t *testing.T) {
2021
defer cancel()
2122

2223
client := setup.GetAPIClient()
23-
sbx := utils.SetupSandboxWithCleanup(t, client, utils.WithTimeout(300))
24-
25-
envdClient := setup.GetEnvdClient(t, ctx)
2624

27-
// Run `npx create-next-app`
25+
// Pre-bake create-next-app into a template build (fresh-boot VM) instead of
26+
// running the page-fault-heavy npm install in the restored sandbox, where
27+
// host load has caused guest soft lockups and envd stream EOFs.
2828
// Pinned to the latest known-good stable release; `@latest` currently resolves
2929
// to a 16.3.0 pre-release that ships a broken SWC binary and 404s on preview.
30-
err := utils.ExecCommand(t, ctx, sbx, envdClient, "npx", "create-next-app@16.2.8", "nextapp", "--yes")
31-
require.NoError(t, err)
30+
tmpl := utils.BuildTemplate(t, utils.TemplateBuildOptions{
31+
Name: "kill-next-app",
32+
Timeout: 10 * time.Minute,
33+
BuildData: api.TemplateBuildStartV2{
34+
FromTemplate: &setup.SandboxTemplateID,
35+
Steps: &[]api.TemplateStep{{
36+
Type: "RUN",
37+
Args: &[]string{"cd /home/user && npx create-next-app@16.2.8 nextapp --yes", "user"},
38+
}},
39+
},
40+
ReqEditors: []api.RequestEditorFn{setup.WithAPIKey()},
41+
})
42+
43+
sbx := utils.SetupSandboxWithCleanup(t, client, utils.WithTimeout(300), utils.WithTemplateID(tmpl.TemplateID))
44+
45+
envdClient := setup.GetEnvdClient(t, ctx)
3246

3347
// Run `npm run dev` in background
3448
cwd := "~/nextapp"

‎tests/integration/internal/tests/orchestrator/sandbox_memory_integrity_test.go‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ func TestSandboxMemoryIntegrity(t *testing.T) {
2121

2222
c := setup.GetAPIClient()
2323

24+
// Resuming without an explicit timeout gives the sandbox only the 15s
25+
// default; hashing the tmpfs after resume can take longer than that under
26+
// load, so the sandbox would be evicted mid-check.
27+
resumeTimeout := int32(300)
28+
2429
// Build a template with stress-ng and time pre-installed so subtests
2530
// skip the page-fault-heavy apt-get phase that saturates decompression
2631
// under parallel load.
@@ -100,7 +105,7 @@ echo "Used memory after tmpfs mount and file fill: ${USED_MEM_MB_AFTER} MB"
100105
require.NotNil(t, res.JSON200)
101106
assert.Equal(t, api.Paused, res.JSON200.State)
102107

103-
sbxResume, err := c.PostSandboxesSandboxIDResumeWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDResumeJSONRequestBody{}, setup.WithAPIKey())
108+
sbxResume, err := c.PostSandboxesSandboxIDResumeWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDResumeJSONRequestBody{Timeout: &resumeTimeout}, setup.WithAPIKey())
104109
require.NoError(t, err)
105110
require.Equal(t, http.StatusCreated, sbxResume.StatusCode())
106111
require.NotNil(t, sbxResume.JSON201)
@@ -135,7 +140,7 @@ echo "Used memory after tmpfs mount and file fill: ${USED_MEM_MB_AFTER} MB"
135140
}
136141
resume := func() {
137142
t.Helper()
138-
r, err := c.PostSandboxesSandboxIDResumeWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDResumeJSONRequestBody{}, setup.WithAPIKey())
143+
r, err := c.PostSandboxesSandboxIDResumeWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDResumeJSONRequestBody{Timeout: &resumeTimeout}, setup.WithAPIKey())
139144
require.NoError(t, err)
140145
require.Equal(t, http.StatusCreated, r.StatusCode())
141146
}

‎tests/integration/internal/tests/orchestrator/sandbox_object_not_found_test.go‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ func TestSandboxObjectNotFound(t *testing.T) {
2222

2323
client := setup.GetOrchestratorClient(t, ctx)
2424

25-
for range 10 {
25+
// Under full host load (parallel 100-sandbox tests) the orchestrator can
26+
// report ResourceExhausted for minutes before template validation is
27+
// reached, so keep the retry window generous.
28+
for range 36 {
2629
_, err := client.Create(ctx, &orchestrator.SandboxCreateRequest{
2730
Sandbox: &orchestrator.SandboxConfig{
2831
TemplateId: "nonexistent-template-id",
@@ -67,6 +70,6 @@ func TestSandboxObjectNotFound(t *testing.T) {
6770
return
6871
}
6972

70-
t.Log("failed to create sandbox after 10 retries")
73+
t.Log("failed to create sandbox after 36 retries")
7174
t.FailNow()
7275
}

‎tests/integration/internal/tests/proxies/auto_resume_test.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ func TestSandboxAutoResumeViaProxy(t *testing.T) {
109109
require.NotNil(t, res.JSON200, "expected 200 response, got status %d", res.StatusCode())
110110
require.Equal(t, api.Paused, res.JSON200.State)
111111

112-
// Make a proxy request to trigger auto-resume.
113-
resumeClient := &http.Client{Timeout: 10 * time.Second}
112+
// Make a proxy request to trigger auto-resume. The single request must
113+
// cover the whole snapshot resume, which can take a while under load.
114+
resumeClient := &http.Client{Timeout: 60 * time.Second}
114115
req := utils.NewRequest(sbx, proxyURL, port, nil)
115116
resp, err = resumeClient.Do(req)
116117
require.NoError(t, err)

‎tests/integration/internal/tests/proxies/mask_request_host_test.go‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,21 @@ func TestMaskRequestHostAPIParameter(t *testing.T) {
3030
// Create sandbox with maskRequestHost set
3131
sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithNetwork(sbxNet), utils.WithTimeout(120))
3232

33-
// run sudo apt-get update; sudo apt-get install -y netcat
34-
// run nc -l -p 8080
3533
envdClient := setup.GetEnvdClient(t, ctx)
3634

37-
// Install netcat for the test
38-
err := utils.ExecCommandAsRoot(t, ctx, sbx, envdClient, "apt-get", "update")
39-
require.NoError(t, err)
40-
err = utils.ExecCommandAsRoot(t, ctx, sbx, envdClient, "apt-get", "install", "-y", "netcat-traditional")
41-
require.NoError(t, err)
42-
4335
port := 8080
44-
// Start a netcat listener on port that outputs request headers to a file
36+
// Single-shot listener that dumps the raw request to a file. Uses the
37+
// preinstalled python instead of installing netcat via apt-get, which is
38+
// page-fault-heavy enough to wedge the sandbox under parallel test load.
4539
outputFile := "/tmp/nc_output.txt"
40+
listener := fmt.Sprintf(
41+
`import socket; s=socket.socket(); s.bind(("0.0.0.0",%d)); s.listen(1); c,_=s.accept(); open(%q,"wb").write(c.recv(65536))`,
42+
port, outputFile)
4643
go func() {
47-
_ = utils.ExecCommandAsRoot(t, ctx, sbx, envdClient,
48-
"/bin/bash", "-c", fmt.Sprintf("nc -l -p %d > %s", port, outputFile))
44+
_ = utils.ExecCommandAsRoot(t, ctx, sbx, envdClient, "python3", "-c", listener)
4945
}()
5046

51-
// Wait for nc to be up
47+
// Wait for the listener to be up
5248
time.Sleep(2 * time.Second)
5349

5450
// Prepare the sandbox URL using helpers

‎tests/integration/internal/tests/proxies/traffic_access_token_test.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,9 @@ func TestEnvdAccessTokenAutoResumeViaProxy(t *testing.T) {
317317
envdHealthURL := *proxyURL
318318
envdHealthURL.Path = "/health"
319319

320-
client := &http.Client{Timeout: 10 * time.Second}
320+
// The auto-resume request below must cover the whole snapshot resume,
321+
// which can take longer than 10s under load.
322+
client := &http.Client{Timeout: 60 * time.Second}
321323
envdPort := int(consts.DefaultEnvdServerPort)
322324

323325
// Verify envd is reachable with valid access token while running.

0 commit comments

Comments
 (0)