opensandbox-group/OpenSandbox

feat(sdk/go): add SandboxPoolManager with fence/tombstone parity with Python and Kotlin

Open

#1,353 opened on Jul 20, 2026

View on GitHub
 (1 comment) (0 reactions) (0 assignees)Python (939 forks)github user discovery
featurehelp wantedsdk/gosdks

Repository metrics

Stars
 (11,461 stars)
PR merge metrics
 (PR metrics pending)

Description

Discovered while reviewing #1352 (client pool guide). Codex flagged that the guide cannot cleanly recommend a cross-SDK "retire an old pool namespace" procedure, because the safe API exists in Python and Kotlin but not in Go.

Current state

Python (sdks/sandbox/python/src/opensandbox/pool_manager.py) and Kotlin (sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/pool/SandboxPoolManager.kt) both ship a SandboxPoolManager.destroy(poolName, options) API that applies a full protocol:

  1. Short-circuit if getDestroyState(poolName) == DESTROYED
  2. beginDestroy(poolName, ownerId) — write a DESTROYING fence into the state store, observable by any still-running peer pool instance
  3. Loop tryTakeIdle(poolName) + killSandbox(id) up to drainTimeout, best-effort
  4. clearPoolState(poolName) — wipe max_idle, idle TTL, etc.
  5. markDestroyed(poolName, ownerId, tombstoneTtl) — write a DESTROYED tombstone (default 7 days) so future callers cannot silently rebind

Options: strategy (only FORCE currently), drainTimeout (default 30s), tombstoneTtl (default 7d).

Go (sdks/sandbox/go/**) has none of this:

  • grep -rE PoolManager|Destroy|Tombstone|Fence|DESTROYING|DESTROYED sdks/sandbox/go → zero matches
  • PoolStateStore interface at sdks/sandbox/go/pool_store.go:25-74 has 15 methods; none of GetDestroyState, BeginDestroy, ClearPoolState, MarkDestroyed
  • RedisPoolStateStore at sdks/sandbox/go/poolredis/store.go mirrors the same 15 methods, no extra destroy Lua / helpers
  • DefaultSandboxPool.Shutdown (sdks/sandbox/go/pool.go:548) only stops the local reconciler and releases the primary lock; it does not kill idle sandboxes (guarded by TestPool_Shutdown_DoesNotReleaseIdle at pool_test.go:803 and TestPool_Shutdown_NonGraceful_DoesNotReleaseIdle at :841)

The guide documents the gap explicitly at docs/guides/client-pool.md "Retiring an old pool namespace" — Go users are told they must stop every writer first, manually loop ReleaseAllIdle + SetMaxIdle(ctx, poolName, 0), rotate PoolName, and (for Redis) clean up keys with DEL/SCAN. Without a shared fence there is no way to safely retire a namespace while any peer pool instance is still writing.

Why this matters

  • Real gap in cross-language SDK parity for a public capability (pool retirement).
  • Docs guide the affected users to an operator-driven manual sequence that races with any surviving peer node — the exact race that the Python/Kotlin DESTROYING fence closes.
  • Users cannot safely swap a pool template on the same pool_name in Go under multi-process / multi-Pod deployment.

Proposed direction

Port the Python/Kotlin design to Go. Rough scope:

  1. Extend PoolStateStore interface (sdks/sandbox/go/pool_store.go) with four methods:
    • GetDestroyState(ctx, poolName) (PoolDestroyState, error)
    • BeginDestroy(ctx, poolName, ownerID) error — returns ErrPoolDestroyed if already tombstoned
    • ClearPoolState(ctx, poolName) error
    • MarkDestroyed(ctx, poolName, ownerID, tombstoneTTL time.Duration) error
  2. Implement in both stores:
    • sdks/sandbox/go/pool_store_memory.go
    • sdks/sandbox/go/poolredis/store.go (with matching Lua / atomic ops as needed, mirroring the Python Redis store)
  3. Add types: PoolDestroyState (ACTIVE / DESTROYING / DESTROYED), PoolDestroyOptions, PoolDestroyResult, PoolDestroyStrategy (start with just FORCE).
  4. Add SandboxPoolManager and NewSandboxPoolManagerBuilder(...) in a new pool_manager.go, exposing Destroy(ctx, poolName, options) (*PoolDestroyResult, error) and following the same 5-step protocol as Python/Kotlin.
  5. Make DefaultSandboxPool observe the fence: Start() and the reconcile loop should check GetDestroyState and refuse to warm up when the namespace is DESTROYING / DESTROYED.
  6. Tests: port the corresponding Python/Kotlin test files. At minimum a table-driven TestSandboxPoolManager_Destroy_* covering idempotency, drain timeout, tombstone TTL expiry, and fence observation from a concurrent live pool.
  7. Docs: once shipped, update docs/guides/client-pool.md "Retiring an old pool namespace" so the Go subsection collapses down to pool_manager.Destroy(...) with a note on PoolDestroyOptions.

Compatibility

  • All new interface methods on PoolStateStore are a breaking change for anyone with a custom store implementation. Options: (a) add them to the interface directly and cut a minor SDK version, or (b) add them via a narrower PoolDestroyStore optional interface and have SandboxPoolManager type-assert. Python and Kotlin took route (a); happy to discuss trade-offs.
  • No changes to specs or server behavior — this is entirely in the Go client pool layer.

References

  • Python implementation: sdks/sandbox/python/src/opensandbox/pool_manager.py, sdks/sandbox/python/src/opensandbox/_pool_store.py
  • Kotlin implementation: sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/pool/SandboxPoolManager.kt, sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/domain/pool/PoolDestroyModels.kt
  • Docs surfacing the gap: PR #1352 (docs/guides/client-pool.md, "Retiring an old pool namespace")
  • Related SDK AGENTS guidance on cross-language behavior parity: sdks/AGENTS.md

Contributor guide