feat(sdk/go): add SandboxPoolManager with fence/tombstone parity with Python and Kotlin
#1,353 opened on Jul 20, 2026
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:
- Short-circuit if
getDestroyState(poolName) == DESTROYED beginDestroy(poolName, ownerId)— write aDESTROYINGfence into the state store, observable by any still-running peer pool instance- Loop
tryTakeIdle(poolName)+killSandbox(id)up todrainTimeout, best-effort clearPoolState(poolName)— wipemax_idle, idle TTL, etc.markDestroyed(poolName, ownerId, tombstoneTtl)— write aDESTROYEDtombstone (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 matchesPoolStateStoreinterface atsdks/sandbox/go/pool_store.go:25-74has 15 methods; none ofGetDestroyState,BeginDestroy,ClearPoolState,MarkDestroyedRedisPoolStateStoreatsdks/sandbox/go/poolredis/store.gomirrors the same 15 methods, no extra destroy Lua / helpersDefaultSandboxPool.Shutdown(sdks/sandbox/go/pool.go:548) only stops the local reconciler and releases the primary lock; it does not kill idle sandboxes (guarded byTestPool_Shutdown_DoesNotReleaseIdleatpool_test.go:803andTestPool_Shutdown_NonGraceful_DoesNotReleaseIdleat: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
DESTROYINGfence closes. - Users cannot safely swap a pool template on the same
pool_namein Go under multi-process / multi-Pod deployment.
Proposed direction
Port the Python/Kotlin design to Go. Rough scope:
- Extend
PoolStateStoreinterface (sdks/sandbox/go/pool_store.go) with four methods:GetDestroyState(ctx, poolName) (PoolDestroyState, error)BeginDestroy(ctx, poolName, ownerID) error— returnsErrPoolDestroyedif already tombstonedClearPoolState(ctx, poolName) errorMarkDestroyed(ctx, poolName, ownerID, tombstoneTTL time.Duration) error
- Implement in both stores:
sdks/sandbox/go/pool_store_memory.gosdks/sandbox/go/poolredis/store.go(with matching Lua / atomic ops as needed, mirroring the Python Redis store)
- Add types:
PoolDestroyState(ACTIVE/DESTROYING/DESTROYED),PoolDestroyOptions,PoolDestroyResult,PoolDestroyStrategy(start with justFORCE). - Add
SandboxPoolManagerandNewSandboxPoolManagerBuilder(...)in a newpool_manager.go, exposingDestroy(ctx, poolName, options) (*PoolDestroyResult, error)and following the same 5-step protocol as Python/Kotlin. - Make
DefaultSandboxPoolobserve the fence:Start()and the reconcile loop should checkGetDestroyStateand refuse to warm up when the namespace isDESTROYING/DESTROYED. - 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. - Docs: once shipped, update
docs/guides/client-pool.md"Retiring an old pool namespace" so the Go subsection collapses down topool_manager.Destroy(...)with a note onPoolDestroyOptions.
Compatibility
- All new interface methods on
PoolStateStoreare 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 narrowerPoolDestroyStoreoptional interface and haveSandboxPoolManagertype-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