fix(backend_detect): add 5s timeout to Portal availability check
Prevents indefinite hang when xdg-desktop-portal service is stuck. Previously the check used zbus::Connection::session() with no timeout, waiting forever for D-Bus responses. User observed 11+ second delay at startup when Portal service was wedged, causing 'client can't connect' because wl-webrtc never reached the WebRTC signaling stage. Changes per Oracle review (Phase 1 of 2 for Portal resilience): - Replace Connection::session() with connection::Builder::session() + method_timeout(5s) to bound method replies - Wrap each async operation (connection build, proxy build, version query) with tokio::time::timeout(5s) for comprehensive coverage - Add log_portal_unresponsive() helper with actionable diagnostic: 'systemctl --user restart xdg-desktop-portal xdg-desktop-portal-kde' - Return false on timeout (existing behavior) so caller falls through to wlr-screencopy detection or fails with clear error Why both method_timeout AND tokio::time::timeout (per Oracle): - method_timeout bounds D-Bus method reply waits - tokio::time::timeout bounds connection/proxy setup and any ashpd future composition (relevant for Phase 2) - Neither alone is sufficient What this does NOT do (deferred to Phase 2 / cap_portal.rs): - Token-aware retry logic (Phase 2) - --no-persist suggestion in diagnostic (Phase 2: only appropriate when restore token was actually in use) - Phased diagnostics for CreateSession/SelectSources/Start operations - Runtime watchdog zbus version note: crate uses zbus 5.x with tokio feature only. Builder::method_timeout() available in zbus 5.x. Tests: - cargo build --release: 0 new warnings (19 baseline preserved) - cargo test: 97 lib + 3 integration, 0 failed - 1 file changed, +55/-9 lines
This commit is contained in:
+55
-9
@@ -1,3 +1,5 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use wayland_client::globals::registry_queue_init;
|
use wayland_client::globals::registry_queue_init;
|
||||||
use wayland_client::globals::GlobalListContents;
|
use wayland_client::globals::GlobalListContents;
|
||||||
@@ -40,6 +42,20 @@ impl Dispatch<WlRegistry, GlobalListContents> for RegistryLs {
|
|||||||
// CAUTION: must NOT use ashpd here — ashpd caches zbus::Connection in a global
|
// CAUTION: must NOT use ashpd here — ashpd caches zbus::Connection in a global
|
||||||
// OnceLock; if the tokio runtime owning that connection is dropped before
|
// OnceLock; if the tokio runtime owning that connection is dropped before
|
||||||
// setup_portal() runs, the cached connection becomes dead and hangs forever.
|
// setup_portal() runs, the cached connection becomes dead and hangs forever.
|
||||||
|
|
||||||
|
/// Per-operation D-Bus timeout for Portal backend detection.
|
||||||
|
/// Portal 后端检测期间每个 D-Bus 操作的超时时间。
|
||||||
|
const PORTAL_DBUS_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
fn log_portal_unresponsive(operation: &str) {
|
||||||
|
tracing::error!(
|
||||||
|
"Portal service did not respond within 5s while {operation}. \
|
||||||
|
This usually means xdg-desktop-portal or xdg-desktop-portal-kde is stuck. \
|
||||||
|
Try: systemctl --user restart xdg-desktop-portal xdg-desktop-portal-kde, \
|
||||||
|
then re-run wl-webrtc."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn check_portal_available() -> bool {
|
fn check_portal_available() -> bool {
|
||||||
let rt = match tokio::runtime::Runtime::new() {
|
let rt = match tokio::runtime::Runtime::new() {
|
||||||
Ok(rt) => rt,
|
Ok(rt) => rt,
|
||||||
@@ -50,12 +66,27 @@ fn check_portal_available() -> bool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let conn = match zbus::Connection::session().await {
|
// Set method_timeout on the connection (bounds method replies) and wrap
|
||||||
Ok(c) => c,
|
// the build itself in tokio::time::timeout (bounds connection setup).
|
||||||
Err(e) => {
|
// 同时设置 method_timeout 与外层 tokio::time::timeout 双重保护。
|
||||||
|
let conn = match tokio::time::timeout(PORTAL_DBUS_TIMEOUT, async {
|
||||||
|
zbus::connection::Builder::session()
|
||||||
|
.expect("D-Bus session bus builder failed")
|
||||||
|
.method_timeout(PORTAL_DBUS_TIMEOUT)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(c)) => c,
|
||||||
|
Ok(Err(e)) => {
|
||||||
tracing::info!("D-Bus session bus unavailable: {e}");
|
tracing::info!("D-Bus session bus unavailable: {e}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log_portal_unresponsive("connecting to D-Bus session bus");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let inner: zbus::Proxy = match zbus::proxy::Builder::new(&conn)
|
let inner: zbus::Proxy = match zbus::proxy::Builder::new(&conn)
|
||||||
@@ -63,12 +94,16 @@ fn check_portal_available() -> bool {
|
|||||||
.and_then(|b| b.path("/org/freedesktop/portal/desktop"))
|
.and_then(|b| b.path("/org/freedesktop/portal/desktop"))
|
||||||
.and_then(|b| b.interface("org.freedesktop.portal.ScreenCast"))
|
.and_then(|b| b.interface("org.freedesktop.portal.ScreenCast"))
|
||||||
{
|
{
|
||||||
Ok(b) => match b.build().await {
|
Ok(b) => match tokio::time::timeout(PORTAL_DBUS_TIMEOUT, b.build()).await {
|
||||||
Ok(p) => p,
|
Ok(Ok(p)) => p,
|
||||||
Err(e) => {
|
Ok(Err(e)) => {
|
||||||
tracing::info!("Portal ScreenCast interface not available: {e}");
|
tracing::info!("Portal ScreenCast interface not available: {e}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log_portal_unresponsive("building ScreenCast proxy");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::info!("Portal ScreenCast proxy build failed: {e}");
|
tracing::info!("Portal ScreenCast proxy build failed: {e}");
|
||||||
@@ -76,15 +111,26 @@ fn check_portal_available() -> bool {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = match inner.get_property::<u32>("version").await {
|
// The most likely operation to hang — requires actual Portal-side work.
|
||||||
Ok(version) => {
|
// 最可能卡住的操作,需要 Portal 端实际处理。
|
||||||
|
let version = match tokio::time::timeout(
|
||||||
|
PORTAL_DBUS_TIMEOUT,
|
||||||
|
inner.get_property::<u32>("version"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(version)) => {
|
||||||
tracing::info!("Portal ScreenCast available (version: {version})");
|
tracing::info!("Portal ScreenCast available (version: {version})");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Ok(Err(e)) => {
|
||||||
tracing::info!("Portal ScreenCast version query failed: {e}");
|
tracing::info!("Portal ScreenCast version query failed: {e}");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log_portal_unresponsive("querying ScreenCast version");
|
||||||
|
false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
version
|
version
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user