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);
} // Initialize audio player
let max_retries = 5;
for i in 0..=max_retries {
match link_player_to_virtual_mic().await {
Ok(_) => break,
Err(e) => println!("{e}\t{i}/{max_retries}"),
}
tokio::spawn(async {
let max_retries = 60;
for i in 0..=max_retries {
match link_player_to_virtual_mic().await {
Ok(_) => {
println!("Successfully linked player to virtual mic.");
break;
}
Err(e) => println!("{e}\t{i}/{max_retries}"),
}
sleep(Duration::from_millis(300 * i)).await;
}
link_player_to_virtual_mic().await?;
sleep(Duration::from_millis(1000)).await;
}
});
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;
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;
}
+18 -16
View File
@@ -86,14 +86,15 @@ impl SoundpadGui {
.as_ref()
.and_then(|cd| dirs.iter().position(|x| x == cd));
let new_dir_index = match (current_dir_index, arrow_up_pressed, arrow_down_pressed) {
(Some(i), true, false) => (i + dirs.len() - 1) % dirs.len(),
(Some(i), false, true) => (i + 1) % dirs.len(),
(Some(i), true, true) => i,
(None, true, _) => dirs.len() - 1,
(None, false, true) => 0,
_ => return,
};
let new_dir_index =
match (current_dir_index, arrow_up_pressed, arrow_down_pressed) {
(Some(i), true, false) => (i + dirs.len() - 1) % dirs.len(),
(Some(i), false, true) => (i + 1) % dirs.len(),
(Some(i), true, true) => i,
(None, true, _) => dirs.len() - 1,
(None, false, true) => 0,
_ => return,
};
self.open_dir(&dirs[new_dir_index]);
} else if self.app_state.current_dir.is_some() {
@@ -109,14 +110,15 @@ impl SoundpadGui {
.as_ref()
.and_then(|f| files.iter().position(|x| x == f));
let new_files_index = match (current_files_index, arrow_up_pressed, arrow_down_pressed) {
(Some(i), true, false) => (i + files.len() - 1) % files.len(),
(Some(i), false, true) => (i + 1) % files.len(),
(Some(i), true, true) => i,
(None, true, _) => files.len() - 1,
(None, false, true) => 0,
_ => return,
};
let new_files_index =
match (current_files_index, arrow_up_pressed, arrow_down_pressed) {
(Some(i), true, false) => (i + files.len() - 1) % files.len(),
(Some(i), false, true) => (i + 1) % files.len(),
(Some(i), true, true) => i,
(None, true, _) => files.len() - 1,
(None, false, true) => 0,
_ => return,
};
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) {
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 {
PlayerState::Playing => (Some(PlayerState::Paused), Some(Request::pause(None))),
PlayerState::Paused => (Some(PlayerState::Playing), Some(Request::resume(None))),
@@ -71,7 +74,10 @@ impl SoundpadGui {
}
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.state = state;
}
+4 -1
View File
@@ -77,7 +77,10 @@ impl App for SoundpadGui {
// 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();
}
+11 -9
View File
@@ -280,16 +280,18 @@ impl AudioPlayer {
) -> Result<u32, Box<dyn Error>> {
let path_buf = file_path.to_path_buf();
let decoder_result = tokio::task::spawn_blocking(move || -> Result<_, Box<dyn Error + Send + Sync>> {
if !path_buf.exists() {
return Err(format!("File does not exist: {}", path_buf.display()).into());
}
let decoder_result =
tokio::task::spawn_blocking(move || -> Result<_, Box<dyn Error + Send + Sync>> {
if !path_buf.exists() {
return Err(format!("File does not exist: {}", path_buf.display()).into());
}
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>)?;
Ok(decoder)
})
.await?;
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>)?;
Ok(decoder)
})
.await?;
match decoder_result {
Ok(source) => {