Skip to content

[Bug]: Idempotent EventQueueSink.close records KeyError as ERROR span #1133

Description

@dorlugasigal

What happened?

Calling EventQueueSink.close() more than once is functionally safe, but the
second close exports an OpenTelemetry ERROR span for
EventQueueSource.remove_sink.

EventQueueSink.close() documents that repeated calls are safe and suppresses
KeyError around remove_sink(). However, EventQueueSource is decorated
with @trace_class, so remove_sink() is traced separately. Its
set.remove() raises before the caller suppresses the exception, and the
telemetry wrapper records the exception and sets the span status to ERROR.

This produces a false error observation even though cleanup completes normally.
If the second cleanup happens after the request, the late span can also extend
the apparent trace latency.

Minimal reproduction

source = EventQueueSource(create_default_sink=False)
sink = await source.tap()

await sink.close(immediate=True)
await sink.close(immediate=True)
await source.close(immediate=True)

With A2A OpenTelemetry instrumentation enabled, the second call records:

a2a.server.events.event_queue_v2.EventQueueSource.remove_sink
KeyError: <EventQueueSink ...>
status: ERROR

The application does not receive the exception because close() suppresses it.

Expected behavior

Repeated or concurrent sink cleanup should not raise an expected exception
inside the traced remove_sink() method, and no ERROR span should be
exported for a successful idempotent close.

Proposed fix

Use set.discard() when detaching a sink. It preserves removal when the sink is
present and does nothing when cleanup already removed it.

Environment

  • a2a-sdk==1.1.0
  • Python 3.12
  • OpenTelemetry instrumentation enabled

Related issue

#1101 covers a broader subscriber-cleanup lifecycle problem. This report is
limited to the false ERROR span from the intentionally suppressed
remove_sink() KeyError.

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions