Skip to content

feat: Add experimental async transport (port of PR #4572) #1746

feat: Add experimental async transport (port of PR #4572)

feat: Add experimental async transport (port of PR #4572) #1746

Triggered via pull request March 12, 2026 16:32
@BYKBYK
synchronize #5646
Status Success
Total duration 51s
Artifacts

changelog-preview.yml

on: pull_request_target
changelog-preview  /  preview
47s
changelog-preview / preview
Fit to window
Zoom out
Zoom in

Annotations

1 error and 8 warnings
Client reports never flushed in AsyncHttpTransport.flush() - coroutine not awaited: sentry_sdk/transport.py#L878
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.
changelog-preview / preview
Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
`_patched_close()` calls `run_until_complete()` on a potentially stopped event loop: sentry_sdk/integrations/asyncio.py#L80
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#L952
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#L258
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#L665
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.
RuntimeError when calling run_until_complete on a closed loop: sentry_sdk/integrations/asyncio.py#L80
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#L665
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#L340
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]`.