Repository navigation
Python: share todo mutation locks across providers on one store - #9869
Closed
NingLin Liu (ninglinLiu) wants to merge 1 commit into
Closed
NingLin Liu (ninglinLiu) wants to merge 1 commit into
NingLin Liu (ninglinLiu) wants to merge 1 commit into
Conversation
TodoProvider kept per-session locks on the provider instance, so two providers over the same TodoStore and session could each load the same next_id, append colliding items, and drop the other save. Move the lock to a store/session/source_id map keyed per event loop, matching the file-access _store_write_lock pattern. Fixes microsoft#9868
NingLin Liu (ninglinLiu)
temporarily deployed
to
github-app-auth
October 30, 2026 04:24 — with
GitHub Actions
Inactive
NingLin Liu (ninglinLiu)
requested review from
Darren Cohen (dargilco),
Jose Alvarez (jpalvarezl) and
sophia-ramsey
as code owners
October 30, 2026 04:24
NingLin Liu (ninglinLiu)
temporarily deployed
to
github-app-auth
October 30, 2026 04:24 — with
GitHub Actions
Inactive
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
File-backed mutations use object-specific locks, allowing separate AgentSession objects for the same persisted session to allocate duplicate IDs and overwrite concurrent updates.
1 open issue
What changed in this PR
This PR addresses #9868 by moving Python todo mutation locks from individual providers to a shared registry, allowing providers using the same store and session object to coordinate updates.
Changes:
- Share locks by store, event loop, session object, and source ID.
- Use weak references so unused locks are released.
- Add tests for lock sharing and concurrent additions through two providers.
| File | Description |
|---|---|
python/packages/core/tests/core/test_harness_todo.py |
Adds shared-lock assertions and a two-provider file-store concurrency test. |
python/packages/core/agent_framework/_harness/_todo.py |
Replaces provider-local locks with a shared, event-loop-aware weak registry. |
Review effort: Balanced
| lock is in use, because whoever holds or awaits the lock still references the store | ||
| and session. | ||
| """ | ||
| key = (id(store), asyncio.get_running_loop(), id(session), source_id) |
Member
|
Closing since the issue has not been triaged |
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Motivation & Context
TodoProviderkept read-modify-write locks on the provider instance. The store is shareable, so two providers over one store and session each took their own lock, could load the samenext_id, and the second save could drop the first provider's items or reuse ids. Anasyncio.Locktied to one event loop also breaks when the same session is reused acrossasyncio.runboundaries.Description & Review Guide
WeakValueDictionarykeyed by(id(store), event loop, id(session), source_id), matching the file-access_store_write_lockpattern.TodoProvider._mutation_locknow returns that shared lock.todos_add/ complete / remove from different providers over the same store and session serialize correctly. Locks remain process-local and are not retained after they go unused (weak values).Related Issue
Fixes #9868
AI Assistance
AI assistance details: Assisted with implementation and tests; I reviewed the lock design against
_store_write_lock, verified lock sharing with a local script, and accept responsibility for the change.Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.