From b6bb982f2dace0a3d19c78b4b4247760a8010d5b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 6 May 2025 19:15:28 -0400 Subject: [PATCH] fix(bluetooth): cpu usage --- cosmic-applet-bluetooth/src/app.rs | 16 ++++++- cosmic-applet-bluetooth/src/bluetooth.rs | 55 +++++++++++++++++++----- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/cosmic-applet-bluetooth/src/app.rs b/cosmic-applet-bluetooth/src/app.rs index eb03ddb7..a3798edd 100644 --- a/cosmic-applet-bluetooth/src/app.rs +++ b/cosmic-applet-bluetooth/src/app.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use crate::bluetooth::{BluerDeviceStatus, BluerRequest, BluerState, DeviceProperty}; +use crate::bluetooth::{set_tick, BluerDeviceStatus, BluerRequest, BluerState, DeviceProperty}; use cosmic::{ app, applet::token::subscription::{activation_token_subscription, TokenRequest, TokenUpdate}, @@ -24,6 +24,7 @@ use cosmic::{ Element, Task, }; use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline}; +use futures::FutureExt; use std::{collections::HashMap, time::Duration}; use tokio::sync::mpsc::Sender; @@ -112,7 +113,13 @@ impl cosmic::Application for CosmicBluetoothApplet { match message { Message::TogglePopup => { if let Some(p) = self.popup.take() { - return destroy_popup(p); + return Task::batch(vec![ + destroy_popup(p), + cosmic::task::future( + set_tick(Duration::from_secs(10)) + .map(|_| cosmic::Action::App(Message::Ignore)), + ), + ]); } else { // TODO request update of state maybe let new_id = window::Id::unique(); @@ -138,6 +145,8 @@ impl cosmic::Application for CosmicBluetoothApplet { |_| cosmic::action::app(Message::Ignore), ), get_popup(popup_settings), + cosmic::task::future(set_tick(Duration::from_secs(3))) + .map(|_: ()| cosmic::Action::App(Message::Ignore)), ]); } } @@ -286,6 +295,9 @@ impl cosmic::Application for CosmicBluetoothApplet { if Some(id) == self.popup { self.popup = None; } + return cosmic::task::future( + set_tick(Duration::from_secs(10)).map(|_| cosmic::Action::App(Message::Ignore)), + ); } Message::OpenSettings => { let exec = "cosmic-settings bluetooth".to_string(); diff --git a/cosmic-applet-bluetooth/src/bluetooth.rs b/cosmic-applet-bluetooth/src/bluetooth.rs index 762d0919..549f654b 100644 --- a/cosmic-applet-bluetooth/src/bluetooth.rs +++ b/cosmic-applet-bluetooth/src/bluetooth.rs @@ -1,7 +1,14 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use std::{collections::HashMap, fmt::Debug, hash::Hash, mem, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + fmt::Debug, + hash::Hash, + mem, + sync::{Arc, LazyLock}, + time::Duration, +}; pub use bluer::DeviceProperty; use bluer::{ @@ -24,11 +31,26 @@ use tokio::{ spawn, sync::{ mpsc::{channel, Receiver, Sender}, - Mutex, + Mutex, RwLock, }, task::JoinHandle, }; +static TICK: LazyLock> = LazyLock::new(|| RwLock::new(Duration::from_secs(10))); + +pub async fn set_tick(duration: Duration) { + let mut guard = TICK.write().await; + *guard = duration; +} + +pub async fn tick(interval: &mut tokio::time::Interval) { + let guard = TICK.read().await; + if *guard != interval.period() { + *interval = tokio::time::interval(*guard); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + } + interval.tick().await; +} // Copied from https://github.com/bluez/bluez/blob/39467578207889fd015775cbe81a3db9dd26abea/src/dbus-common.c#L53 #[inline] fn device_type_to_icon(device_type: &str) -> &'static str { @@ -68,8 +90,10 @@ pub fn bluetooth_subscription( } retry_count = retry_count.saturating_add(1); - _ = tokio::time::sleep(Duration::from_millis(2_u64.saturating_pow(retry_count))) - .await; + _ = tokio::time::sleep(Duration::from_millis( + 2_u64.saturating_pow(retry_count).max(68719476734), + )) + .await; }; let state = bluer_state(&session_state.adapter).await; @@ -116,8 +140,8 @@ pub fn bluetooth_subscription( _ = output.send(message).await; }; - let mut interval = tokio::time::interval(Duration::from_secs(1)); - + let mut interval = tokio::time::interval(Duration::from_secs(10)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { let Some(mut session_rx) = session_state.rx.take() else { break; @@ -126,8 +150,13 @@ pub fn bluetooth_subscription( if let Some(event) = session_rx.recv().await { event_handler(event).await; // Consume any additional available events. + let mut count = 0; while let Some(event) = session_rx.try_recv().ok() { event_handler(event).await; + count += 1; + if count == 100 { + break; + } } } else { break; @@ -531,10 +560,11 @@ impl BluerSessionState { let wake_up_discover_tx = self.wake_up_discover_tx.clone(); let _handle: JoinHandle> = spawn(async move { let mut status = adapter_clone.is_powered().await.unwrap_or_default(); - let mut interval = tokio::time::interval(Duration::from_secs(3)); + let mut interval = tokio::time::interval(Duration::from_secs(10)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); let mut devices = Vec::new(); loop { - interval.tick().await; + tick(&mut interval).await; let new_status = adapter_clone.is_powered().await.unwrap_or_default(); devices = build_device_list(devices, &adapter_clone).await; if new_status != status { @@ -569,19 +599,23 @@ impl BluerSessionState { let _monitor_devices: tokio::task::JoinHandle> = spawn(async move { let mut devices: Vec = Vec::new(); + let mut interval = tokio::time::interval(Duration::from_secs(1)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { + interval.tick().await; let wakeup_fut = wake_up.recv(); // Listens for process changes and builds edvice lists. let listener_fut = async { let mut new_devices = Vec::new(); - let mut interval = tokio::time::interval(Duration::from_secs(1)); + let mut interval = tokio::time::interval(Duration::from_secs(10)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); let mut change_stream = match adapter_clone.discover_devices_with_changes().await { Ok(stream) => stream, Err(_) => { - interval.tick().await; + tick(&mut interval).await; return; } }; @@ -768,7 +802,6 @@ async fn bluer_state(adapter: &Adapter) -> BluerState { #[inline(never)] async fn build_device_list(mut devices: Vec, adapter: &Adapter) -> Vec { let addrs = adapter.device_addresses().await.unwrap_or_default(); - devices.clear(); if addrs.len() > devices.capacity() { devices.reserve(addrs.len() - devices.capacity());