feat: Add experimental async transport (port of PR #4572) #5646
5 issues
code-review: Found 5 issues (1 high, 3 medium, 1 low)
High
Client reports never flushed in AsyncHttpTransport.flush() - coroutine not awaited - `sentry_sdk/transport.py:878`
At line 878, lambda: self._flush_client_reports(force=True) submits a sync lambda that returns a coroutine without awaiting it. The AsyncWorker._process_callback method awaits the callback itself (await callback()), but since the lambda is synchronous, this completes immediately and returns the coroutine object from _flush_client_reports, which is then discarded without being awaited. This means client reports are silently never sent during flush operations.
Medium
RuntimeError when calling run_until_complete on a closed loop - `sentry_sdk/integrations/asyncio.py:80-88`
The _patched_close() function calls loop.run_until_complete(_flush()) on the loop that is being closed. However, if the loop is already stopped (but not yet closed), or has pending callbacks that haven't been drained, this can cause a RuntimeError with message 'This event loop is already running' in some scenarios (e.g., nested event loops or when called from within an async context). While the try/except block catches exceptions, the root cause is that run_until_complete is being called on a loop that may be in an inconsistent state.
Test will fail: isinstance() check fails on Mock with spec - `tests/integrations/asyncio/test_asyncio.py:665-667`
The test uses Mock(spec=AsyncHttpTransport) but Python's isinstance() does not consider mock objects with specs as instances of the spec class. In _flush(), the check if not isinstance(client.transport, AsyncHttpTransport) evaluates to True (since the mock is not a real AsyncHttpTransport instance), causing an early return before close_async() is called. The assertion mock_client.close_async.assert_called_once() will fail.
httpcore[asyncio] dependency may break py3.6/py3.7 test environments - `tox.ini:340`
The common: httpcore[asyncio] dependency (line 340) is added without a Python version constraint, but httpcore 1.x requires Python 3.8+. The common test environment runs on py3.6 and py3.7 (line 21). This will likely cause pip to fail when creating those test environments since it cannot satisfy the httpcore dependency. Consider using a version-specific constraint like {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t}-common: httpcore[asyncio].
Low
Test lacks global scope client cleanup - `tests/test_transport.py:1030`
The test test_async_transport_background_thread_capture sets sentry_sdk.get_global_scope().set_client(client) but does not clean up the global scope after the test completes. This could cause test pollution if subsequent tests depend on global scope state. The similar test test_transport_works_async in the same PR properly uses request.addfinalizer for cleanup.
Also found at:
tests/test_transport.py:1061
Duration: 13m 7s · Tokens: 4.9M in / 55.9k out · Cost: $7.62 (+extraction: $0.00, +merge: $0.00, +fix_gate: $0.01)
Annotations
Check failure on line 878 in sentry_sdk/transport.py
github-actions / warden: code-review
Client reports never flushed in AsyncHttpTransport.flush() - coroutine not awaited
At line 878, `lambda: self._flush_client_reports(force=True)` submits a sync lambda that returns a coroutine without awaiting it. The `AsyncWorker._process_callback` method awaits the callback itself (`await callback()`), but since the lambda is synchronous, this completes immediately and returns the coroutine object from `_flush_client_reports`, which is then discarded without being awaited. This means client reports are silently never sent during flush operations.
Check warning on line 88 in sentry_sdk/integrations/asyncio.py
github-actions / warden: code-review
RuntimeError when calling run_until_complete on a closed loop
The `_patched_close()` function calls `loop.run_until_complete(_flush())` on the loop that is being closed. However, if the loop is already stopped (but not yet closed), or has pending callbacks that haven't been drained, this can cause a `RuntimeError` with message 'This event loop is already running' in some scenarios (e.g., nested event loops or when called from within an async context). While the try/except block catches exceptions, the root cause is that `run_until_complete` is being called on a loop that may be in an inconsistent state.
Check warning on line 667 in tests/integrations/asyncio/test_asyncio.py
github-actions / warden: code-review
Test will fail: isinstance() check fails on Mock with spec
The test uses `Mock(spec=AsyncHttpTransport)` but Python's `isinstance()` does not consider mock objects with specs as instances of the spec class. In `_flush()`, the check `if not isinstance(client.transport, AsyncHttpTransport)` evaluates to `True` (since the mock is not a real AsyncHttpTransport instance), causing an early return before `close_async()` is called. The assertion `mock_client.close_async.assert_called_once()` will fail.
Check warning on line 340 in tox.ini
github-actions / warden: code-review
httpcore[asyncio] dependency may break py3.6/py3.7 test environments
The `common: httpcore[asyncio]` dependency (line 340) is added without a Python version constraint, but httpcore 1.x requires Python 3.8+. The common test environment runs on py3.6 and py3.7 (line 21). This will likely cause pip to fail when creating those test environments since it cannot satisfy the httpcore dependency. Consider using a version-specific constraint like `{py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t}-common: httpcore[asyncio]`.