From 2e13b1695e6f536f0b52c749e742b4d1647ed677 Mon Sep 17 00:00:00 2001 From: Lenno Nagel Date: Fri, 27 Feb 2026 14:39:06 +0200 Subject: [PATCH] Await connection callback instead of creating a detached task _read_then_call used asyncio.create_task() to fire off the connection callback without keeping a reference to the resulting task. Since the event loop only holds weak references to tasks, the GC can collect them at any time, producing "Task was destroyed but it is pending!" errors under load. Because asyncio.start_server already runs _read_then_call in a strongly-referenced task, the callback can simply be awaited directly. This keeps the connection handler alive for the full connection lifetime without needing a separate task. Co-Authored-By: Claude Opus 4.6 --- proxyprotocol/reader.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/proxyprotocol/reader.py b/proxyprotocol/reader.py index 21ea798..d804a08 100644 --- a/proxyprotocol/reader.py +++ b/proxyprotocol/reader.py @@ -66,9 +66,8 @@ def get_callback(self, callback: _WrappedCallback, to :func:`asyncio.start_server`. The returned callback will first read the PROXY protocol header before - starting the provided *callback* as a :class:`~asyncio.Task`. The - *callback* argument is similar to *client_connected_cb* but with an - additional positional argument -- the + calling the provided *callback*. The *callback* argument is similar to + *client_connected_cb* but with an additional positional argument -- the :class:`~proxyprotocol.sock.SocketInfo` read from the header. Args: @@ -89,4 +88,4 @@ async def _read_then_call(self, callback: _WrappedCallback, writer.close() result = ProxyResultUnknown(exc) sock_info = SocketInfo.get(writer, result, unique_id=uuid4().bytes) - asyncio.create_task(callback(reader, writer, sock_info)) + await callback(reader, writer, sock_info)