Skip to content

--ble still hangs on macOS #921

@max-arnold

Description

@max-arnold

Even with v2.7.8 that includes #893, the CLI still hangs for me on macOS Ventura 13.7.8 and Python 3.14 installed from MacPorts.

I traced the issue down to re-enterant BLEInterface.close():

BLEInterface.close() calls self.client.disconnect(), and Bleak invokes disconnected_callback=lambda _: self.close() during that disconnect, so close() re-enters itself mid-shutdown and hangs.

Just add print("Closing...") to the top of close() to see it yourself.

The following patch fixes that for me, feel free to include it if you think it is a valid approach:

diff --git a/meshtastic/ble_interface.py b/meshtastic/ble_interface.py
index 0237672..a484650 100644
--- a/meshtastic/ble_interface.py
+++ b/meshtastic/ble_interface.py
@@ -234,25 +234,32 @@ def _sendToRadioImpl(self, toRadio) -> None:
             self.should_read = True
 
     def close(self) -> None:
+        if getattr(self, "_closing", False):
+            return
+
+        self._closing = True
         try:
-            MeshInterface.close(self)
-        except Exception as e:
-            logger.error(f"Error closing mesh interface: {e}")
-
-        if self._want_receive:
-            self._want_receive = False  # Tell the thread we want it to stop
-            if self._receiveThread:
-                self._receiveThread.join(
-                    timeout=2
-                )  # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect)
-                self._receiveThread = None
-
-        if self.client:
-            atexit.unregister(self._exit_handler)
-            self.client.disconnect()
-            self.client.close()
-            self.client = None
-        self._disconnected() # send the disconnected indicator up to clients
+            try:
+                MeshInterface.close(self)
+            except Exception as e:
+                logger.error(f"Error closing mesh interface: {e}")
+
+            if self._want_receive:
+                self._want_receive = False  # Tell the thread we want it to stop
+                if self._receiveThread:
+                    self._receiveThread.join(
+                        timeout=2
+                    )  # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect)
+                    self._receiveThread = None
+
+            if self.client:
+                atexit.unregister(self._exit_handler)
+                self.client.disconnect()
+                self.client.close()
+                self.client = None
+            self._disconnected() # send the disconnected indicator up to clients
+        finally:
+            self._closing = False
 
 
 class BLEClient:

To avoid patching the code, I use the following trick as a workaround (place that snippet into your Python's site-packages, e.g. .venv/lib/python3.14/site-packages/meshtastic_ble_close_guard.pth):

import meshtastic.ble_interface as b;exec("o=b.BLEInterface.close\ndef c(self,*a,**k):\n if getattr(self,'_closing',False): return\n self._closing=True\n try: return o(self,*a,**k)\n finally: self._closing=False\nb.BLEInterface.close=c",{"b":b})

Possibly related issue #909

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions