From 315d4a9cd7be562d6fcbc6ebd619197193ae79b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Cabessa?= Date: Tue, 11 Aug 2026 09:47:48 +0200 Subject: [PATCH] fix: performance regression when sniffio not installed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sniffio` is optional and lazily imported in `current_async_library`, when absent we use `asyncio` However the cost of import failure is paid every time the function is called. This patch moves the import at module level, like it is already done for `anyio` or `trio` Why it happens now?: Httpcore never explicitly declared `sniffio` but it was installed via `anyio` until they stopped depending on it: https://github.com/agronholm/anyio/pull/1021 Here is a sample to reproduce the issue ``` import asyncio import time import httpcore REQUESTS = 500 RESPONSE = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok" async def handle(reader, writer): try: while await reader.readuntil(b"\r\n\r\n"): writer.write(RESPONSE) await writer.drain() except (asyncio.IncompleteReadError, ConnectionResetError): pass writer.close() async def main(): try: import sniffio # noqa: F401 status = "INSTALLED" except ImportError: status = "ABSENT" server = await asyncio.start_server(handle, "127.0.0.1", 0) url = f"http://127.0.0.1:{server.sockets[0].getsockname()[1]}/" async with httpcore.AsyncConnectionPool() as pool: await pool.request("GET", url) # warm up the pool cpu = time.process_time() for _ in range(REQUESTS): await pool.request("GET", url) cpu = time.process_time() - cpu print(f"sniffio {status}: {cpu / REQUESTS * 1e6:.0f} us CPU per request") server.close() asyncio.run(main()) ``` On my machine: | httpcore | sniffio | CPU / req | |----------|-----------|-----------| | master | installed | ~330 µs | | master | missing | ~500 µs | | patched | missing | ~330 µs | | patched | installed | ~330 µs | --- httpcore/_synchronization.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/httpcore/_synchronization.py b/httpcore/_synchronization.py index 2ecc9e9c..32d9ada7 100644 --- a/httpcore/_synchronization.py +++ b/httpcore/_synchronization.py @@ -18,14 +18,17 @@ except ImportError: # pragma: nocover anyio = None # type: ignore +try: + import sniffio +except ImportError: # pragma: nocover + sniffio = None # type: ignore + def current_async_library() -> str: # Determine if we're running under trio or asyncio. # See https://sniffio.readthedocs.io/en/latest/ - try: - import sniffio - except ImportError: # pragma: nocover - environment = "asyncio" + if sniffio is None: + environment = "asyncio" # pragma: nocover else: environment = sniffio.current_async_library()