fix(android): 8s Rust timeout on audio_start; always emit connect: debug events

- engine.rs: wrap spawn_blocking(audio_start) in an 8s tokio timeout so
  the connect command fails fast with a clear error if the Oboe HAL
  never returns, instead of blocking the JS 45s timer
- lib.rs: emit_call_debug now always forwards connect: and
  register_signal: steps to the JS overlay regardless of the debug-logs
  toggle — needed because app-data clears reset the toggle to false,
  making join failures invisible on first install
- main.ts: JS timeout bumped to 45s (Rust 8s fires first); timeout
  message now includes last native connect: step so the toast is
  actionable without opening the debug log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 07:49:21 +04:00
parent 2aa6582585
commit 394987a349
3 changed files with 48 additions and 19 deletions

View File

@@ -683,19 +683,32 @@ impl CallEngine {
"connect:audio_start_start",
serde_json::json!({ "t_ms": t_pre_audio }),
);
let audio_start_result = tokio::task::spawn_blocking(crate::wzp_native::audio_start)
.await
.map_err(|e| {
crate::emit_call_debug(
&app,
"connect:audio_start_panic",
serde_json::json!({
"t_ms": call_t0.elapsed().as_millis(),
"error": e.to_string(),
}),
);
anyhow::anyhow!("audio_start task panic: {e}")
})?;
let audio_start_task = tokio::task::spawn_blocking(crate::wzp_native::audio_start);
let audio_start_result =
match tokio::time::timeout(std::time::Duration::from_secs(8), audio_start_task).await {
Ok(join_result) => join_result.map_err(|e| {
crate::emit_call_debug(
&app,
"connect:audio_start_panic",
serde_json::json!({
"t_ms": call_t0.elapsed().as_millis(),
"error": e.to_string(),
}),
);
anyhow::anyhow!("audio_start task panic: {e}")
})?,
Err(_) => {
crate::emit_call_debug(
&app,
"connect:audio_start_timeout",
serde_json::json!({
"t_ms": call_t0.elapsed().as_millis(),
"timeout_ms": 8000,
}),
);
return Err(anyhow::anyhow!("wzp_native_audio_start timed out after 8s"));
}
};
if let Err(code) = audio_start_result {
crate::emit_call_debug(
&app,

View File

@@ -59,13 +59,15 @@ fn set_call_debug_logs_internal(on: bool) {
CALL_DEBUG_LOGS.store(on, Ordering::Relaxed);
}
/// Emit a `call-debug-log` event to the JS side IF the flag is on.
/// Emit a `call-debug-log` event to the JS side.
/// Also mirrors to `tracing::info!` so logcat keeps its copy
/// regardless of the flag — the toggle only controls the GUI
/// overlay, not the underlying Android log stream.
/// regardless of the flag. Connect/register steps are always emitted
/// because they are needed to diagnose failed joins after app data is
/// cleared and the GUI debug toggle is back to its default false value.
pub(crate) fn emit_call_debug(app: &tauri::AppHandle, step: &str, details: serde_json::Value) {
tracing::info!(step, ?details, "call-debug");
if !call_debug_logs_enabled() {
let force_emit = step.starts_with("connect:") || step.starts_with("register_signal:");
if !force_emit && !call_debug_logs_enabled() {
return;
}
let payload = serde_json::json!({

View File

@@ -195,11 +195,24 @@ function errorMessage(e: unknown): string {
return String(e);
}
function connectWithTimeout(args: Record<string, unknown>, timeoutMs = 15000) {
function connectDebugSummary(entry: CallDebugEntry | null): string {
if (!entry) return "no native connect event received";
const details = entry.details && typeof entry.details === "object"
? JSON.stringify(entry.details)
: String(entry.details ?? "");
return `${entry.step}${details ? ` ${details}` : ""}`;
}
let lastConnectDebug: CallDebugEntry | null = null;
function connectWithTimeout(args: Record<string, unknown>, timeoutMs = 45000) {
lastConnectDebug = null;
return Promise.race([
invoke("connect", args),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("connect timed out (15s) - check audio permissions")), timeoutMs)
setTimeout(() => reject(new Error(
`connect timed out (${Math.round(timeoutMs / 1000)}s); last native step: ${connectDebugSummary(lastConnectDebug)}`
)), timeoutMs)
),
]);
}
@@ -221,6 +234,7 @@ const CALL_DEBUG_MAX = 200;
listen("call-debug-log", (event: any) => {
const entry: CallDebugEntry = event.payload;
callDebugBuffer.push(entry);
if (entry.step?.startsWith("connect:")) lastConnectDebug = entry;
if (callDebugBuffer.length > CALL_DEBUG_MAX) callDebugBuffer.shift();
renderCallDebugLog();
});