fix(harness): isolate sandbox binding per call to fix concurrent corruption - #2675
fix(harness): isolate sandbox binding per call to fix concurrent corruption#2675larry-zy wants to merge 1 commit into
Conversation
…uption (agentscope-ai#2490) SandboxLifecycleMiddleware and SandboxBackedFilesystem held the acquired sandbox in a single agent-level slot (an AtomicReference plus the proxy's volatile field). Because AgentBase.serializeOnKey only serialises same (userId, sessionId) calls, distinct-session calls run in parallel on one agent bean and raced on that slot: one call would execute against another session's sandbox, and a finishing call's release would tear down a still-live sibling sandbox. Bind the acquired SandboxAcquireResult per call on the invocation's RuntimeContext and resolve it there first, so concurrent distinct-session calls stay isolated. releaseForCall reads back its own binding and tears down only its own sandbox. The volatile field is retained as a best-effort fallback for context-free internal callers that resolve the filesystem with a shared empty RuntimeContext (e.g. WorkspaceMessageBus). To keep that fallback from being clobbered across concurrent calls, the field is now maintained via a synchronized compare-and-clear (clearSandboxIfCurrent) so a releasing call never nulls a sibling's binding. Adds SandboxLifecycleConcurrencyReproTest, which drives the legal A.acquire -> B.acquire -> A.use -> A.release interleaving and asserts each call executes against its own sandbox and A's release affects only A.
|
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes issue #2490 — concurrent calls for distinct (userId, sessionId) sessions on the same agent bean corrupted each other's sandbox binding. The root cause was a single agent-level slot (volatile Sandbox field + currentAcquireResult) shared across all concurrent calls. The fix moves the sandbox binding to the per-call RuntimeContext (typed attribute), with the volatile field retained as a best-effort fallback for context-free callers (e.g., WorkspaceMessageBus). A compareAndClear pattern (synchronized + identity check) prevents one call's release from nulling another's fallback. A deterministic concurrency test reproduces the exact interleaving and validates three isolation invariants. The fix is architecturally sound and correctly addresses the reported issue.
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes issue #2490 — concurrent calls for distinct (userId, sessionId) sessions on the same agent bean corrupted each other's sandbox binding. The root cause was a single agent-level slot (volatile Sandbox field + currentAcquireResult) shared across all concurrent calls. The fix moves the sandbox binding to the per-call RuntimeContext (typed attribute), with the volatile field retained as a best-effort fallback for context-free callers (e.g., WorkspaceMessageBus). A compareAndClear pattern (synchronized + identity check) prevents one call's release from nulling another's fallback. A deterministic concurrency test reproduces the exact interleaving and validates three isolation invariants. The fix is architecturally sound and correctly addresses the reported issue.
Related: #2490
Summary
SandboxLifecycleMiddleware and SandboxBackedFilesystem held the acquired sandbox in a single agent-level slot (an AtomicReference plus the proxy's volatile field). Since
AgentBase.serializeOnKey only serialises calls with the same (userId, sessionId), calls from distinct sessions run in parallel on one agent bean and raced on that shared slot,
causing two failures:
Changes
WorkspaceMessageBus). To stop that fallback from being clobbered under concurrency, it's now maintained via a synchronized compare-and-clear (clearSandboxIfCurrent), so a
releasing call never nulls a sibling's binding.
Testing
Adds SandboxLifecycleConcurrencyReproTest, which drives the legal A.acquire → B.acquire → A.use → A.release interleaving and asserts each call executes against its own sandbox
and that A's release affects only A.
mvn -pl agentscope-harness test
-Dtest='SandboxLifecycleConcurrencyReproTest,SandboxBackedFilesystemTest,SandboxLifecycleMiddlewareCallbackTest'
All related tests pass (18 run, 0 failures, 0 errors).