fix(observe): preserve asyncio.timeout awareness in async generator wrapper - #1690
Open
goingforstudying-ctrl wants to merge 5 commits into
Open
Conversation
goingforstudying-ctrl
force-pushed
the
fix/async-generator-timeout-awareness
branch
9 times, most recently
from
June 16, 2026 09:06
0d4e480 to
c486887
Compare
goingforstudying-ctrl
force-pushed
the
fix/async-generator-timeout-awareness
branch
5 times, most recently
from
June 25, 2026 18:09
4342eee to
dcc4e24
Compare
…rapper The _ContextPreservedAsyncGeneratorWrapper was creating a fresh asyncio.Task for every __anext__ call (via asyncio.create_task( context=...)). This changed the task identity between generator iterations, which silently broke asyncio.timeout / asyncio.timeout_at because those context managers bind to asyncio.current_task() at enter time and the bound task is already done by the next iteration. Replace the asyncio.create_task wrapping with a token-based approach that sets preserved contextvars directly on the current task. Context vars are task-local so they survive generator suspension points without changing the task identity. The same pattern is applied to aclose() for consistency. Closes langfuse/langfuse#13349
…r trade-off - Assert span.ended == 1 after TimeoutError in test_async_generator_wrapper_respects_asyncio_timeout - Expand docstring on _ContextPreservedAsyncGeneratorWrapper to note that context-var mutations across yield points are discarded between iterations
goingforstudying-ctrl
force-pushed
the
fix/async-generator-timeout-awareness
branch
from
June 29, 2026 10:29
dcc4e24 to
0d4ca52
Compare
…am sync Signed-off-by: goingforstudying-ctrl <goingforstudying@gmail.com>
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.
@observecurrently resumes every async-generator iteration in a new task. Anasyncio.timeoutentered inside the generator therefore targets a task that has already completed after the first yield, allowing later iterations to run past the deadline.Resume the generator's awaitable in the consumer's existing task while running each
send,throw, andclosein the preservedcontextvars.Context. This keeps task-bound timeouts effective and preserves context-variable values and tokens across yields, including OpenTelemetry child spans, without leaking generator context into the caller.aclose()uses the same mechanism and retains the current main branch's cleanup and span-finalization behavior.Regression coverage includes timeouts entered inside the generator (both completion and expiry), task identity, context mutations and newly introduced context variables across yields, token reset during cleanup, cancellation,
GeneratorExitdelegation, explicit close, and child-span parenting/cleanup through the public@observedecorator. The timeout and task-identity regressions were confirmed failing against upstream main before applying the fix. Cancellation tests now synchronize on an event instead of assuming a fixed number of inner-task scheduling steps.Related issue: langfuse/langfuse#13349
Validation:
uv sync --lockeduv run --frozen ruff check .— passed.uv run --frozen ruff format --check langfuse/_client/observe.py tests/unit/test_observe.py— passed.uv run --frozen mypy langfuse --no-error-summary— passed.uv run --frozen pytest -n 2 --dist worksteal tests/unit --timeout=60with the CI test-only environment values, Python 3.12 — 694 passed, 2 skipped.uv run --python 3.10 --frozen pytest -q tests/unit/test_observe.py --timeout=20in a separate environment — 19 passed, 2 skipped (asyncio.timeoutrequires Python 3.11).Full-repository
ruff format --check .reports existing formatting intests/unit/test_media.py, which is unchanged from upstream main. E2E and live-provider suites were not run locally because they require a Langfuse server and provider credentials; the changed tracing behavior is covered by local in-memory exporter tests.