Skip to content

feat: idle-timeout keepalive model with auto-resume - #3

Open
Javey wants to merge 6 commits into
contember:mainfrom
Javey:main
Open

feat: idle-timeout keepalive model with auto-resume#3
Javey wants to merge 6 commits into
contember:mainfrom
Javey:main

Conversation

@Javey

@Javey Javey commented Aug 31, 2026

Copy link
Copy Markdown

Summary

Replaces the absolute ExpiresAt deadline model with a computed LastActiveAt + Timeout idle model, adds data-plane auto-keepalive, idempotent docker pause/unpause, and proxy-level auto-resume for paused sandboxes.

Changes

1. Idle-timeout model with data-plane auto-keepalive

  • Sandbox.ExpiresAt field → computed ExpiresAt() method (LastActiveAt + Timeout)
  • New LastActiveAt + Timeout fields as sources of truth
  • Manager.MarkActivity(id) called by dispatch on every data-plane request (coalesced to 1s/sandbox)
  • EnforceTimeouts uses idle check (now - LastActiveAt >= Timeout) instead of absolute deadline
  • LabelTimeout Docker label for Rehydrate to recover original TTL
  • Config: EDVABE_KEEPALIVE_ENABLED (default on), EDVABE_KEEPALIVE_COALESCE (default 1s)
  • API responses and dashboard include lastActiveAt field

2. Idempotent docker pause/unpause

  • Pre-flight ContainerInspect before pause/unpause: return nil if already in desired state
  • Fixes Manager TOCTOU race (Resume/Connect check State==Paused under lock, release, then call unpause — concurrent resume wins the race)
  • Enables recovery from external docker commands

3. Auto-resume paused sandboxes on data-plane requests

  • Proxy handler checks sandbox state before forwarding
  • If paused, calls Manager.Connect(id, sb.Timeout) to resume + reset idle clock
  • Running sandboxes skip the check entirely
  • Browser refresh or SDK request to paused sandbox transparently resumes it

Inspired by CubeSandbox's traffic-driven keepalive model and sandbox_state.lua auto-resume gate.

Test plan

  • go vet ./... passes
  • go test ./... passes
  • TestAutoResumePausedSandbox — verifies resume+forward path
  • TestProxyNoResumeForRunningSandbox — verifies no spurious Connect for running sandboxes
  • All existing tests updated for new ExpiresAt() method

Javey added 6 commits August 27, 2026 20:25
Replace absolute ExpiresAt field with computed property derived from
LastActiveAt + Timeout. Data-plane requests (any traffic routed through
the proxy) now implicitly reset the idle timer via Manager.MarkActivity,
eliminating the need for SDK clients to explicitly call /timeout to keep
sandboxes alive.

Key changes:
- Sandbox.ExpiresAt field → ExpiresAt() computed method
- Add Sandbox.Timeout and Sandbox.LastActiveAt as sources of truth
- Add LabelTimeout Docker label for Rehydrate to recover original TTL
- EnforceTimeouts uses idle check (now - LastActiveAt >= Timeout)
  instead of absolute deadline (now > ExpiresAt)
- dispatch.go NewRouter accepts onActivity callback, wired to
  Manager.MarkActivity in main.go
- MarkActivity coalesces writes (default 1s) to avoid lock contention
- Config: EDVABE_KEEPALIVE_ENABLED (default on), EDVABE_KEEPALIVE_COALESCE
- API responses and dashboard include lastActiveAt field

Design inspired by CubeSandbox's traffic-driven keepalive model.
KeepaliveEnabled was a bool with broken default handling — both
branches of the if set true, making it impossible to disable keepalive.
Changed to *bool: nil = default on, &true = explicit on, &false = off.
Drop *bool three-state approach. main.go is the only Options constructor
and always passes a concrete bool. The original bug was just redundant
if-branches — a direct assignment fixes it.
Pre-flight ContainerInspect before pause/unpause: Pause returns early
if already paused, Unpause returns early if already running. Robust
against Docker API message changes across versions. Fixes the
Manager TOCTOU race (Resume/Connect check State==Paused under lock,
release, then call unpause - a concurrent resume wins the race) and
enables recovery from external docker commands.
Data-plane traffic (any request routed through the proxy) now
auto-resumes a paused sandbox before forwarding. This mirrors
CubeSandbox sandbox_state.lua gate: a browser refresh or SDK request
to a paused sandbox transparently unpauses it instead of hanging or
erroring.

NewProxy gains a third AutoResumer interface param (satisfied by
Manager.Connect, which unpauses + resets the idle clock). Running
sandboxes skip the check entirely. Paused sandboxes get Connect(id,
sb.Timeout) — preserving the original TTL while granting a fresh idle
lease.

Two new tests: TestAutoResumePausedSandbox verifies the resume+forward
path; TestProxyNoResumeForRunningSandbox verifies no spurious Connect
for running sandboxes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant