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 wayland_client::globals::registry_queue_init;
|
||||
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
|
||||
// OnceLock; if the tokio runtime owning that connection is dropped before
|
||||
// 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 {
|
||||
let rt = match tokio::runtime::Runtime::new() {
|
||||
Ok(rt) => rt,
|
||||
@@ -50,12 +66,27 @@ fn check_portal_available() -> bool {
|
||||
};
|
||||
|
||||
rt.block_on(async {
|
||||
let conn = match zbus::Connection::session().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
// Set method_timeout on the connection (bounds method replies) and wrap
|
||||
// the build itself in tokio::time::timeout (bounds connection setup).
|
||||
// 同时设置 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}");
|
||||
return false;
|
||||
}
|
||||
Err(_) => {
|
||||
log_portal_unresponsive("connecting to D-Bus session bus");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
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.interface("org.freedesktop.portal.ScreenCast"))
|
||||
{
|
||||
Ok(b) => match b.build().await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
Ok(b) => match tokio::time::timeout(PORTAL_DBUS_TIMEOUT, b.build()).await {
|
||||
Ok(Ok(p)) => p,
|
||||
Ok(Err(e)) => {
|
||||
tracing::info!("Portal ScreenCast interface not available: {e}");
|
||||
return false;
|
||||
}
|
||||
Err(_) => {
|
||||
log_portal_unresponsive("building ScreenCast proxy");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
Err(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 {
|
||||
Ok(version) => {
|
||||
// The most likely operation to hang — requires actual Portal-side work.
|
||||
// 最可能卡住的操作,需要 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})");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
Ok(Err(e)) => {
|
||||
tracing::info!("Portal ScreenCast version query failed: {e}");
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
log_portal_unresponsive("querying ScreenCast version");
|
||||
false
|
||||
}
|
||||
};
|
||||
version
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user