Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/on-resume-reboot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'e2b': minor
'@e2b/python-sdk': minor
Comment thread
bchalios marked this conversation as resolved.
---

Add `onResume` / `on_resume` to `Sandbox.connect()`: `'reboot'` resumes a paused sandbox from its disk state alone, leaving the memory snapshot untouched, for the case where restoring that memory wedges the guest. `'restore'` stays the default. Where filesystem-only resume is not enabled, a `'reboot'` that would actually drop memory is rejected with an error rather than silently restoring it.
12 changes: 9 additions & 3 deletions packages/js-sdk/src/api/schema.gen.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/js-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type {
SandboxOpts,
SandboxApiOpts,
SandboxConnectOpts,
SandboxOnResume,
SandboxForkOpts,
SandboxMetricsOpts,
SandboxPauseOpts,
Expand Down
22 changes: 22 additions & 0 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,16 @@ export interface SandboxOpts extends ConnectionOpts {
lifecycle?: SandboxLifecycle
}

/**
* How a paused sandbox comes back.
*
* `'restore'` restores the memory snapshot, so processes and open connections
* survive the pause. `'reboot'` ignores any memory in the snapshot and
* cold-boots from disk state alone — the rescue path for a snapshot whose
* memory image wedges the guest.
*/
export type SandboxOnResume = 'restore' | 'reboot'

/**
* Options for connecting to a Sandbox.
*/
Expand All @@ -674,6 +684,17 @@ export type SandboxConnectOpts = ConnectionOpts & {
* @default 300_000 // 5 minutes
*/
timeoutMs?: number

/**
* How to bring a paused sandbox back: `'restore'` (the default) restores the
* memory snapshot; `'reboot'` cold-boots from disk state, so writes not
* flushed before the pause may be lost. Rejected where filesystem-only resume
* is not enabled; a no-op for a snapshot without memory or a sandbox that is
* already running.
*
* @default 'restore'
*/
onResume?: SandboxOnResume
Comment thread
bchalios marked this conversation as resolved.
}

/**
Expand Down Expand Up @@ -1796,6 +1817,7 @@ export class SandboxApi extends ClientFactory {
},
body: {
timeout: timeoutToSeconds(timeoutMs),
memory: apiOpts?.onResume === 'reboot' ? false : undefined,
},
signal: config.getSignal(apiOpts?.requestTimeoutMs, apiOpts?.signal),
})
Expand Down
80 changes: 80 additions & 0 deletions packages/js-sdk/tests/sandbox/onResumeRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'

import { Sandbox } from '../../src'
import { TEST_API_KEY, apiUrl } from '../setup'

let lastConnectBody: Record<string, unknown> | undefined

const server = setupServer(
http.post(apiUrl('/sandboxes/:sandboxID/connect'), async ({ request }) => {
lastConnectBody = (await request.json()) as Record<string, unknown>
return HttpResponse.json({
sandboxID: 'test-sandbox-id',
templateID: 'base',
envdVersion: '0.2.4',
})
})
)

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))

afterAll(() => server.close())

afterEach(() => {
lastConnectBody = undefined
server.resetHandlers()
})

test('Sandbox.connect omits memory when onResume is not given', async () => {
await Sandbox.connect('test-sandbox-id', { apiKey: TEST_API_KEY })

expect(lastConnectBody).toBeDefined()
expect(lastConnectBody).not.toHaveProperty('memory')
})

test("Sandbox.connect omits memory for onResume: 'restore'", async () => {
// 'restore' is the API's own default, so it must travel as an absent field
// rather than memory: true — the two are not interchangeable on the wire.
await Sandbox.connect('test-sandbox-id', {
apiKey: TEST_API_KEY,
onResume: 'restore',
})

expect(lastConnectBody).not.toHaveProperty('memory')
})

test("Sandbox.connect sends memory: false for onResume: 'reboot'", async () => {
await Sandbox.connect('test-sandbox-id', {
apiKey: TEST_API_KEY,
onResume: 'reboot',
})

expect(lastConnectBody?.memory).toBe(false)
})

test('sandbox.connect carries onResume on the instance form too', async () => {
const sandbox = await Sandbox.connect('test-sandbox-id', {
apiKey: TEST_API_KEY,
})
lastConnectBody = undefined

await sandbox.connect({ onResume: 'reboot' })
expect(lastConnectBody?.memory).toBe(false)

await sandbox.connect()
expect(lastConnectBody).not.toHaveProperty('memory')
})

test('an untyped onResume value never sends memory: false', async () => {
// Untyped callers can pass anything; only the 'reboot' literal opts into a
// cold boot, so an unrecognized value must fall back to a memory restore.
await Sandbox.connect('test-sandbox-id', {
apiKey: TEST_API_KEY,
// @ts-expect-error 'Reboot' is not a valid onResume value
onResume: 'Reboot',
})

expect(lastConnectBody).not.toHaveProperty('memory')
})
2 changes: 2 additions & 0 deletions packages/python-sdk/e2b/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
SandboxNetworkTransformContext,
SandboxNetworkTransformResolver,
SandboxNetworkUpdate,
SandboxOnResume,
SandboxListOrder,
SandboxQuery,
SandboxState,
Expand Down Expand Up @@ -233,6 +234,7 @@
"SandboxNetworkUpdate",
"SandboxLifecycle",
"SandboxOnTimeout",
"SandboxOnResume",
"ALL_TRAFFIC",
# IAM
"SandboxIamOpts",
Expand Down
17 changes: 16 additions & 1 deletion packages/python-sdk/e2b/api/client/models/connect_sandbox.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions packages/python-sdk/e2b/api/client/models/resumed_sandbox.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading