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: <Task pending name='Task-54' coro=<BaseSelectorEventLoop._accept_connection2() done, defined at /opt/homebrew/Cellar/python@3.14/3.14.5/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/selector_events.py:217> wait_for=<Future finished result=None>>
```

when the event loop is closed.

Fix that by closing the listeners.

Fixes: https://github.com/bitcoin/bitcoin/issues/35508
This commit is contained in:
Vasil Dimov
2026-06-22 18:32:56 +02:00
parent f72537037d
commit 29fba5ddbb

View File

@@ -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