fix: serialize concurrent cancel requests with a per-task lock - #1185
Open
ez-lbz wants to merge 2 commits into
Open
fix: serialize concurrent cancel requests with a per-task lock#1185ez-lbz wants to merge 2 commits into
ez-lbz wants to merge 2 commits into
Conversation
🧪 Code Coverage (vs
|
| Base | PR | Delta | |
|---|---|---|---|
| src/a2a/server/events/event_queue_v2.py | 91.79% | 91.28% | 🔴 -0.51% |
| src/a2a/server/request_handlers/default_request_handler.py | 98.13% | 98.26% | 🟢 +0.12% |
| src/a2a/utils/telemetry.py | 91.47% | 90.70% | 🔴 -0.78% |
| Total | 93.00% | 92.99% | ⚪️ -0.00% |
Generated by coverage-comment.yml
The ref-counted per-task cancel lock was typed as a heterogeneous list (`list[asyncio.Lock | int]`), which the type checker cannot narrow by index (entry[0] vs entry[1]). Replace it with a small dataclass carrying the lock and the in-flight user count, and add the TOCTOU acronym to the spellcheck allow list.
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.
What changed
1. Serialize the cancel check-then-act with a per-task lock
Problem:
LegacyRequestHandler.on_cancel_task()(src/a2a/server/request_handlers/default_request_handler.py) performed "load task → check terminal state → callagent_executor.cancel()" without any synchronization. Two concurrent cancels of the same task could both pass the terminal-state check and both issue a cancel, duplicating the cancellation side effects (TOCTOU race).Fix (src/a2a/server/request_handlers/default_request_handler.py):
_cancel_locks) so the entire check-then-act critical section inon_cancel_taskis atomic per task. The first cancel to acquire the lock performs the cancel; any concurrent cancel waits, then re-reads the task state and fails withTaskNotCancelableErrorif the task is now terminal.DefaultRequestHandlerV2is unaffected: cancels there are already serialized perActiveTaskbyActiveTaskRegistry/ActiveTask.Testing
./.venv/Scripts/python -m pytest tests/server/request_handlers/test_default_request_handler.py -q→ 75 passed (includes newtest_on_cancel_task_serializes_concurrent_cancels: two concurrent cancels, the first blocks inside the executor, and the second must observe the resulting terminal state and raiseTaskNotCancelableErrorwithout ever reaching the executor; also asserts the per-task lock entry is cleaned up afterwards)../.venv/Scripts/python -m ruff checkon modified files: clean (the pre-existingtoo-many-positional-argumentsfinding on the handler__init__exists onmain).TaskNotCancelableErrorinstead of issuing a duplicate cancel.