D4:`pick` 的排序键从「来源序」改成「版本距离 → 来源序」,全不满足时选最接近要求的那个 (托管 25 与 JAVA_HOME/扫盘 17 之间选 17,而不是按 rank 选托管)。新增 `select::decide`: 不符且**没有人显式指定 Java** 时 `ull launch` 直接报 `java_mismatch` 不 spawn(`--java` / 实例 `javaPath` 是豁免,只告警)——1.12.2 配 Java 17/25 必然起不来,警告没有信息量。 `JavaMismatch` + `check` 拆到 `mismatch.rs` 并加 `advice()`(mapping.rs 408→282 行)。 D6:`LaunchSession::finish` 的等进程改成**有界**。判据在 `startup_watch.rs`(纯逻辑): ①窗口从未出现且**连续 N 秒没有任何日志**(还在打日志就说明还在加载,慢加载不被判死); ②日志出现关闭标记后 N 秒仍未退出(实机「关窗后 java.exe 仍存活」的形状)。 到点只**停止等待、绝不杀进程**:写 `session_end(still_running)` + 索引 + `launcher.log`, 返回 `LaunchError::Watchdog`,CLI 翻成 `launch_timeout`。两个时长可配 (默认 300s/60s;`ULL_LAUNCH_STARTUP_TIMEOUT_SECS` / `ULL_LAUNCH_SHUTDOWN_GRACE_SECS`, 生效值每次启动写进 launcher.log 与人读输出)。 D10:把「被终止」与「应用错误」分开。①我们自己杀的:`tree_kill` **送达即登记** `kill_registry`(pid 登记簿,取走语义、上限 64),覆盖 GUI 从别的线程 `kill_tree` 的路径, `finish` 据此判 `killed`;②外部强杀:Windows 上「退出码 1 + 零错误输出 + 无崩溃/JVM 关键字」 判 `killed` 并标 `terminated_externally`(任务管理器与 `taskkill /F` 都是 `TerminateProcess(h, 1)`,应用真崩给的是 NTSTATUS 异常码)。 顺带:`SessionHandle` 拆 `session_handle.rs`(launch_session.rs 422→343 行)、 `NativesReport::to_json` 落 core(cli/launch.rs 腾出空间,仍 399 行)。 用例:select 距离/取舍、startup_watch 状态机、watchdog_cases(4 条真进程)、 exit_cases(外部终止与「我们杀的」区分)、pipeline 跨线程 kill。
131 lines
4.4 KiB
Rust
131 lines
4.4 KiB
Rust
//! 一次启动的结果([`SessionHandle`])。
|
||
//!
|
||
//! 从 `launch_session` 拆出:那边是「跑一局 + 收尾」的流水线,这里是**纯数据 + 展示**。
|
||
//! 拆开的直接原因是行数——`launch_session` 加上 D6 的有界等待后越过了 400 行红线。
|
||
|
||
use std::path::PathBuf;
|
||
|
||
use serde_json::Value;
|
||
|
||
use crate::launch::args_table::AssembledCommand;
|
||
use crate::launch::crash_scan::CrashArtifact;
|
||
use crate::launch::exit_analysis::ExitAnalysis;
|
||
use crate::launch::session_index::SessionIndexEntry;
|
||
|
||
/// 一次启动的结果。
|
||
#[derive(Debug, Clone)]
|
||
pub struct SessionHandle {
|
||
/// 会话 id。
|
||
pub session_id: String,
|
||
/// 事件流文件。
|
||
pub log_path: PathBuf,
|
||
/// 组装结果(`argv()` 含凭据明文;对外展示/上报请用 [`Self::redacted_argv`])。
|
||
pub command: AssembledCommand,
|
||
/// 脱敏后的完整 argv(accessToken → `<redacted:token>`,主目录 → `<home>`)。
|
||
redacted_argv: Vec<String>,
|
||
/// 退出判定。
|
||
pub exit: ExitAnalysis,
|
||
/// 诊断结论(M15 产出,已写入事件流)。
|
||
pub diagnostics: Option<Value>,
|
||
/// 索引条目(写入 `index.jsonl` 的那一行)。
|
||
pub index: Option<SessionIndexEntry>,
|
||
/// 崩溃现场文件。
|
||
pub artifacts: Vec<CrashArtifact>,
|
||
/// 游戏存活毫秒数。
|
||
pub uptime_ms: u64,
|
||
/// 日志泵是否超时(watchdog 触发 = 有子进程没关 stdout)。
|
||
pub pump_stalled: bool,
|
||
/// 会话写入错误(磁盘问题;不影响进程结果)。
|
||
pub event_write_errors: Vec<String>,
|
||
}
|
||
|
||
impl SessionHandle {
|
||
/// 构造(`redacted_argv` 只在这里赋值,外部拿不到明文 argv 的那条路)。
|
||
#[allow(clippy::too_many_arguments)]
|
||
pub(crate) fn new(
|
||
session_id: String,
|
||
log_path: PathBuf,
|
||
command: AssembledCommand,
|
||
redacted_argv: Vec<String>,
|
||
exit: ExitAnalysis,
|
||
diagnostics: Option<Value>,
|
||
index: Option<SessionIndexEntry>,
|
||
artifacts: Vec<CrashArtifact>,
|
||
uptime_ms: u64,
|
||
pump_stalled: bool,
|
||
event_write_errors: Vec<String>,
|
||
) -> Self {
|
||
Self {
|
||
session_id,
|
||
log_path,
|
||
command,
|
||
redacted_argv,
|
||
exit,
|
||
diagnostics,
|
||
index,
|
||
artifacts,
|
||
uptime_ms,
|
||
pump_stalled,
|
||
event_write_errors,
|
||
}
|
||
}
|
||
|
||
/// 脱敏后的完整 argv(UI/日志/上报都用它,别直接外发 `command.argv()`)。
|
||
pub fn redacted_argv(&self) -> &[String] {
|
||
&self.redacted_argv
|
||
}
|
||
|
||
/// 脱敏后的一行人读命令行。
|
||
pub fn redacted_display(&self) -> String {
|
||
self.redacted_argv
|
||
.iter()
|
||
.map(|arg| crate::launch::command_builder::quote_for_display(arg))
|
||
.collect::<Vec<_>>()
|
||
.join(" ")
|
||
}
|
||
|
||
/// 是否“正常且无异常”:退出判定 NORMAL 且没有静默失败标记。
|
||
pub fn succeeded(&self) -> bool {
|
||
!self.exit.exit_type.is_abnormal() && self.exit.silent_failure.is_none()
|
||
}
|
||
|
||
/// 是否需要进诊断(等价于 `exit.needs_diagnosis`,保留给上层判空)。
|
||
pub fn needs_diagnosis(&self) -> bool {
|
||
self.exit.needs_diagnosis
|
||
}
|
||
|
||
/// 结局标签,与 GUI 的 `LogOutcome` 同词(`Ok` / `Crashed` / `SilentFail`)。
|
||
///
|
||
/// `launcher.log` 里两条路(GUI / `ull launch`)都写这一套词,见
|
||
/// [`crate::launch::launcher_log::launch_end_line`]。
|
||
pub fn outcome_label(&self) -> &'static str {
|
||
if self.exit.silent_failure.is_some() {
|
||
"SilentFail"
|
||
} else if self.succeeded() {
|
||
"Ok"
|
||
} else {
|
||
"Crashed"
|
||
}
|
||
}
|
||
|
||
/// 一句话摘要。
|
||
pub fn summary(&self) -> String {
|
||
let mut text = format!("会话 {}:{}", self.session_id, self.exit.summary());
|
||
if self.pump_stalled {
|
||
text.push_str(";日志泵超时");
|
||
}
|
||
if let Some(top) = &self.diagnostics {
|
||
if let Some(id) = top
|
||
.get("candidates")
|
||
.and_then(Value::as_array)
|
||
.and_then(|items| items.first())
|
||
.and_then(|item| item.get("id"))
|
||
.and_then(Value::as_str)
|
||
{
|
||
text.push_str(&format!(";诊断首因 {id}"));
|
||
}
|
||
}
|
||
text
|
||
}
|
||
}
|