From 29fba5ddbb9ed4ddfd4f90c44d02dc877c5052da Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 22 Jun 2026 18:32:56 +0200 Subject: [PATCH] test: close the listeners before terminating the event loop Whenever a test creates a new `P2PInterface` object a new listener is created inside `NetworkThread.create_listen_server()` by calling `cls.network_event_loop.create_server()`. These listeners are never closed which might result in: ``` 2026-06-10T22:13:35.3934880Z Task was destroyed but it is pending! 2026-06-10T22:13:35.3936020Z task: wait_for=> ``` when the event loop is closed. Fix that by closing the listeners. Fixes: https://github.com/bitcoin/bitcoin/issues/35508 --- test/functional/test_framework/p2p.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 65260b7d322..7e661d178ce 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -748,6 +748,7 @@ class NetworkThread(threading.Thread): NetworkThread.listeners = {} NetworkThread.protos = {} + NetworkThread.protos_accept_done = [] def run(self): """Start the network thread.""" @@ -756,6 +757,21 @@ class NetworkThread(threading.Thread): def close(self, *, timeout): """Close the connections and network event loop.""" + for p in NetworkThread.protos_accept_done: + p.peer_disconnect() + NetworkThread.protos_accept_done.clear() + + listeners = list(NetworkThread.listeners.values()) + NetworkThread.listeners.clear() + + async def close_listeners(): + for listener in listeners: + listener.close() + for listener in listeners: + await listener.wait_closed() + future = asyncio.run_coroutine_threadsafe(close_listeners(), self.network_event_loop) + future.result(timeout=timeout) + self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop) wait_until_helper_internal(lambda: not self.network_event_loop.is_running(), timeout=timeout) self.network_event_loop.close() @@ -795,6 +811,7 @@ class NetworkThread(threading.Thread): response = cls.protos.get((addr, port)) # remove protocol function from dict only when reconnection doesn't need to happen/already happened if not proto.reconnect: + cls.protos_accept_done.append(response) cls.protos[(addr, port)] = None return response