-
-
Notifications
You must be signed in to change notification settings - Fork 38
Description
Describe the bug
Today, due to some jankyness in my usually smooth laptop performance, I looked at ActivityMonitor in macOS and my Iced/Tokio app that uses meshtastic/rust crate had 1400 threads running.
Upon investigation it looks like my iced subscription (which scans for BLE devices every 4 seconds) is creating a thread or two every iteration...and they never die, leading to the number of threads used by the application growing constantly.
Thread count starts around 35 and increases 1 or 2 every 4 seconds, the period of this iteration in my code:
pub fn ble_discovery() -> impl Stream<Item = DiscoveryEvent> {
stream::channel(100, move |mut gui_sender| async move {
let mut mesh_radio_ids: Vec<BleDevice> = vec![];
// loop scanning for devices
loop {
match available_ble_devices(Duration::from_secs(4)).await {
Ok(radios_now_ids) => {
// detect lost radios
for id in &mesh_radio_ids {
if !radios_now_ids.iter().any(|other_id| id == other_id) {
// inform GUI of a device lost
gui_sender
.send(BLERadioLost(id.clone()))
.await
.unwrap_or_else(|e| eprintln!("Discovery gui send error: {e}"));
}
}
// detect new radios found
for id in &radios_now_ids {
if !mesh_radio_ids.iter().any(|other_id| id == other_id) {
// track it for the future
mesh_radio_ids.push(id.clone());
// inform GUI of a new device found
gui_sender
.send(BLERadioFound(id.clone()))
.await
.unwrap_or_else(|e| eprintln!("Discovery gui send error: {e}"));
}
}
}
Err(e) => {
...elided...
}
}
}
})
}
Digging into available_ble_devices code it looks like Àdapter::newcallsrun_corebluetooth_thread` and that seems to create threads that never die.
Call Graph
available_ble_devices
--> BleHandler::available_ble_devices
--> available_peripherals
--> Manager.adapters
--> Adapter::new
--> `run_corebluetooth_thread'
pub fn run_corebluetooth_thread(
event_sender: Sender<CoreBluetoothEvent>,
) -> Result<Sender<CoreBluetoothMessage>, Error> {
let authorization = unsafe { CBManager::authorization_class() };
if authorization != CBManagerAuthorization::AllowedAlways
&& authorization != CBManagerAuthorization::NotDetermined
{
warn!("Authorization status {:?}", authorization);
return Err(Error::PermissionDenied);
} else {
trace!("Authorization status {:?}", authorization);
}
let (sender, receiver) = mpsc::channel::<CoreBluetoothMessage>(256);
// CoreBluetoothInternal is !Send, so we need to keep it on a single thread.
thread::spawn(move || {
let runtime = runtime::Builder::new_current_thread().build().unwrap();
runtime.block_on(async move {
let mut cbi = CoreBluetoothInternal::new(receiver, event_sender);
loop {
cbi.wait_for_message().await;
}
})
});
Ok(sender)
}
that seems to loop forever in the thread?
loop {
cbi.wait_for_message().await;
}
Adapter::new() also spawn tasks of its own, but those may die on channel closure, I didn't check the detailed logic of that.
To Reproduce
Steps to reproduce:
- Have some code that performs discovery using available_ble_devices in a loop that never exits
- Observe the number of threads in ActivityMonitor (or equivalent) grow constantly
Expected behavior
Threads to die after a short time or when they cease to be useful, or being reused.
Additional context
Screenshot on app startup (37 threads)
Screenshot after a short period (without use, just doing discovery) (251 threads)
