Skip to content

fix(tracing): Find query source across the greenlet that async SQLAlchemy runs the driver in - #8237

Open
ryanhiizy wants to merge 6 commits into
getsentry:masterfrom
ryanhiizy:fix/query-source-greenlet-parent
Open

ryanhiizy wants to merge 6 commits into
getsentry:masterfrom
ryanhiizy:fix/query-source-greenlet-parent

Conversation

@ryanhiizy

@ryanhiizy ryanhiizy commented Nov 10, 2026 •

Copy link
Copy Markdown

Description

As discussed in #8205 (thanks @sentrivana).

With async SQLAlchemy, DB spans never get query source (code.function, code.filepath, …), because add_source never finds an application frame.

SQLAlchemy's asyncio extension runs the sync driver inside a greenlet that it starts from the coroutine awaiting the query. The cursor events that create the span run in that greenlet. Walking f_back from there reaches only SQLAlchemy and greenlet frames, then stops at the greenlet's boundary, so it never sees the application code that issued the query.

When the current greenlet is SQLAlchemy's bridge, add_source now continues into the parent greenlet's suspended frames, which hold the awaiting coroutine. Other greenlets aren't followed, since a greenlet's parent isn't necessarily its caller and following it could attribute the span to unrelated code. It only reads frames, and it does nothing if greenlet isn't imported or the lookup fails.

The bridge is recognised by __sqlalchemy_greenlet_provider__, the attribute SQLAlchemy checks itself, which it sets from 1.4.53 and 2.0.31. Earlier releases don't set it, so there it is recognised by its class, _AsyncIoGreenlet from a sqlalchemy module. I checked both paths locally against 1.4.52 and 2.0.30, where the test fails without the class check.

The test runs a query through SQLAlchemy's real greenlet_spawn from a coroutine and checks the coroutine is its source, for both static and streamed spans; it fails on master.

It skips only before SQLAlchemy 1.4, not when greenlet is missing, so it fails instead of silently not running if greenlet ever drops out of the 1.4+ environments.

greenlet is only an optional extra of SQLAlchemy 2.x, and 1.4 installs it only on some platforms (not macOS arm64), so I added it to the 1.4+ test environments in scripts/populate_tox/config.py. In tox.ini I applied only the lines the generator adds for it, leaving out the 2.1.6 → 2.1.8 bump it would also make, to keep this PR scoped. Happy to commit the full regeneration instead if you prefer; a full run currently also bumps most other integrations.

With the reproduction from the issue (SQLAlchemy 2.1.6, aiosqlite):

master:  SELECT 1 -> None
this PR: SELECT 1 -> async_query

Issues


Written with Claude (Opus 5.5). I reviewed the change and ran the reproduction and the SQLAlchemy 1.2, 1.4, 2.0 and 2.1 test suites locally before opening this.

@ryanhiizy
ryanhiizy requested a review from a team as a code owner November 10, 2026 22:03
… bridge

A greenlet's parent is not necessarily its caller, so following any parent
could attribute a span to unrelated code. Only SQLAlchemy's bridge greenlet,
which it marks with __sqlalchemy_greenlet_provider__, is known to be spawned
by the coroutine awaiting the query.

Test through SQLAlchemy's real greenlet_spawn, add a case where an unrelated
parent gives no source, and install greenlet for the SQLAlchemy 2.x test
environments, where it is only an optional extra.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dc44691. Configure here.

Comment thread sentry_sdk/tracing_utils.py Resolved
….0.31

Earlier releases don't set __sqlalchemy_greenlet_provider__ on the bridge
greenlet, so identify it by its class there.
…hout greenlet

Skip on SQLAlchemy before 1.4 only, so the tests can't silently stop
running if greenlet drops out of the 1.4+ environments.
SQLAlchemy 1.4 only depends on greenlet on some platforms, not macOS
arm64, where the async bridge tests would otherwise fail.

This branch has not been deployed

No deployments
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.

Query source is never set on DB spans from async SQLAlchemy

1 participant