Cleanup bluetooth connection failure code (#495)

This commit is contained in:
J. Nick Koston 2023-07-19 14:25:32 -05:00 committed by GitHub
parent 821735e3a3
commit 2d89b9e267
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 51 deletions

View File

@ -588,7 +588,7 @@ class APIClient:
"""Handle a timeout.""" """Handle a timeout."""
if fut.done(): if fut.done():
return return
fut.set_exception(asyncio.TimeoutError()) fut.set_exception(asyncio.TimeoutError)
def _on_bluetooth_device_connection_response( def _on_bluetooth_device_connection_response(
self, self,
@ -653,47 +653,33 @@ class APIClient:
msg_types, msg_types,
) )
timeout_handle = self._loop.call_later( loop = self._loop
timeout, self._handle_timeout, connect_future timeout_handle = loop.call_later(timeout, self._handle_timeout, connect_future)
) timeout_expired = False
try: connect_ok = False
try: try:
await connect_future await connect_future
connect_ok = True
except asyncio.TimeoutError as err: except asyncio.TimeoutError as err:
timeout_expired = True
# Disconnect before raising the exception to ensure # Disconnect before raising the exception to ensure
# the slot is recovered before the timeout is raised # the slot is recovered before the timeout is raised
# to avoid race were we run out even though we have a slot. # to avoid race were we run out even though we have a slot.
addr = to_human_readable_address(address) addr = to_human_readable_address(address)
if debug: if debug:
_LOGGER.debug( _LOGGER.debug("%s: Connecting timed out, waiting for disconnect", addr)
"%s: Connecting timed out, waiting for disconnect", addr disconnect_timed_out = (
not await self._bluetooth_device_disconnect_guard_timeout(
address, disconnect_timeout
) )
disconnect_timed_out = False
try:
await self.bluetooth_device_disconnect(
address, timeout=disconnect_timeout
)
except TimeoutAPIError:
disconnect_timed_out = True
if debug:
_LOGGER.debug(
"%s: Disconnect timed out: %s", addr, disconnect_timed_out
)
finally:
try:
unsub()
except (KeyError, ValueError):
_LOGGER.warning(
"%s: Bluetooth device connection timed out but already unsubscribed "
"(likely due to unexpected disconnect)",
addr,
) )
raise TimeoutAPIError( raise TimeoutAPIError(
f"Timeout waiting for connect response while connecting to {addr} " f"Timeout waiting for connect response while connecting to {addr} "
f"after {timeout}s, disconnect timed out: {disconnect_timed_out}, " f"after {timeout}s, disconnect timed out: {disconnect_timed_out}, "
f" after {disconnect_timeout}s" f" after {disconnect_timeout}s"
) from err ) from err
except asyncio.CancelledError: finally:
if not connect_ok:
try: try:
unsub() unsub()
except (KeyError, ValueError): except (KeyError, ValueError):
@ -701,12 +687,30 @@ class APIClient:
"%s: Bluetooth device connection canceled but already unsubscribed", "%s: Bluetooth device connection canceled but already unsubscribed",
addr, addr,
) )
raise if not timeout_expired:
finally:
timeout_handle.cancel() timeout_handle.cancel()
return unsub return unsub
async def _bluetooth_device_disconnect_guard_timeout(
self, address: int, timeout: float
) -> bool:
"""Disconnect from a Bluetooth device and guard against timeout.
Return true if the disconnect was successful, false if it timed out.
"""
try:
await self.bluetooth_device_disconnect(address, timeout=timeout)
except TimeoutAPIError:
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug(
"%s: Disconnect timed out: %s",
to_human_readable_address(address),
timeout,
)
return False
return True
async def bluetooth_device_pair( async def bluetooth_device_pair(
self, address: int, timeout: float = DEFAULT_BLE_TIMEOUT self, address: int, timeout: float = DEFAULT_BLE_TIMEOUT
) -> BluetoothDevicePairing: ) -> BluetoothDevicePairing:

View File

@ -599,7 +599,7 @@ class APIConnection:
"""Handle a timeout.""" """Handle a timeout."""
if fut.done(): if fut.done():
return return
fut.set_exception(asyncio.TimeoutError()) fut.set_exception(asyncio.TimeoutError)
def _handle_complex_message( def _handle_complex_message(
self, self,
@ -658,13 +658,16 @@ class APIConnection:
# the message could fail to be removed if the # the message could fail to be removed if the
# the await is cancelled # the await is cancelled
timeout_handle = self._loop.call_later(timeout, self._handle_timeout, fut) timeout_handle = self._loop.call_later(timeout, self._handle_timeout, fut)
timeout_expired = False
try: try:
await fut await fut
except asyncio.TimeoutError as err: except asyncio.TimeoutError as err:
timeout_expired = True
raise TimeoutAPIError( raise TimeoutAPIError(
f"Timeout waiting for response for {type(send_msg)} after {timeout}s" f"Timeout waiting for response for {type(send_msg)} after {timeout}s"
) from err ) from err
finally: finally:
if not timeout_expired:
timeout_handle.cancel() timeout_handle.cancel()
for msg_type in msg_types: for msg_type in msg_types:
message_handlers[msg_type].discard(on_message) message_handlers[msg_type].discard(on_message)