From 29fba5ddbb9ed4ddfd4f90c44d02dc877c5052da Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 22 Jun 2026 18:32:56 +0200 Subject: [PATCH 1/2] 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 From e4d80e7001e9996a2836225f45a905dd16ffc777 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 22 Jun 2026 18:39:28 +0200 Subject: [PATCH 2/2] test: close the loop after the network thread has completed https://docs.python.org/3.15/library/asyncio-eventloop.html#asyncio.loop.close reads "The loop must not be running when this function is called". It seems safer to call `close()` after the thread has exited. --- test/functional/test_framework/p2p.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 7e661d178ce..af0e3bf2b61 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -774,8 +774,8 @@ class NetworkThread(threading.Thread): 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() self.join(timeout) + self.network_event_loop.close() # Safe to remove event loop. NetworkThread.network_event_loop = None