Skip to content

Commit 3517433

Browse files
authored
Fix a deadlock when services are missing (#165)
When services are missing we want to clear the cache and disconnect so we can try again to get the services. The code that triggered the disconnect when services were missing tried to obtain the connection lock but the lock was already held which resulted in a deadlock.
1 parent 5fa6ed8 commit 3517433

File tree

1 file changed

+24
-13
lines changed

1 file changed

+24
-13
lines changed

switchbot/devices/device.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ async def _ensure_connected(self):
270270
ble_device_callback=lambda: self._device,
271271
)
272272
_LOGGER.debug("%s: Connected; RSSI: %s", self.name, self.rssi)
273+
self._client = client
273274

274275
try:
275276
self._resolve_characteristics(client.services)
@@ -282,10 +283,10 @@ async def _ensure_connected(self):
282283
exc_info=True,
283284
)
284285
await client.clear_cache()
285-
await self._execute_forced_disconnect()
286+
self._cancel_disconnect_timer()
287+
await self._execute_disconnect_with_lock()
286288
raise
287289

288-
self._client = client
289290
self._reset_disconnect_timer()
290291
await self._start_notify()
291292

@@ -358,18 +359,28 @@ async def _execute_timed_disconnect(self) -> None:
358359

359360
async def _execute_disconnect(self) -> None:
360361
"""Execute disconnection."""
362+
_LOGGER.debug("%s: Executing disconnect", self.name)
361363
async with self._connect_lock:
362-
if self._disconnect_timer: # If the timer was reset, don't disconnect
363-
return
364-
client = self._client
365-
self._expected_disconnect = True
366-
self._client = None
367-
self._read_char = None
368-
self._write_char = None
369-
if client and client.is_connected:
370-
_LOGGER.debug("%s: Disconnecting", self.name)
371-
await client.disconnect()
372-
_LOGGER.debug("%s: Disconnect completed", self.name)
364+
await self._execute_disconnect_with_lock()
365+
366+
async def _execute_disconnect_with_lock(self) -> None:
367+
"""Execute disconnection while holding the lock."""
368+
assert self._connect_lock.locked(), "Lock not held"
369+
_LOGGER.debug("%s: Executing disconnect with lock", self.name)
370+
if self._disconnect_timer: # If the timer was reset, don't disconnect
371+
_LOGGER.debug("%s: Skipping disconnect as timer reset", self.name)
372+
return
373+
client = self._client
374+
self._expected_disconnect = True
375+
self._client = None
376+
self._read_char = None
377+
self._write_char = None
378+
if client and client.is_connected:
379+
_LOGGER.debug("%s: Disconnecting", self.name)
380+
await client.disconnect()
381+
_LOGGER.debug("%s: Disconnect completed", self.name)
382+
else:
383+
_LOGGER.debug("%s: Already disconnected", self.name)
373384

374385
async def _send_command_locked(self, key: str, command: bytes) -> bytes:
375386
"""Send command to device and read response."""

0 commit comments

Comments
 (0)