fix(bluetooth): cpu usage

This commit is contained in:
Ashley Wulber 2025-05-06 19:15:28 -04:00 committed by Michael Murphy
parent 9eb4053d31
commit b6bb982f2d
2 changed files with 58 additions and 13 deletions

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // 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::{ use cosmic::{
app, app,
applet::token::subscription::{activation_token_subscription, TokenRequest, TokenUpdate}, applet::token::subscription::{activation_token_subscription, TokenRequest, TokenUpdate},
@ -24,6 +24,7 @@ use cosmic::{
Element, Task, Element, Task,
}; };
use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline}; use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline};
use futures::FutureExt;
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
@ -112,7 +113,13 @@ impl cosmic::Application for CosmicBluetoothApplet {
match message { match message {
Message::TogglePopup => { Message::TogglePopup => {
if let Some(p) = self.popup.take() { 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 { } else {
// TODO request update of state maybe // TODO request update of state maybe
let new_id = window::Id::unique(); let new_id = window::Id::unique();
@ -138,6 +145,8 @@ impl cosmic::Application for CosmicBluetoothApplet {
|_| cosmic::action::app(Message::Ignore), |_| cosmic::action::app(Message::Ignore),
), ),
get_popup(popup_settings), 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 { if Some(id) == self.popup {
self.popup = None; self.popup = None;
} }
return cosmic::task::future(
set_tick(Duration::from_secs(10)).map(|_| cosmic::Action::App(Message::Ignore)),
);
} }
Message::OpenSettings => { Message::OpenSettings => {
let exec = "cosmic-settings bluetooth".to_string(); let exec = "cosmic-settings bluetooth".to_string();

View file

@ -1,7 +1,14 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // 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; pub use bluer::DeviceProperty;
use bluer::{ use bluer::{
@ -24,11 +31,26 @@ use tokio::{
spawn, spawn,
sync::{ sync::{
mpsc::{channel, Receiver, Sender}, mpsc::{channel, Receiver, Sender},
Mutex, Mutex, RwLock,
}, },
task::JoinHandle, task::JoinHandle,
}; };
static TICK: LazyLock<RwLock<Duration>> = 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 // Copied from https://github.com/bluez/bluez/blob/39467578207889fd015775cbe81a3db9dd26abea/src/dbus-common.c#L53
#[inline] #[inline]
fn device_type_to_icon(device_type: &str) -> &'static str { fn device_type_to_icon(device_type: &str) -> &'static str {
@ -68,8 +90,10 @@ pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
} }
retry_count = retry_count.saturating_add(1); retry_count = retry_count.saturating_add(1);
_ = tokio::time::sleep(Duration::from_millis(2_u64.saturating_pow(retry_count))) _ = tokio::time::sleep(Duration::from_millis(
.await; 2_u64.saturating_pow(retry_count).max(68719476734),
))
.await;
}; };
let state = bluer_state(&session_state.adapter).await; let state = bluer_state(&session_state.adapter).await;
@ -116,8 +140,8 @@ pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
_ = output.send(message).await; _ = 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 { loop {
let Some(mut session_rx) = session_state.rx.take() else { let Some(mut session_rx) = session_state.rx.take() else {
break; break;
@ -126,8 +150,13 @@ pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
if let Some(event) = session_rx.recv().await { if let Some(event) = session_rx.recv().await {
event_handler(event).await; event_handler(event).await;
// Consume any additional available events. // Consume any additional available events.
let mut count = 0;
while let Some(event) = session_rx.try_recv().ok() { while let Some(event) = session_rx.try_recv().ok() {
event_handler(event).await; event_handler(event).await;
count += 1;
if count == 100 {
break;
}
} }
} else { } else {
break; break;
@ -531,10 +560,11 @@ impl BluerSessionState {
let wake_up_discover_tx = self.wake_up_discover_tx.clone(); let wake_up_discover_tx = self.wake_up_discover_tx.clone();
let _handle: JoinHandle<anyhow::Result<()>> = spawn(async move { let _handle: JoinHandle<anyhow::Result<()>> = spawn(async move {
let mut status = adapter_clone.is_powered().await.unwrap_or_default(); 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(); let mut devices = Vec::new();
loop { loop {
interval.tick().await; tick(&mut interval).await;
let new_status = adapter_clone.is_powered().await.unwrap_or_default(); let new_status = adapter_clone.is_powered().await.unwrap_or_default();
devices = build_device_list(devices, &adapter_clone).await; devices = build_device_list(devices, &adapter_clone).await;
if new_status != status { if new_status != status {
@ -569,19 +599,23 @@ impl BluerSessionState {
let _monitor_devices: tokio::task::JoinHandle<Result<(), anyhow::Error>> = let _monitor_devices: tokio::task::JoinHandle<Result<(), anyhow::Error>> =
spawn(async move { spawn(async move {
let mut devices: Vec<BluerDevice> = Vec::new(); let mut devices: Vec<BluerDevice> = Vec::new();
let mut interval = tokio::time::interval(Duration::from_secs(1));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop { loop {
interval.tick().await;
let wakeup_fut = wake_up.recv(); let wakeup_fut = wake_up.recv();
// Listens for process changes and builds edvice lists. // Listens for process changes and builds edvice lists.
let listener_fut = async { let listener_fut = async {
let mut new_devices = Vec::new(); 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 = let mut change_stream =
match adapter_clone.discover_devices_with_changes().await { match adapter_clone.discover_devices_with_changes().await {
Ok(stream) => stream, Ok(stream) => stream,
Err(_) => { Err(_) => {
interval.tick().await; tick(&mut interval).await;
return; return;
} }
}; };
@ -768,7 +802,6 @@ async fn bluer_state(adapter: &Adapter) -> BluerState {
#[inline(never)] #[inline(never)]
async fn build_device_list(mut devices: Vec<BluerDevice>, adapter: &Adapter) -> Vec<BluerDevice> { async fn build_device_list(mut devices: Vec<BluerDevice>, adapter: &Adapter) -> Vec<BluerDevice> {
let addrs = adapter.device_addresses().await.unwrap_or_default(); let addrs = adapter.device_addresses().await.unwrap_or_default();
devices.clear(); devices.clear();
if addrs.len() > devices.capacity() { if addrs.len() > devices.capacity() {
devices.reserve(addrs.len() - devices.capacity()); devices.reserve(addrs.len() - devices.capacity());