fix: clean MeshCore adapter shutdown

close() stopped the asyncio loop without disconnecting first, leaving the
EventDispatcher._process_events() task and the serial transport pending
("Task was destroyed but it is pending!" / "Event loop is closed").
Now await MeshCore.disconnect() and let the transport's connection_lost
callback settle before stopping the loop, then join the thread.

Found by running the adapter against a live MeshCore-flashed Heltec V3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-07-01 15:31:52 +01:00
parent 512e30a6fd
commit d04e4c8370

View File

@ -79,5 +79,25 @@ class MeshCoreAdapter:
)
def close(self) -> None:
if self._loop is not None:
if self._loop is None:
return
# Disconnect on the loop thread first: MeshCore.disconnect() stops the
# EventDispatcher task and closes the serial transport. Skipping this
# and stopping the loop directly leaves _process_events() pending
# ("Task was destroyed but it is pending!" / "Event loop is closed").
if self._mc is not None:
async def _shutdown() -> None:
await self._mc.disconnect()
# Let the serial transport's connection_lost callback run to
# completion before the loop stops, otherwise it lingers as a
# pending SerialTransport._call_connection_lost task.
await asyncio.sleep(0.25)
try:
fut = asyncio.run_coroutine_threadsafe(_shutdown(), self._loop)
fut.result(timeout=5)
except Exception:
pass
self._loop.call_soon_threadsafe(self._loop.stop)
if self._thread is not None:
self._thread.join(timeout=5)