diff --git a/docs/windows-session-runtime.md b/docs/windows-session-runtime.md index 1bfb5fa..e6c5085 100644 --- a/docs/windows-session-runtime.md +++ b/docs/windows-session-runtime.md @@ -78,6 +78,8 @@ Session Host 每次连接新建一个进程,不常驻、不复用运行时, 任一步失败都必须关闭包通道并收敛为 Disconnected,只返回有界脱敏错误;未完成握手的 Session Host 最多等待 15 秒后退出。 +Session Host 的路径读取和启动失败日志必须在同一次 WinRT 初始化周期内完成;`RoUninitialize` 后不能再次查询缓存的 `ApplicationData` factory。没有 package identity 的裸启动快速、安全退出,不进入握手、不启动 backend,也不回退到普通用户数据目录。 + ## 会合记录 `LocalState/vcore/windows/rendezvous.json` 最大 4 KiB: diff --git a/src/bin/windows_session_host.rs b/src/bin/windows_session_host.rs index eb6e009..a91bc56 100644 --- a/src/bin/windows_session_host.rs +++ b/src/bin/windows_session_host.rs @@ -2,9 +2,7 @@ #[cfg(windows)] fn main() { - if let Err(error) = vcore::windows::session::run() { - vcore::windows::session::log_startup_failure(&error); - } + let _ = vcore::windows::session::run(); } #[cfg(not(windows))] diff --git a/src/windows/session.rs b/src/windows/session.rs index 4b1a5fd..776d06a 100644 --- a/src/windows/session.rs +++ b/src/windows/session.rs @@ -64,6 +64,15 @@ impl Drop for WinRtGuard { #[doc(hidden)] pub fn run() -> io::Result<()> { let _winrt = WinRtGuard::enter()?; + let result = run_initialized(); + if result.is_err() { + // Cached WinRT factories must not be queried after RoUninitialize. + log_startup_failure(); + } + result +} + +fn run_initialized() -> io::Result<()> { let (local_folder, installed_folder) = package_folders()?; log::append(&local_folder, "session", "Session Host starting"); let runtime = tokio::runtime::Builder::new_current_thread() @@ -83,8 +92,7 @@ pub fn run() -> io::Result<()> { result } -#[doc(hidden)] -pub fn log_startup_failure(_error: &io::Error) { +fn log_startup_failure() { if let Ok((local_folder, _)) = package_folders() { log::append(&local_folder, "session", "Session Host startup failed"); } diff --git a/tests/windows_session_startup.rs b/tests/windows_session_startup.rs new file mode 100644 index 0000000..09b5c31 --- /dev/null +++ b/tests/windows_session_startup.rs @@ -0,0 +1,52 @@ +#![cfg(all(windows, feature = "ffi"))] + +use std::{ + os::windows::process::CommandExt, + process::{Command, Stdio}, + ptr, thread, + time::{Duration, Instant}, +}; + +#[link(name = "kernel32")] +unsafe extern "system" { + fn GetCurrentPackageFullName(length: *mut u32, name: *mut u16) -> i32; + fn SetErrorMode(mode: u32) -> u32; +} + +#[test] +fn unpackaged_session_host_exits_without_crashing() { + let mut length = 0; + // Do not activate a packaged session or touch package-local runtime data. + assert_eq!( + unsafe { GetCurrentPackageFullName(&mut length, ptr::null_mut()) }, + 15700, + "this test requires an unpackaged process" + ); + + // Child processes inherit this mode; suppress loader and crash dialogs. + let previous = unsafe { SetErrorMode(0x8003) }; + let spawned = Command::new(env!("CARGO_BIN_EXE_vcore-windows-session-host")) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .creation_flags(0x08000000) + .spawn(); + unsafe { SetErrorMode(previous) }; + let mut child = spawned.expect("start the session host"); + let deadline = Instant::now() + Duration::from_secs(10); + loop { + if let Some(status) = child.try_wait().expect("read session host status") { + assert!( + status.success(), + "unpackaged startup must exit cleanly: {status}" + ); + break; + } + if Instant::now() >= deadline { + child.kill().expect("terminate the timed-out test process"); + child.wait().expect("reap the timed-out test process"); + panic!("unpackaged startup must not wait for a VPN session"); + } + thread::sleep(Duration::from_millis(20)); + } +}