Fix daemon autostart issue caused by sync pipewire retry loop (#43)

At boot time, PipeWire takes some time to register the `pwsp-daemon` and `pwsp-virtual-mic` devices. Previously, the daemon's retry loop for `link_player_to_virtual_mic()` was synchronous and limited to 5 attempts (1.5 seconds total). This caused systemd autostarts to fail with a code 1 if the devices were not yet available.

This change replaces the synchronous wait with an asynchronous `tokio::spawn` task. It will retry the link attempt up to 60 times with a 1-second delay without blocking the startup of the rest of the daemon. This prevents it from exiting abruptly during autostart.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Tarasov Aleksandr
2026-03-22 17:05:26 +03:00
committed by GitHub
parent 6114b9a7f8
commit f01a0e656c
5 changed files with 58 additions and 38 deletions
+17 -10
View File
@@ -31,16 +31,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
eprintln!("Failed to initialize audio player: {}", err); eprintln!("Failed to initialize audio player: {}", err);
} // Initialize audio player } // Initialize audio player
let max_retries = 5; tokio::spawn(async {
for i in 0..=max_retries { let max_retries = 60;
match link_player_to_virtual_mic().await { for i in 0..=max_retries {
Ok(_) => break, match link_player_to_virtual_mic().await {
Err(e) => println!("{e}\t{i}/{max_retries}"), Ok(_) => {
} println!("Successfully linked player to virtual mic.");
break;
}
Err(e) => println!("{e}\t{i}/{max_retries}"),
}
sleep(Duration::from_millis(300 * i)).await; sleep(Duration::from_millis(1000)).await;
} }
link_player_to_virtual_mic().await?; });
let runtime_dir = get_runtime_dir(); let runtime_dir = get_runtime_dir();
@@ -97,7 +101,10 @@ async fn commands_loop(listener: UnixListener) -> Result<(), Box<dyn Error>> {
let request_len = u32::from_le_bytes(len_bytes) as usize; let request_len = u32::from_le_bytes(len_bytes) as usize;
if request_len > 10 * 1024 * 1024 { if request_len > 10 * 1024 * 1024 {
eprintln!("Failed to read message from client: request too large ({} bytes)!", request_len); eprintln!(
"Failed to read message from client: request too large ({} bytes)!",
request_len
);
return; return;
} }
+18 -16
View File
@@ -86,14 +86,15 @@ impl SoundpadGui {
.as_ref() .as_ref()
.and_then(|cd| dirs.iter().position(|x| x == cd)); .and_then(|cd| dirs.iter().position(|x| x == cd));
let new_dir_index = match (current_dir_index, arrow_up_pressed, arrow_down_pressed) { let new_dir_index =
(Some(i), true, false) => (i + dirs.len() - 1) % dirs.len(), match (current_dir_index, arrow_up_pressed, arrow_down_pressed) {
(Some(i), false, true) => (i + 1) % dirs.len(), (Some(i), true, false) => (i + dirs.len() - 1) % dirs.len(),
(Some(i), true, true) => i, (Some(i), false, true) => (i + 1) % dirs.len(),
(None, true, _) => dirs.len() - 1, (Some(i), true, true) => i,
(None, false, true) => 0, (None, true, _) => dirs.len() - 1,
_ => return, (None, false, true) => 0,
}; _ => return,
};
self.open_dir(&dirs[new_dir_index]); self.open_dir(&dirs[new_dir_index]);
} else if self.app_state.current_dir.is_some() { } else if self.app_state.current_dir.is_some() {
@@ -109,14 +110,15 @@ impl SoundpadGui {
.as_ref() .as_ref()
.and_then(|f| files.iter().position(|x| x == f)); .and_then(|f| files.iter().position(|x| x == f));
let new_files_index = match (current_files_index, arrow_up_pressed, arrow_down_pressed) { let new_files_index =
(Some(i), true, false) => (i + files.len() - 1) % files.len(), match (current_files_index, arrow_up_pressed, arrow_down_pressed) {
(Some(i), false, true) => (i + 1) % files.len(), (Some(i), true, false) => (i + files.len() - 1) % files.len(),
(Some(i), true, true) => i, (Some(i), false, true) => (i + 1) % files.len(),
(None, true, _) => files.len() - 1, (Some(i), true, true) => i,
(None, false, true) => 0, (None, true, _) => files.len() - 1,
_ => return, (None, false, true) => 0,
}; _ => return,
};
self.app_state.selected_file = Some(files[new_files_index].clone()); self.app_state.selected_file = Some(files[new_files_index].clone());
} }
+8 -2
View File
@@ -58,7 +58,10 @@ impl SoundpadGui {
pub fn play_toggle(&mut self) { pub fn play_toggle(&mut self) {
let (new_state, request) = { let (new_state, request) = {
let guard = self.audio_player_state_shared.lock().unwrap_or_else(|e| e.into_inner()); let guard = self
.audio_player_state_shared
.lock()
.unwrap_or_else(|e| e.into_inner());
match guard.state { match guard.state {
PlayerState::Playing => (Some(PlayerState::Paused), Some(Request::pause(None))), PlayerState::Playing => (Some(PlayerState::Paused), Some(Request::pause(None))),
PlayerState::Paused => (Some(PlayerState::Playing), Some(Request::resume(None))), PlayerState::Paused => (Some(PlayerState::Playing), Some(Request::resume(None))),
@@ -71,7 +74,10 @@ impl SoundpadGui {
} }
if let Some(state) = new_state { if let Some(state) = new_state {
let mut guard = self.audio_player_state_shared.lock().unwrap_or_else(|e| e.into_inner()); let mut guard = self
.audio_player_state_shared
.lock()
.unwrap_or_else(|e| e.into_inner());
guard.new_state = Some(state.clone()); guard.new_state = Some(state.clone());
guard.state = state; guard.state = state;
} }
+4 -1
View File
@@ -77,7 +77,10 @@ impl App for SoundpadGui {
// Sync audio player state // Sync audio player state
{ {
let guard = self.audio_player_state_shared.lock().unwrap_or_else(|e| e.into_inner()); let guard = self
.audio_player_state_shared
.lock()
.unwrap_or_else(|e| e.into_inner());
self.audio_player_state = guard.clone(); self.audio_player_state = guard.clone();
} }
+11 -9
View File
@@ -280,16 +280,18 @@ impl AudioPlayer {
) -> Result<u32, Box<dyn Error>> { ) -> Result<u32, Box<dyn Error>> {
let path_buf = file_path.to_path_buf(); let path_buf = file_path.to_path_buf();
let decoder_result = tokio::task::spawn_blocking(move || -> Result<_, Box<dyn Error + Send + Sync>> { let decoder_result =
if !path_buf.exists() { tokio::task::spawn_blocking(move || -> Result<_, Box<dyn Error + Send + Sync>> {
return Err(format!("File does not exist: {}", path_buf.display()).into()); if !path_buf.exists() {
} return Err(format!("File does not exist: {}", path_buf.display()).into());
}
let file = fs::File::open(&path_buf)?; let file = fs::File::open(&path_buf)?;
let decoder = Decoder::try_from(file).map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)?; let decoder = Decoder::try_from(file)
Ok(decoder) .map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)?;
}) Ok(decoder)
.await?; })
.await?;
match decoder_result { match decoder_result {
Ok(source) => { Ok(source) => {