Key todo provider session locks by identity, not service ID#503
Key todo provider session locks by identity, not service ID#503PratikDhanave wants to merge 6 commits into
Conversation
The todo context provider keyed its per-session lock registry by Session.ServiceID(), which breaks the synchronization guarantee the lock is meant to provide: - a service ID may be empty, collapsing unrelated sessions onto one shared "_default" lock; - distinct sessions can share a service ID and unexpectedly share a lock; - the service ID is mutable, so one session can resolve to different locks over its lifetime; - registry entries were never removed and accumulated for the provider's lifetime. Key the registry by session object identity via weak.Pointer[agent.Session] instead, with a runtime.AddCleanup to drop entries once a session is collected. This mirrors the agentmode fix in microsoft#491 and matches the .NET provider, which keys its per-session lock by object identity via ConditionalWeakTable. A nil/absent session uses the shared fallback lock. Adds internal tests covering: same session -> same lock; distinct sessions with empty service IDs -> distinct locks; distinct sessions sharing a service ID -> distinct locks; nil session -> fallback lock; stale entries drained after GC.
There was a problem hiding this comment.
Pull request overview
This pull request fixes the todo context provider’s per-session lock registry so it is keyed by session object identity rather than Session.ServiceID(), avoiding lock collisions/mutation issues and preventing unbounded registry growth by removing entries after session GC.
Changes:
- Reworked
Provider.getSessionLockto key the lock registry byweak.Pointer[agent.Session]and to register aruntime.AddCleanupcallback that deletes the entry when the session is collected. - Updated provider documentation/comments to accurately describe the identity-keyed behavior and the GC cleanup behavior.
- Added internal tests validating lock identity semantics and that stale registry entries are cleaned up after GC.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
agent/harness/todo/todo.go |
Keys session locks by weak session identity and deletes lock entries via runtime.AddCleanup on session GC. |
agent/harness/todo/getsessionlock_internal_test.go |
Adds coverage for same-session lock stability, distinct-session lock separation, nil-session fallback, and cleanup of stale entries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,108 @@ | |||
| // Copyright (c) Microsoft. All rights reserved. | |||
There was a problem hiding this comment.
Move these tests to todo_test.go
There was a problem hiding this comment.
Done — moved into todo_test.go as a black-box concurrency test (concurrent tool invocations + public reads on a shared session). I dropped the white-box getSessionLock identity tests: as with the agentmode fix you merged (#491), lock identity is an implementation detail with no clean black-box observation (distinct vs. shared locks are both race-free). Happy to restore explicit white-box identity coverage if you'd prefer.
Address review feedback (prefer black-box). Replace the white-box getSessionLock identity tests with a black-box concurrency test in todo_test.go that exercises the per-session lock via concurrent tool invocations and public reads on a shared session, mirroring the resolution merged for the agentmode provider (microsoft#491).
Summary
The
todocontext provider keys its per-session lock registry bySession.ServiceID(). This is the same latent issue that was corrected for theagentmodeprovider in #491, and it breaks the synchronization guarantee the per-session lock exists to provide:"_default"lock;Fix
Key the registry by session object identity via
weak.Pointer[agent.Session], with aruntime.AddCleanupthat drops each entry once its session is garbage-collected (the cleanup captures only the weak key, never the session). A nil/absent session uses the shared fallback lock.This mirrors the approach taken for
agentmodein #491 and — notably — restores parity with .NET: the existing code comment already stated the intent was to match ".NET's per-session SemaphoreSlim via ConditionalWeakTable," butConditionalWeakTableis identity-keyed, so theServiceID-keyed Go implementation had actually diverged from it. Identity keying is the alignment.Public API
No exported symbols change; the fix is internal to the
todoprovider's lock registry.Tests
Adds
getsessionlock_internal_test.go(packagetodo) covering the same cases requested during the #491 review:*agent.Sessionalways receives the same lock;go test -race -shuffle=on ./agent/harness/todopasses;go build ./...andgo vet ./...clean; gofumpt clean.Requires Go 1.25 (
weak,runtime.AddCleanup), which the module already targets.