From 6eab2c5420f192dda1e1dba4e000d1b33a7ab74b Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Fri, 24 May 2024 13:14:04 +0200 Subject: [PATCH] fix(macos): Break the corebluetooth loop when manager turned off In Core Bluetooth when the device is not applicable for background bluetooth manager will create an event for state change. And then change the manager state to power off. Currently, it is not tracked at all which leads to the forever stuck unresolved issues while the connection to peripheral is still held. An additional problem I faced that there is no way to manually kill the event loop of the corebluetooth from outside so the `CoreBluetoothInternal::drop` is never called because it is always living in the stalled thread. In this change, I added an API to access the manager state and exited the event loop when if the manager turned off. --- src/corebluetooth/adapter.rs | 18 +++++++++++--- src/corebluetooth/framework.rs | 19 ++++++++++++++- src/corebluetooth/internal.rs | 43 ++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/corebluetooth/adapter.rs b/src/corebluetooth/adapter.rs index c139c43d..fb3dc251 100644 --- a/src/corebluetooth/adapter.rs +++ b/src/corebluetooth/adapter.rs @@ -2,6 +2,7 @@ use super::internal::{run_corebluetooth_thread, CoreBluetoothEvent, CoreBluetoot use super::peripheral::{Peripheral, PeripheralId}; use crate::api::{Central, CentralEvent, ScanFilter}; use crate::common::adapter_manager::AdapterManager; +use crate::corebluetooth::internal::{CoreBluetoothReply, CoreBluetoothReplyFuture}; use crate::{Error, Result}; use async_trait::async_trait; use futures::channel::mpsc::{self, Sender}; @@ -29,7 +30,7 @@ impl Adapter { debug!("Waiting on adapter connect"); if !matches!( receiver.next().await, - Some(CoreBluetoothEvent::AdapterConnected) + Some(CoreBluetoothEvent::DidUpdateState) ) { return Err(Error::Other( "Adapter failed to connect.".to_string().into(), @@ -39,7 +40,7 @@ impl Adapter { let manager = Arc::new(AdapterManager::default()); let manager_clone = manager.clone(); - let adapter_sender_clone = adapter_sender.clone(); + let mut adapter_sender_clone = adapter_sender.clone(); task::spawn(async move { while let Some(msg) = receiver.next().await { match msg { @@ -67,7 +68,18 @@ impl Adapter { CoreBluetoothEvent::DeviceDisconnected { uuid } => { manager_clone.emit(CentralEvent::DeviceDisconnected(uuid.into())); } - _ => {} + CoreBluetoothEvent::DidUpdateState => { + let fut = CoreBluetoothReplyFuture::default(); + let _ = adapter_sender_clone + .send(CoreBluetoothMessage::FetchManagerState { + future: fut.get_state_clone(), + }) + .await; + + if let CoreBluetoothReply::ManagerState(state) = fut.await { + error!("Adapter state changed to {:?}. Aborting manager", state) + } + } } } }); diff --git a/src/corebluetooth/framework.rs b/src/corebluetooth/framework.rs index 2efa110b..a7d1879b 100644 --- a/src/corebluetooth/framework.rs +++ b/src/corebluetooth/framework.rs @@ -163,6 +163,22 @@ pub mod cb { } } + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + #[repr(i64)] + #[allow(dead_code)] + pub enum CBManagerState { + Unknown = 0, + Resetting = 1, + Unsupported = 2, + Unauthorized = 3, + PoweredOff = 4, + PoweredOn = 5, + } + + pub fn centeralmanger_state(cbcentralmanager: id) -> CBManagerState { + unsafe { msg_send![cbcentralmanager, state] } + } + pub fn centralmanager_stopscan(cbcentralmanager: id) { unsafe { msg_send![cbcentralmanager, stopScan] } } @@ -207,10 +223,11 @@ pub mod cb { unsafe { msg_send![cbperipheral, name] } } + #[allow(dead_code)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(i64)] pub enum CBPeripheralState { - Disonnected = 0, + Disconnected = 0, Connecting = 1, Connected = 2, Disconnecting = 3, diff --git a/src/corebluetooth/internal.rs b/src/corebluetooth/internal.rs index c3dd3eef..2518c741 100644 --- a/src/corebluetooth/internal.rs +++ b/src/corebluetooth/internal.rs @@ -11,7 +11,7 @@ use super::{ central_delegate::{CentralDelegate, CentralDelegateEvent}, framework::{ - cb::{self, CBManagerAuthorization, CBPeripheralState}, + cb::{self, CBManagerAuthorization, CBManagerState, CBPeripheralState}, ns, }, future::{BtlePlugFuture, BtlePlugFutureStateShared}, @@ -21,10 +21,7 @@ use super::{ nsuuid_to_uuid, }, }; -use crate::api::{ - bleuuid::uuid_from_u16, CharPropFlags, Characteristic, Descriptor, ScanFilter, Service, - WriteType, -}; +use crate::api::{CharPropFlags, Characteristic, Descriptor, ScanFilter, Service, WriteType}; use crate::Error; use cocoa::{ base::{id, nil}, @@ -148,6 +145,7 @@ pub enum CoreBluetoothReply { ReadResult(Vec), Connected(BTreeSet), State(CBPeripheralState), + ManagerState(CBManagerState), Ok, Err(String), } @@ -408,11 +406,14 @@ pub enum CoreBluetoothMessage { data: Vec, future: CoreBluetoothReplyStateShared, }, + FetchManagerState { + future: CoreBluetoothReplyStateShared, + }, } #[derive(Debug)] pub enum CoreBluetoothEvent { - AdapterConnected, + DidUpdateState, DeviceDiscovered { uuid: Uuid, name: Option, @@ -795,6 +796,18 @@ impl CoreBluetoothInternal { } } + fn get_manager_state_sync(&mut self) -> CBManagerState { + cb::centeralmanger_state(*self.manager) + } + + fn get_manager_state(&mut self, fut: CoreBluetoothReplyStateShared) { + let state = cb::centeralmanger_state(*self.manager); + trace!("Manager state {:?} ", state); + fut.lock() + .unwrap() + .set_reply(CoreBluetoothReply::ManagerState(state)); + } + fn write_value( &mut self, peripheral_uuid: Uuid, @@ -837,6 +850,11 @@ impl CoreBluetoothInternal { characteristic_uuid: Uuid, fut: CoreBluetoothReplyStateShared, ) { + trace!( + "Manager State {:?}", + cb::centeralmanger_state(*self.manager) + ); + if let Some(peripheral) = self.peripherals.get_mut(&peripheral_uuid) { if let Some(service) = peripheral.services.get_mut(&service_uuid) { if let Some(characteristic) = service.characteristics.get_mut(&characteristic_uuid) @@ -1010,7 +1028,7 @@ impl CoreBluetoothInternal { // "ready" variable in our adapter that will cause scans/etc // to fail if this hasn't updated. CentralDelegateEvent::DidUpdateState => { - self.dispatch_event(CoreBluetoothEvent::AdapterConnected).await + self.dispatch_event(CoreBluetoothEvent::DidUpdateState).await } CentralDelegateEvent::DiscoveredPeripheral{cbperipheral} => { self.on_discovered_peripheral(cbperipheral).await @@ -1109,6 +1127,9 @@ impl CoreBluetoothInternal { CoreBluetoothMessage::IsConnected{peripheral_uuid, future} => { self.is_connected(peripheral_uuid, future); }, + CoreBluetoothMessage::FetchManagerState {future} =>{ + self.get_manager_state(future); + }, CoreBluetoothMessage::ReadDescriptorValue{peripheral_uuid, service_uuid, characteristic_uuid, descriptor_uuid, future} => { self.read_descriptor_value(peripheral_uuid, service_uuid, characteristic_uuid, descriptor_uuid, future) } @@ -1189,6 +1210,14 @@ pub fn run_corebluetooth_thread( runtime.block_on(async move { let mut cbi = CoreBluetoothInternal::new(receiver, event_sender); loop { + // When the IOS or MacOS device if powered off or locked + // the manager state will suddenly throw DidUpdateState event and turn off. + // If we are not exiting the main loop here the futures requested after + // power off will be stuck forever. + if cbi.get_manager_state_sync() == CBManagerState::PoweredOff { + trace!("Breaking out of the corebluetooth loop. Manager is off."); + break; + } cbi.wait_for_message().await; } })