Skip to content

telemetry: fix spurious 'Failed to detach context' error on shutdown - #77

Open
andre-merzky wants to merge 2 commits into
mainfrom
fix/telemetry_teardown
Open

telemetry: fix spurious 'Failed to detach context' error on shutdown#77
andre-merzky wants to merge 2 commits into
mainfrom
fix/telemetry_teardown

Conversation

@andre-merzky

Copy link
Copy Markdown
Member

Summary

TelemetryManager.stop() logged a spurious OpenTelemetry error on every otherwise-clean shutdown whenever it ran in a different asyncio task than start():

ERROR:    Failed to detach context
...
ValueError: <Token var=<ContextVar name='current_context' ...> was created in a different Context

ContextVar tokens can only be reset from the contextvars.Context (i.e. the asyncio task) that created them. Under the ORBIT rhapsody plugin, session init and close are separate requests handled by separate tasks, so the cross-task detach failed every time. The ValueError is caught inside opentelemetry.context.detach() and logged as an ERROR there, so a try/except at the call site cannot silence it — the detach must be skipped instead.

Fix

Record the attaching task in start(); stop() detaches only when it runs in that same task, and otherwise just drops the token. Skipping is safe: the attach only ever affected the starting task's context, and the tracer provider shuts down immediately afterwards.

Verification

  • Repro script (start in one task, stop in another): unpatched code logs the exact reported error; patched code shuts down silently.
  • tests/unit/telemetry: 90 passed with opentelemetry installed (1 failure in test_emits_resource_update is pre-existing — fails identically on unpatched main in this environment).
  • Full unit suite: unchanged vs main (remaining failures/errors are missing-dask / Dragon-runtime environment issues, verified pre-existing).

🤖 Generated with Claude Code

ContextVar tokens can only be reset from the contextvars.Context (i.e.
the asyncio task) that created them.  When TelemetryManager.start() and
stop() run in different tasks -- the normal case under the ORBIT rhapsody
plugin, where session init and close are separate requests -- the detach
in stop() raised ValueError inside opentelemetry.context.detach(), which
OTel catches and logs as 'ERROR: Failed to detach context' on every
otherwise-clean shutdown.

Record the attaching task in start() and detach only when stop() runs in
that same task.  Skipping the detach is safe: the attach only ever
affected the starting task's context, and the tracer provider is shut
down immediately afterwards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the telemetry manager to track the asyncio task that attached the OpenTelemetry session context token, ensuring that the context is only detached if stop() is called from the same task that executed start(). This prevents spurious ValueError logs at shutdown. The review feedback suggests using a weak reference (weakref.ref) to store the task instead of a strong reference to avoid potential memory leaks, along with the corresponding dereferencing logic during the identity check.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/rhapsody/telemetry/manager.py Outdated
self._session_ctx_token = otel_context.attach(
trace_mod.set_span_in_context(self._session_span)
)
self._session_ctx_task = asyncio.current_task()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Storing a strong reference to an asyncio.Task object can prevent the task, its coroutine, and all local variables in its call stack from being garbage collected, potentially causing a memory leak in long-running applications. Using a weak reference instead avoids this issue while still allowing identity comparison.

Suggested change
self._session_ctx_task = asyncio.current_task()
import weakref
_task = asyncio.current_task()
self._session_ctx_task = weakref.ref(_task) if _task is not None else None

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9161618 — task is now held as a weakref.ref, with the import at module level.

Comment thread src/rhapsody/telemetry/manager.py Outdated
Comment on lines +335 to +336
if asyncio.current_task() is self._session_ctx_task:
otel_context.detach(self._session_ctx_token)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Dereference the weak reference to the task before performing the identity check. If the task has already been garbage collected, the weak reference will resolve to None, and the detach step will be safely skipped.

Suggested change
if asyncio.current_task() is self._session_ctx_task:
otel_context.detach(self._session_ctx_token)
_attached_task = self._session_ctx_task() if self._session_ctx_task is not None else None
if _attached_task is not None and asyncio.current_task() is _attached_task:
otel_context.detach(self._session_ctx_token)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9161618 — the weakref is dereferenced before the identity check; a collected task resolves to None and the detach is skipped.

A strong reference would pin the starting task's coroutine frame and
locals for the entire session lifetime; the weakref keeps the identity
check and resolves to None once the task is gone, in which case the
detach is skipped just like any other cross-task stop().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andre-merzky

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the telemetry manager to avoid spurious OpenTelemetry errors during shutdown. It introduces a weak reference to the asyncio task that starts the telemetry session, ensuring that detaching the OpenTelemetry context token in stop() is only attempted if it is executed within the same task that attached it. I have no feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant