Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Define ROCKBOX_SERVER so apps/main.c calls server_init()

Daemon was reaching the engine thread, main_c() was running, init() was
completing — but no port ever bound on 6061/6062/6063/6600 because
apps/main.c:486 gates server_init() and broker_init() behind
#ifdef ROCKBOX_SERVER. Without it the firmware boots fine but the
kernel server thread (which runs crates/server::start_server +
start_servers) is never spawned.

Added #define ROCKBOX_SERVER to androidcdylib.h. Desktop sdlapp builds
define it via configure; embedded-daemon needs the same.

Also added diagnostics to daemon.rs (panic catch in engine thread,
panic hook for tracing-android, checkpoint logs, 30s timeout instead
of 5s) so the next time something hangs/crashes we'll see why.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

+48 -13
+4
Cargo.lock
··· 9249 9249 "once_cell", 9250 9250 "prost", 9251 9251 "reqwest", 9252 + "rockbox-airplay", 9253 + "rockbox-chromecast", 9252 9254 "rockbox-discovery", 9253 9255 "rockbox-library", 9254 9256 "rockbox-server", 9255 9257 "rockbox-settings", 9258 + "rockbox-slim", 9256 9259 "rockbox-sys", 9260 + "rockbox-upnp", 9257 9261 "serde", 9258 9262 "serde_json", 9259 9263 "tokio",
+36 -13
crates/expo/src/daemon.rs
··· 66 66 /// unrelated `pcm_<sink>_*` exports. Referencing a real C-ABI fn pulls 67 67 /// the entire crate's #[no_mangle] export set into the cdylib. 68 68 #[used] 69 - static _KEEPALIVE_AIRPLAY: unsafe extern "C" fn(*const c_char, u16) = 69 + static _KEEPALIVE_AIRPLAY: unsafe extern "C" fn(*const c_char, u16) = 70 70 rockbox_airplay::pcm_airplay_set_host; 71 71 #[used] 72 - static _KEEPALIVE_SLIM: extern "C" fn(u16) = 73 - rockbox_slim::pcm_squeezelite_set_slim_port; 72 + static _KEEPALIVE_SLIM: extern "C" fn(u16) = rockbox_slim::pcm_squeezelite_set_slim_port; 74 73 #[used] 75 74 static _KEEPALIVE_CHROMECAST: unsafe extern "C" fn(*const c_char) = 76 75 rockbox_chromecast::pcm::pcm_chromecast_set_device_host; 77 76 #[used] 78 - static _KEEPALIVE_UPNP: extern "C" fn(u16) = 79 - rockbox_upnp::pcm_upnp_set_http_port; 77 + static _KEEPALIVE_UPNP: extern "C" fn(u16) = rockbox_upnp::pcm_upnp_set_http_port; 80 78 81 79 /// `save_remote_track_metadata` — port of crates/cli's identical function 82 80 /// (we don't depend on rockbox-cli because of its host-only assumptions: ··· 100 98 let rt = match tokio::runtime::Runtime::new() { 101 99 Ok(rt) => rt, 102 100 Err(e) => { 103 - tracing::error!("save_remote_track_metadata: failed to create runtime: {}", e); 101 + tracing::error!( 102 + "save_remote_track_metadata: failed to create runtime: {}", 103 + e 104 + ); 104 105 return -1; 105 106 } 106 107 }; ··· 178 179 179 180 fn install_logcat_subscriber() { 180 181 // Idempotent: try_init returns Err on second call, which we ignore. 182 + // Also install a panic hook that routes Rust panics to logcat — by 183 + // default a panic in a spawned thread produces no output anywhere 184 + // we can see. 185 + std::panic::set_hook(Box::new(|info| { 186 + tracing::error!(target: "rockbox", "Rust panic: {}", info); 187 + })); 181 188 let _ = tracing_subscriber::registry() 182 189 .with( 183 190 tracing_subscriber::EnvFilter::try_from_default_env() ··· 224 231 } 225 232 226 233 install_logcat_subscriber(); 234 + tracing::info!("daemon: install_logcat_subscriber done"); 227 235 configure_environment(config_dir, music_dir, device_name); 236 + tracing::info!("daemon: env configured (HOME={} MUSIC={} NAME={})", 237 + config_dir, music_dir, device_name); 228 238 229 239 let port: u16 = std::env::var("ROCKBOX_PORT") 230 240 .ok() 231 241 .and_then(|s| s.parse().ok()) 232 242 .unwrap_or(6061); 233 243 234 - // Boot the firmware on a dedicated pthread. apps/main.c::main_c never 235 - // returns under normal operation; if it does (panic, exit), we transition 236 - // back to STOPPED so the next start works. 244 + // Catch panics in the engine thread — they're otherwise silent and 245 + // we'd see "did not bind within 5s" with no idea why. Plus log a 246 + // checkpoint right before main_c() so we know if the C side was 247 + // even reached. 248 + tracing::info!("daemon: spawning rockbox-engine thread"); 237 249 thread::Builder::new() 238 250 .name("rockbox-engine".into()) 239 251 .stack_size(2 * 1024 * 1024) 240 252 .spawn(move || { 241 - let rc = unsafe { main_c() }; 242 - tracing::warn!("rockbox engine exited rc={}", rc); 253 + tracing::info!("rockbox-engine: thread started, calling main_c()"); 254 + let rc = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 255 + unsafe { main_c() } 256 + })); 257 + match rc { 258 + Ok(code) => tracing::warn!("rockbox-engine: main_c returned rc={}", code), 259 + Err(p) => tracing::error!("rockbox-engine: PANICKED — {:?}", 260 + p.downcast_ref::<&str>() 261 + .map(|s| *s) 262 + .or_else(|| p.downcast_ref::<String>().map(|s| s.as_str())) 263 + .unwrap_or("<non-string panic>")), 264 + } 243 265 STATE.store(STATE_STOPPED, Ordering::Release); 244 266 LOCAL_PORT.store(0, Ordering::Release); 245 267 }) 246 268 .expect("spawn rockbox-engine thread"); 247 269 248 - if !wait_for_grpc(port, Instant::now() + Duration::from_secs(5)) { 249 - tracing::error!("daemon: gRPC server did not bind within 5s"); 270 + tracing::info!("daemon: waiting up to 30s for gRPC :{} to bind", port); 271 + if !wait_for_grpc(port, Instant::now() + Duration::from_secs(30)) { 272 + tracing::error!("daemon: gRPC server did not bind within 30s — engine stuck or crashed silently"); 250 273 STATE.store(STATE_STOPPED, Ordering::Release); 251 274 return -110; 252 275 }
+8
firmware/export/config/androidcdylib.h
··· 65 65 66 66 #define AB_REPEAT_ENABLE 67 67 68 + /* THIS IS THE MAGIC FLAG. apps/main.c:486 gates server_init() (which spawns 69 + * the kernel thread that runs crates/server::start_server + start_servers, 70 + * binding gRPC/HTTP/GraphQL/MPD on 6061/6063/6062/6600) behind 71 + * #ifdef ROCKBOX_SERVER. Without it the firmware boots fine but nothing 72 + * ever binds. The desktop sdlapp build defines this via configure; 73 + * embedded-daemon needs the same. */ 74 + #define ROCKBOX_SERVER 75 + 68 76 /* `sigevent_t` (glibc-style typedef used by kernel-unix.c) is provided as 69 77 * a -D macro in androidcdylibcc rather than typedef'd here — putting code 70 78 * in config.h would leak into the output of `preprocess` (which uses