Skip to content

fix(instrumentation): keep Qdrant async spans open until completion - #4659

Open
GaliFFun wants to merge 2 commits into
traceloop:mainfrom
GaliFFun:fix/qdrant-async-spans
Open

GaliFFun wants to merge 2 commits into
traceloop:mainfrom
GaliFFun:fix/qdrant-async-spans

Conversation

@GaliFFun

@GaliFFun GaliFFun commented Oct 25, 2026 •

Copy link
Copy Markdown

Fixes #4658

Summary

AsyncQdrantClient.upsert() currently ends its Qdrant span as soon as the client returns a coroutine. The actual request then runs outside that span: durations are nearly zero, transport spans inherit the caller, and exceptions raised during await leave an OK span.

Add an async wrapper that awaits the operation inside start_as_current_span. Choose the wrapper using the actual method's coroutine status, since AsyncQdrantClient also exposes synchronous upload methods. Share request-attribute handling between the sync and async wrappers.

Span names, attributes, method coverage, and public APIs stay the same. No documentation or configuration change is needed.

Tests

  • 16 new regression cases, covering span lifetime while a request is pending, parent/child context, delayed exceptions, cancellation, suppression, unawaited calls, empty results, synchronous upload methods, and uninstrumentation.
  • Before the fix: 9 failures, 11 passes. After the fix: all 20 tests pass, including the 4 existing synchronous tests.
  • All 20 pass on Python 3.10.18, 3.11.9, and 3.12.11 with wrapt 2.5.1, using both the C extension and Python fallback.
  • All 20 also pass on Python 3.10 with wrapt 1.17.3, using both implementations.
  • Nx package lint, test, and build-release targets pass. The built wheel was installed in a fresh environment outside the checkout; all 20 copied tests pass against the installed package.
  • Qdrant client 1.11.3; real client calls use a deterministic HTTP mock or the in-memory client. No live Qdrant server is required.

The upstream lockfile is already stale: uv lock --check fails on the unchanged base. Local validation used temporary environments resolved from the existing pyproject constraints. This PR does not change dependencies or lockfiles.

Screenshots

Captured from Jaeger 2.6.0 using the real client and instrumentor with httpx.MockTransport and a controlled 250 ms delay. These show instrumentation lifecycle behavior, not live Qdrant server latency. The standalone reproducer is included in #4658.

Successful request: before and after

Before: the Qdrant span lasts 15 microseconds; the 251 ms transport span is its sibling.

Successful request before the fix

After: the Qdrant span lasts 262.125 ms and parents the transport span.

Successful request after the fix

HTTP 500: before and after

Before: the Qdrant span lasts 14 microseconds, remains OK, and has no exception event.

Failed request before the fix

After: the Qdrant span lasts 261.740 ms, has status ERROR, and records one exception event.

Failed request after the fix

  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change.
  • PR name follows conventional commits format: feat(instrumentation): ... or fix(instrumentation): ....
  • (If applicable) I have updated the documentation accordingly. (Not applicable: no public API or configuration changes.)

Summary by CodeRabbit

  • New Features
    • Added tracing support for asynchronous Qdrant client calls, bringing them in line with synchronous calls. Spans capture request details and results, preserve parent-child relationships while requests are awaited, and record failures. Instrumentation suppression and cleanup after cancellation or uninstrumentation are also supported.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fc834b8f-bfa4-49e5-89a3-0a85f03d53ae
Commits

Reviewing files that changed from the base of the PR and between 048a394 and 8525fa7.

Files selected for processing: 3
  • packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/__init__.py
  • packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/wrapper.py
  • packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_async_instrumentation.py
Files skipped from review as they are similar to previous changes: 3
  • packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/init.py
  • packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_async_instrumentation.py
  • packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/wrapper.py

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

Walkthrough

The Qdrant instrumentor now selects an asynchronous wrapper for coroutine methods. The wrapper awaits each operation within a Qdrant span and records request attributes. Tests cover async calls, errors, cancellation, suppression, and method restoration.

Changes: Qdrant async tracing

Layer / File(s) Summary
Wrapper selection and span recording
packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/__init__.py, packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/wrapper.py
The instrumentor selects _awrap for coroutine methods and _wrap for other methods. Both wrappers use shared request-attribute logic. _awrap awaits the operation inside a Qdrant span.
Async instrumentation validation
packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_async_instrumentation.py
Tests cover span timing and parentage, request errors, cancellation, suppression, unawaited calls, empty results, upload methods, and restoration after uninstrumentation.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant QdrantInstrumentor
  participant AsyncQdrantClient
  participant _awrap
  participant HTTPTransport
  QdrantInstrumentor->>AsyncQdrantClient: wraps coroutine method with _awrap
  Caller->>AsyncQdrantClient: awaits Qdrant operation
  AsyncQdrantClient->>_awrap: invokes wrapped method
  _awrap->>HTTPTransport: awaits operation inside Qdrant span
  HTTPTransport-->>_awrap: returns response or raises error
  _awrap-->>Caller: returns response or propagates error
Loading

Possibly related issues

Suggested reviewers: anush008

Merge Risk: Minimal · up to 8525f

No identified issue currently blocks merging the Qdrant async span fix, subject to normal checks.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the main change: Qdrant async instrumentation spans now stay open until the awaited operation completes. The raw summary confirms an async wrapper awaits the operation inside start_as_…
Linked Issues check Passed Issue [#4658] is the only active directly linked issue. The implementation selects _awrap for coroutine methods and _wrap for synchronous methods. _awrap awaits the Qdrant operation inside `star…
Out of Scope Changes check Passed The changes stay within issue [#4658]. The wrapper-selection logic addresses mixed synchronous and asynchronous methods on AsyncQdrantClient. _set_request_attributes removes duplicated attribute d…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 41 functions across 3 files.
Full details: Title check

Explanation

The title names the main change: Qdrant async instrumentation spans now stay open until the awaited operation completes. The raw summary confirms an async wrapper awaits the operation inside start_as_current_span, and the linked issue #4658 asks for the same span lifecycle fix. The title is specific, uses a conventional commit prefix, and is 67 characters long, which is within a reasonable length.

Full details: Linked Issues check

Explanation

Issue [#4658] is the only active directly linked issue. The implementation selects _awrap for coroutine methods and _wrap for synchronous methods. _awrap awaits the Qdrant operation inside start_as_current_span, so the span remains current until completion. Exceptions during the await pass through the span context and receive ERROR status with an exception event. The coroutine wrapper starts no span before the coroutine is awaited. The new tests cover pending requests, parentage, errors, cancellation, unawaited calls, suppression, synchronous upload methods, and uninstrumentation.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue [#4658]. The wrapper-selection logic addresses mixed synchronous and asynchronous methods on AsyncQdrantClient. _set_request_attributes removes duplicated attribute dispatch while preserving the existing method coverage and attribute names. The regression tests verify the requested lifecycle behavior. Added docstrings describe the changed instrumentation code. No unrelated API, dependency, configuration, method-coverage, or compatibility changes are present.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@CLAassistant

CLAassistant commented Oct 25, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@GaliFFun
GaliFFun force-pushed the fix/qdrant-async-spans branch from 9fd6118 to 048a394 Compare October 25, 2026 12:34
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.

🐛 Bug Report: Qdrant async spans end before the operation runs

2 participants