Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/windows-session-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 1 addition & 3 deletions src/bin/windows_session_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
12 changes: 10 additions & 2 deletions src/windows/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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");
}
Expand Down
52 changes: 52 additions & 0 deletions tests/windows_session_startup.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading