fix(a2a/events): guard A2aEventQueue with threading.Lock to prevent concurrent iteration crash
CI / benchmark-publish (pull_request) Has been skipped
CI / helm (pull_request) Successful in 35s
CI / lint (pull_request) Successful in 1m17s
CI / quality (pull_request) Successful in 1m18s
CI / security (pull_request) Successful in 1m27s
CI / build (pull_request) Successful in 1m8s
CI / typecheck (pull_request) Successful in 2m16s
CI / push-validation (pull_request) Successful in 26s
CI / unit_tests (pull_request) Failing after 4m32s
CI / docker (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 4m49s
CI / e2e_tests (pull_request) Successful in 4m48s
CI / coverage (pull_request) Successful in 9m52s
CI / status-check (pull_request) Failing after 3s
CI / benchmark-regression (pull_request) Successful in 1h5m55s
CI / benchmark-publish (pull_request) Has been skipped
CI / helm (pull_request) Successful in 35s
CI / lint (pull_request) Successful in 1m17s
CI / quality (pull_request) Successful in 1m18s
CI / security (pull_request) Successful in 1m27s
CI / build (pull_request) Successful in 1m8s
CI / typecheck (pull_request) Successful in 2m16s
CI / push-validation (pull_request) Successful in 26s
CI / unit_tests (pull_request) Failing after 4m32s
CI / docker (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 4m49s
CI / e2e_tests (pull_request) Successful in 4m48s
CI / coverage (pull_request) Successful in 9m52s
CI / status-check (pull_request) Failing after 3s
CI / benchmark-regression (pull_request) Successful in 1h5m55s
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""A2A event streaming — local queue, SSE formatting, and remote stub.
|
||||
"""A2A event streaming - local queue, SSE formatting, and remote stub.
|
||||
|
||||
The :class:`A2aEventQueue` provides a working in-memory event queue for
|
||||
local mode and a stub for remote subscriptions that raises
|
||||
@@ -62,30 +62,32 @@ class A2aEventQueue:
|
||||
self._lock: threading.Lock = threading.Lock()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Local-mode operations (working)
|
||||
# Local-mode operations (thread-safe)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
"""Whether this queue has been closed."""
|
||||
return self._is_closed
|
||||
with self._lock:
|
||||
return self._is_closed
|
||||
|
||||
def publish(self, event: A2aEvent) -> None:
|
||||
"""Append *event* to the local queue and notify subscribers.
|
||||
|
||||
The subscriber dict is snapshotted inside the lock so that concurrent
|
||||
:meth:`subscribe_local` / :meth:`unsubscribe` calls on other threads
|
||||
cannot mutate it while we iterate. Callbacks are invoked *outside*
|
||||
cannot mutate it while we iterate. The close check and type check
|
||||
are also performed inside the lock. Callbacks are invoked *outside*
|
||||
the lock to avoid deadlocks when a callback itself modifies
|
||||
subscriptions.
|
||||
"""
|
||||
if self._is_closed:
|
||||
raise RuntimeError("Cannot publish to a closed event queue")
|
||||
if not isinstance(event, A2aEvent):
|
||||
raise TypeError("event must be an A2aEvent instance")
|
||||
with self._lock:
|
||||
self._events.append(event)
|
||||
if self._is_closed:
|
||||
raise RuntimeError("Cannot publish to a closed event queue")
|
||||
if not isinstance(event, A2aEvent):
|
||||
raise TypeError("event must be an A2aEvent instance")
|
||||
callbacks = list(self._subscriptions.items())
|
||||
self._events.append(event)
|
||||
logger.debug(
|
||||
"a2a.event.published",
|
||||
event_id=event.event_id,
|
||||
@@ -175,12 +177,12 @@ class SseEventFormatter:
|
||||
|
||||
Two trailing newlines terminate the event per the EventSource spec.
|
||||
The data payload follows JSON-RPC 2.0 notification format per the A2A
|
||||
protocol specification (§Streaming Architecture).
|
||||
protocol specification (Streaming Architecture).
|
||||
"""
|
||||
|
||||
# Mapping from A2A event type names to JSON-RPC 2.0 method strings.
|
||||
# Per spec §Streaming Architecture: TaskStatusUpdateEvent → task/statusUpdate,
|
||||
# TaskArtifactUpdateEvent → task/artifactUpdate.
|
||||
# Per spec Streaming Architecture: TaskStatusUpdateEvent -> task/statusUpdate,
|
||||
# TaskArtifactUpdateEvent -> task/artifactUpdate.
|
||||
_EVENT_TYPE_TO_METHOD: ClassVar[dict[str, str]] = {
|
||||
"TaskStatusUpdateEvent": "task/statusUpdate",
|
||||
"TaskArtifactUpdateEvent": "task/artifactUpdate",
|
||||
|
||||
Reference in New Issue
Block a user