You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[util-genai] Abandoned streams finalize without closing the wrapped generator #1035
This issue was generated with AI assistance and requires investigation and confirmation prior to being worked on.
When a stream is abandoned, _StreamTelemetry.__del__ ends the invocation without closing the wrapped stream. For generator-backed streams, the generator's cleanup (finally / GeneratorExit handling) then runs after the span has ended and outside the invocation's context, or stays pending while another reference holds the generator. Spans started during that cleanup lose their parent.
Repro (SyncAgentStreamWrapper from #1033, but the behavior is inherited by every SyncStreamWrapper / AsyncStreamWrapper):
defagent_run():
try:
yield"a"; yield"b"finally:
withtracer.start_as_current_span("cleanup-work"):
passinv=handler.invoke_local_agent(agent_name="A")
gen=agent_run()
for_inW(gen, inv):
breakgc.collect() # invoke_agent A ends (error.type=_OTHER)delgengc.collect() # cleanup runs now: agent span already ended, cleanup-work has no parent
Possible direction: close the wrapped stream under _execution_context() before finalizing in __del__. The sync case can call close(); the async case can't await from __del__, so it needs a different approach (e.g. scheduling aclose() on the running loop, or relying on the loop's async generator finalizer hook).
I can take the sync part if you aren't planning to handle it yourself, subject to maintainer agreement on the behavior change.
The November 6 validation on cd79a039 used fake generators through a SyncStreamWrapper subclass, with suspended and attached invocations. With the regression tests applied, uv run --no-sync pytest util/opentelemetry-util-genai/tests/test_stream.py -q -k closes_wrapped_generator failed both cases in each of three runs (exit 1). With the local sync patch, both passed in each of three runs (exit 0). uv run --no-sync pytest util/opentelemetry-util-genai/tests/test_stream.py -q -k cleanup_span_is_child passed all four full-consumption and explicit-close controls in three runs before and three after (exit 0).
These are prior validation results. They don't cover real agent instrumentation or retained external generator references. Async and tool-wrapper changes are outside that validation's scope. I'd limit my contribution to your proposed sync change for now. For async cleanup, do the maintainers prefer scheduling aclose() on a running loop or relying on its async generator finalizer hook?
Activity
sangkyoonnam commentedon Nov 6, 2026
I can take the sync part if you aren't planning to handle it yourself, subject to maintainer agreement on the behavior change.
The November 6 validation on
cd79a039used fake generators through aSyncStreamWrappersubclass, with suspended and attached invocations. With the regression tests applied,uv run --no-sync pytest util/opentelemetry-util-genai/tests/test_stream.py -q -k closes_wrapped_generatorfailed both cases in each of three runs (exit 1). With the local sync patch, both passed in each of three runs (exit 0).uv run --no-sync pytest util/opentelemetry-util-genai/tests/test_stream.py -q -k cleanup_span_is_childpassed all four full-consumption and explicit-close controls in three runs before and three after (exit 0).These are prior validation results. They don't cover real agent instrumentation or retained external generator references. Async and tool-wrapper changes are outside that validation's scope. I'd limit my contribution to your proposed sync change for now. For async cleanup, do the maintainers prefer scheduling
aclose()on a running loop or relying on its async generator finalizer hook?