feat: Add experimental async transport (port of PR #4572) #5646
7 issues
Medium
close_async() and __aexit__ silently skip cleanup for sync transport - `sentry_sdk/client.py:1066-1073`
close_async() returns early without calling _close_components() or transport.kill() when used with a sync transport. This means if someone uses async with client: context manager with a sync transport, neither the session flusher, batchers, monitor, nor the transport itself will be properly cleaned up. The sync close() should be called as a fallback to ensure resources are released.
Also found at:
tests/integrations/asyncio/test_asyncio.py:665-673
httpcore[asyncio] added to common environment incompatible with Python 3.6 and 3.7 - `tox.ini:340`
The change adds common: httpcore[asyncio] as a dependency for all common test environments. However, httpcore 1.x (which is specified in setup.py via httpcore[asyncio]==1.*) requires Python >=3.8. The common environment includes Python 3.6 and 3.7 configurations (line 21: {py3.6,py3.7,...}-common), which will cause pip installation failures when tox tries to install this dependency on those Python versions.
SOCKS proxy failure silently bypasses proxy and connects directly - `sentry_sdk/transport.py:952-956`
When a SOCKS proxy is configured but httpcore.AsyncSOCKSProxy raises RuntimeError (e.g., socksio not installed), the code logs a warning but then falls through to return httpcore.AsyncConnectionPool(**opts) on line 960, silently establishing a direct connection without any proxy. Users who configure a SOCKS proxy typically expect all traffic to go through it (e.g., for privacy, corporate firewall compliance, or security reasons). Silently bypassing the proxy could result in unintended data exposure or security policy violations.
Test will fail: isinstance() check on Mock object returns False - `tests/integrations/asyncio/test_asyncio.py:665-673`
The test test_loop_close_flushes_async_transport uses Mock(spec=AsyncHttpTransport) for the transport. However, the _flush() function in patch_loop_close() uses isinstance(client.transport, AsyncHttpTransport) to check the transport type. Since a Mock with spec is not an actual instance of AsyncHttpTransport, isinstance() returns False, causing the function to return early without calling client.close_async(). The subsequent assertions mock_client.close_async.assert_called_once() and mock_client.close_async.assert_awaited_once() will fail because close_async is never called.
Low
Missing test coverage for async transport fallback scenarios - `sentry_sdk/transport.py:1155-1175`
The make_transport function has multiple fallback paths when async transport is requested but cannot be used: (1) no event loop running, (2) AsyncioIntegration not enabled, (3) httpcore[asyncio] not installed. None of these fallback paths have explicit test coverage to verify the warning messages are logged and sync transport is correctly used instead. This could lead to regressions in the fallback behavior going unnoticed.
Race condition in close_async() can cause AttributeError - `sentry_sdk/client.py:1076`
In close_async(), between the await of flush_async() (line 1074) and the call to self.transport.kill() (line 1076), another concurrent call to close_async() could set self.transport to None. This would cause an AttributeError when trying to call kill() on None. While this requires unusual concurrent usage of close_async(), it violates the principle of defensive coding.
Missing test finalizer causes test pollution - `tests/test_transport.py:1074`
The test test_async_transport_concurrent_requests sets the client on the global scope via sentry_sdk.get_global_scope().set_client(client) at line 1074, but unlike similar tests (e.g., lines 125-126, 971-972, 1097-1098), it does not register a finalizer to clean up the global scope. This can cause test pollution where subsequent tests may inherit a client with a closed transport.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 3 | 9m 24s | $8.08 |
| find-bugs | 4 | 17m 46s | $15.46 |
| skill-scanner | 0 | 8m 11s | $2.57 |
| security-review | 0 | 16m 56s | $4.55 |
Duration: 52m 18s · Tokens: 21.0M in / 176.6k out · Cost: $30.72 (+extraction: $0.02, +merge: $0.00, +fix_gate: $0.02, +dedup: $0.02)