feat: Add experimental async transport (port of PR #4572) #5646
4 issues
find-bugs: Found 4 issues (4 medium)
Medium
`_patched_close()` calls `run_until_complete()` on a potentially stopped event loop - `sentry_sdk/integrations/asyncio.py:80-88`
The _patched_close() function attempts to call loop.run_until_complete(_flush()) when the event loop's close() method is called. However, close() is typically invoked after the loop has been stopped via loop.stop(). Calling run_until_complete() on a stopped loop will raise a RuntimeError ("This event loop is already running" or "Event loop stopped before Future completed"), which is caught by the broad exception handler but prevents the flush from completing successfully. This means Sentry events may be silently dropped during shutdown.
Missing pool return after SOCKS proxy failure causes TypeError - `sentry_sdk/transport.py:952-956`
When a SOCKS proxy is configured but socksio is not installed, the except RuntimeError block (lines 952-956) logs a warning but doesn't return a pool or clear the proxy_headers from opts. Control flow falls through to line 960 which returns AsyncConnectionPool(**opts). If proxy_headers was set (line 940), this will cause a TypeError because AsyncConnectionPool doesn't accept proxy_headers parameter. Additionally, unlike the sync HttpTransport which uses a flag pattern to explicitly handle the fallback case, this async implementation silently falls through.
_wait_flush returns early without waiting for active tasks when initial join succeeds - `sentry_sdk/worker.py:258-278`
When _wait_flush calls await asyncio.wait_for(self._queue.join(), timeout=initial_timeout) and it succeeds (returns within 0.1s), the method returns immediately. However, _queue.join() only waits until all items retrieved from the queue have had task_done() called. Since task_done() is called in _on_task_complete (after the async task completes processing), this should work correctly. However, if a new item is added to the queue between getting it and task_done() being called, and the queue becomes empty quickly, join() could return while active tasks are still processing. This is a timing edge case where flush may return before all HTTP requests complete.
Test uses Mock(spec=) which fails isinstance() check, causing test to fail - `tests/integrations/asyncio/test_asyncio.py:665-673`
The test creates mock_transport = Mock(spec=AsyncHttpTransport) (line 665), but the _flush() function in asyncio.py checks isinstance(client.transport, AsyncHttpTransport) which returns False for Mock objects even with a spec. This means _flush() returns early without calling close_async(), causing assertions on lines 672-673 to fail. The test needs to patch the isinstance check or use a different approach to mock the transport type.
Duration: 9m 27s · Tokens: 9.9M in / 69.4k out · Cost: $14.91 (+extraction: $0.00, +merge: $0.00, +fix_gate: $0.01)
Annotations
Check warning on line 88 in sentry_sdk/integrations/asyncio.py
github-actions / warden: find-bugs
`_patched_close()` calls `run_until_complete()` on a potentially stopped event loop
The `_patched_close()` function attempts to call `loop.run_until_complete(_flush())` when the event loop's `close()` method is called. However, `close()` is typically invoked after the loop has been stopped via `loop.stop()`. Calling `run_until_complete()` on a stopped loop will raise a `RuntimeError` ("This event loop is already running" or "Event loop stopped before Future completed"), which is caught by the broad exception handler but prevents the flush from completing successfully. This means Sentry events may be silently dropped during shutdown.
Check warning on line 956 in sentry_sdk/transport.py
github-actions / warden: find-bugs
Missing pool return after SOCKS proxy failure causes TypeError
When a SOCKS proxy is configured but socksio is not installed, the `except RuntimeError` block (lines 952-956) logs a warning but doesn't return a pool or clear the `proxy_headers` from `opts`. Control flow falls through to line 960 which returns `AsyncConnectionPool(**opts)`. If `proxy_headers` was set (line 940), this will cause a `TypeError` because `AsyncConnectionPool` doesn't accept `proxy_headers` parameter. Additionally, unlike the sync `HttpTransport` which uses a flag pattern to explicitly handle the fallback case, this async implementation silently falls through.
Check warning on line 278 in sentry_sdk/worker.py
github-actions / warden: find-bugs
_wait_flush returns early without waiting for active tasks when initial join succeeds
When `_wait_flush` calls `await asyncio.wait_for(self._queue.join(), timeout=initial_timeout)` and it succeeds (returns within 0.1s), the method returns immediately. However, `_queue.join()` only waits until all items retrieved from the queue have had `task_done()` called. Since `task_done()` is called in `_on_task_complete` (after the async task completes processing), this should work correctly. However, if a new item is added to the queue between getting it and `task_done()` being called, and the queue becomes empty quickly, `join()` could return while active tasks are still processing. This is a timing edge case where flush may return before all HTTP requests complete.
Check warning on line 673 in tests/integrations/asyncio/test_asyncio.py
github-actions / warden: find-bugs
Test uses Mock(spec=) which fails isinstance() check, causing test to fail
The test creates `mock_transport = Mock(spec=AsyncHttpTransport)` (line 665), but the `_flush()` function in `asyncio.py` checks `isinstance(client.transport, AsyncHttpTransport)` which returns False for Mock objects even with a spec. This means `_flush()` returns early without calling `close_async()`, causing assertions on lines 672-673 to fail. The test needs to patch the `isinstance` check or use a different approach to mock the transport type.