Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s

Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
AskaEth committed 2026-10-31 21:54:10 +08:00
commit bc41dfcf7d
517 files changed
+92844

No files matched your search

+222
View File
@@ -0,0 +1,222 @@
//! 进程管理:spawn + stdout/stderr 泵 + 进程树回收。
//!
//! 对应 `analyse/02` §6 的四条线程模型(`stdout-pump` / `stderr-pump` / `exit-waiter`),
//! 但线程生命周期的收尾由 [`crate::launch::watchdog`] 兜底,不无条件 `join`。
//!
//! 本模块只管“把命令行跑起来并收集输出”,不做组件检查、不做诊断——
//! 那些是 `launch` 上层流水线(阶段B)与 M15 的职责。
use std::path::{Path, PathBuf};
use std::process::{Child, Command, ExitStatus, Stdio};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::launch::args_table::AssembledCommand;
use crate::launch::error::{LaunchError, Result};
use crate::launch::pump::{LineSink, LogLine, PumpGroup, RingBuffer, RingBufferConfig};
use crate::launch::tree_kill;
use crate::launch::watchdog::{self, PumpVerdict, WatchdogConfig};
/// 强制结束后等进程消失的预算。
const FORCE_KILL_BUDGET: Duration = Duration::from_secs(5);
/// 轮询进程状态的间隔。
const POLL_INTERVAL: Duration = Duration::from_millis(20);
/// 进程管理配置。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProcessConfig {
/// 日志泵等待预算。
pub watchdog: WatchdogConfig,
/// 停止时温和关闭的宽限期。
pub graceful_stop: Duration,
/// 环形缓冲配置。
pub buffer: RingBufferConfig,
}
impl Default for ProcessConfig {
fn default() -> Self {
Self {
watchdog: WatchdogConfig::default(),
graceful_stop: Duration::from_secs(3),
buffer: RingBufferConfig::default(),
}
}
}
/// 已启动的游戏进程。
#[derive(Debug)]
pub struct RunningProcess {
child: Child,
pump: PumpGroup,
buffer: Arc<Mutex<RingBuffer>>,
started_at: Instant,
pid: u32,
config: ProcessConfig,
}
impl RunningProcess {
/// 启动进程并接管两条输出流。
pub fn spawn(
command: &AssembledCommand,
working_dir: Option<&Path>,
sink: Arc<dyn LineSink>,
config: ProcessConfig,
) -> Result<Self> {
let mut process = Command::new(&command.program);
process.args(&command.args);
for (key, value) in &command.env {
process.env(key, value);
}
if let Some(dir) = working_dir {
process.current_dir(dir);
}
process
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
/// 独立进程组:便于 `taskkill /T` 递归回收。
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
process.creation_flags(CREATE_NEW_PROCESS_GROUP);
}
let mut child = process.spawn().map_err(|source| LaunchError::Spawn {
program: command.program.clone(),
source,
})?;
let pid = child.id();
let buffer = Arc::new(Mutex::new(RingBuffer::new(config.buffer)));
let stdout = child.stdout.take();
let stderr = child.stderr.take();
let pump = PumpGroup::spawn(stdout, stderr, buffer.clone(), sink);
Ok(Self {
child,
pump,
buffer,
started_at: Instant::now(),
pid,
config,
})
}
/// 进程号。
pub fn pid(&self) -> u32 {
self.pid
}
/// 已运行毫秒数。
pub fn uptime_ms(&self) -> u64 {
u64::try_from(self.started_at.elapsed().as_millis()).unwrap_or(u64::MAX)
}
/// 启动时刻(窗口检测计时用)。
pub fn started_at(&self) -> Instant {
self.started_at
}
/// 共享的日志环形缓冲。
pub fn buffer(&self) -> Arc<Mutex<RingBuffer>> {
self.buffer.clone()
}
/// 当前可读日志快照。
pub fn lines(&self) -> Vec<LogLine> {
match self.buffer.lock() {
Ok(guard) => guard.lines().cloned().collect(),
Err(_) => Vec::new(),
}
}
/// 等待进程退出(不等待日志泵)。
pub fn wait(&mut self) -> Result<ExitStatus> {
self.child.wait().map_err(|source| LaunchError::Io {
op: "wait_child",
path: PathBuf::from(self.pid.to_string()),
source,
})
}
/// 进程退出后,按预算收尾日志泵。
pub fn supervise(&mut self) -> PumpVerdict {
watchdog::wait_for_pumps(&mut self.pump, &self.config.watchdog)
}
/// 进程是否仍在运行。
pub fn is_running(&mut self) -> bool {
matches!(self.child.try_wait(), Ok(None))
}
/// 三段式停止整棵进程树(温和 → 宽限 → 强制)。
///
/// 判定标准是**进程是否真的没了**,不是 `taskkill`/`kill` 的退出码:
/// 外部命令对“已经退出的进程”会返回非 0(Windows 上 `taskkill` 报 128),
/// 把它当失败会误报——尤其是温和阶段已经把进程关掉的情况。
pub fn stop(&mut self) -> Result<()> {
if !self.is_running() {
return Ok(());
}
let _ = tree_kill::terminate(self.pid);
if self.wait_until_exit(self.config.graceful_stop) {
return Ok(());
}
let _ = tree_kill::force_kill(self.pid);
if self.wait_until_exit(FORCE_KILL_BUDGET) {
return Ok(());
}
Err(LaunchError::Kill {
pid: self.pid,
source: std::io::Error::other("强制结束后进程仍在运行"),
})
}
/// 直接强杀(看门狗/崩溃兜底路径)。
pub fn force_kill(&mut self) -> Result<()> {
if !self.is_running() {
return Ok(());
}
let _ = tree_kill::force_kill(self.pid);
if self.wait_until_exit(FORCE_KILL_BUDGET) {
Ok(())
} else {
Err(LaunchError::Kill {
pid: self.pid,
source: std::io::Error::other("强制结束后进程仍在运行"),
})
}
}
fn wait_until_exit(&mut self, budget: Duration) -> bool {
let deadline = Instant::now() + budget;
loop {
match self.child.try_wait() {
Ok(Some(_)) => return true,
Ok(None) => {}
Err(_) => return false,
}
if Instant::now() >= deadline {
return false;
}
std::thread::sleep(POLL_INTERVAL);
}
}
}
/// 从退出状态取整数退出码。
///
/// POSIX 上被子进程信号杀死没有 `code()`,按 shell 惯例补 `128 + signal`
/// (`SIGKILL` → 137、`SIGTERM` → 143,与 `analyse/02` 附录 C 一致)。
pub fn exit_code(status: &ExitStatus) -> i32 {
if let Some(code) = status.code() {
return code;
}
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
return 128 + signal;
}
}
-1
}