diff --git a/host-bridge/adapters/meshcore_adapter.py b/host-bridge/adapters/meshcore_adapter.py index cc98ebd..b474577 100644 --- a/host-bridge/adapters/meshcore_adapter.py +++ b/host-bridge/adapters/meshcore_adapter.py @@ -79,5 +79,25 @@ class MeshCoreAdapter: ) def close(self) -> None: - if self._loop is not None: - self._loop.call_soon_threadsafe(self._loop.stop) + 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)