fix(core): 老版本 Java 按版本距离选 + 启动看门狗有界等待 + 结束归属(M214 D4/D6/D10)
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。
This commit is contained in:
1 parent
5959a27add
commit
ff78fe2cae
23 files changed
+1708
-250
No files matched your search
@@ -12,6 +12,7 @@ use unlucky_core::game::install::instance::{
|
||||
read_instance_config, write_instance_config, InstanceConfig, Isolation,
|
||||
};
|
||||
use unlucky_core::game::isolation::{derive_game_dir, observe_mode, IsolationMode};
|
||||
use unlucky_core::game::java::select;
|
||||
use unlucky_core::game::ProfileList;
|
||||
use unlucky_core::launch::time::{format_rfc3339_millis, now};
|
||||
use unlucky_core::launch::{LaunchError, Launcher, LauncherConfig};
|
||||
@@ -98,8 +99,19 @@ pub fn run(ctx: &Ctx, args: &LaunchArgs) -> Result<Report, CliError> {
|
||||
..input
|
||||
};
|
||||
|
||||
// D13:实例覆盖 > 托管匹配 > 系统。托管命中时把路径钉死交给 `snapshot::prepare`。
|
||||
// D13 选 Java(托管命中就把路径钉死交给 `snapshot::prepare`);`input.java` 下面会被换成
|
||||
// 「生效路径」,先记下「用户有没有自己指定 Java」。D4:不符且没人显式指定就拦,不 spawn。
|
||||
unlucky_core::launch::launcher_log::set_dir(ctx.logs_dir());
|
||||
let explicit_java = input.java.is_some();
|
||||
let java = super::java::resolve_for_launch(ctx, &input.version_id, input.java.as_deref());
|
||||
if !args.dry_run {
|
||||
if let Some(mismatch) = select::decide(java.mismatch, explicit_java).block() {
|
||||
unlucky_core::launch::launcher_log::record("launch", &mismatch.message());
|
||||
return Err(
|
||||
CliError::user("java_mismatch", mismatch.message()).with_hint(mismatch.advice())
|
||||
);
|
||||
}
|
||||
}
|
||||
let input = PlanInput {
|
||||
java: java.override_path.clone(),
|
||||
..input
|
||||
@@ -107,7 +119,6 @@ pub fn run(ctx: &Ctx, args: &LaunchArgs) -> Result<Report, CliError> {
|
||||
|
||||
// M188/M211:命令行这一路也把启动器日志写进 `<data>/logs/launcher.log`
|
||||
// (落点与格式的真相都在 core 的 `launcher_log`,GUI 写的是同一份)。
|
||||
unlucky_core::launch::launcher_log::set_dir(ctx.logs_dir());
|
||||
unlucky_core::launch::launcher_log::record_start(
|
||||
&input.instance_id,
|
||||
&isolation.game_dir,
|
||||
@@ -118,9 +129,14 @@ pub fn run(ctx: &Ctx, args: &LaunchArgs) -> Result<Report, CliError> {
|
||||
);
|
||||
let plan = snapshot::prepare(ctx, &input)?;
|
||||
let config = LauncherConfig::new(ctx.sessions_root())
|
||||
.with_launcher(super::meta::LAUNCHER_NAME, env!("CARGO_PKG_VERSION"));
|
||||
.with_launcher(super::meta::LAUNCHER_NAME, env!("CARGO_PKG_VERSION"))
|
||||
.with_watchdog_env();
|
||||
// D6:两个看门狗时长的**生效值**落 launcher.log —— 卡死之后第一个要看的就是「等了多久才放弃」。
|
||||
let watchdog = config.watchdog_summary();
|
||||
unlucky_core::launch::launcher_log::record("launch", &watchdog);
|
||||
let launcher = Launcher::new(config);
|
||||
let mut human = Vec::new();
|
||||
human.push(watchdog);
|
||||
human.push(format!(
|
||||
"隔离档位 {}(来源 {})· 运行目录 {}",
|
||||
isolation.isolation.as_str(),
|
||||
@@ -158,18 +174,7 @@ pub fn run(ctx: &Ctx, args: &LaunchArgs) -> Result<Report, CliError> {
|
||||
lines.push(format!("告警: {warning}"));
|
||||
}
|
||||
human.extend(lines);
|
||||
let natives = dry.natives.as_ref().map(|natives| {
|
||||
json!({
|
||||
"dir": natives.dir.display().to_string(),
|
||||
"files": natives.files,
|
||||
"skipped": natives.skipped,
|
||||
"ignored": natives.ignored,
|
||||
"jars": natives.jars,
|
||||
"custom": natives.custom,
|
||||
"up_to_date": natives.up_to_date,
|
||||
"era": natives.era.as_str(),
|
||||
})
|
||||
});
|
||||
let natives = dry.natives.as_ref().map(|natives| natives.to_json());
|
||||
let data = json!({
|
||||
"dry_run": true,
|
||||
"instance_found": instance_found,
|
||||
@@ -336,6 +341,11 @@ fn global_mode(ctx: &Ctx) -> IsolationMode {
|
||||
}
|
||||
|
||||
fn map_launch_error(error: &LaunchError, ctx: &Ctx) -> CliError {
|
||||
// D6:看门狗超时是一个**可区分的**结局(脚本要能单独处理),且自带现场与可调项,
|
||||
// 不该被塞上「版本本体目录」这种无关提示。
|
||||
if let LaunchError::Watchdog { .. } = error {
|
||||
return CliError::user("launch_timeout", error.to_string());
|
||||
}
|
||||
let hint = format!("版本本体目录:{}", ctx.game().versions_dir().display());
|
||||
CliError::user("launch", error.to_string()).with_hint(hint)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user