feat: 远程分片加绑定门与认证门,worker 无特权口径落文档

This commit is contained in:
2026-10-11 18:31:18 +00:00
parent 8426d00133
commit 4fb66bf2e3
8 changed files with 551 additions and 131 deletions
+88 -11
View File
@@ -1,11 +1,17 @@
//! Worker 模式:远程分片的执行半区。连监督者端点收窗口指派帧,in-process
//! 执行测试,发心跳/失败/现场/DONE 帧。
//!
//! 认证门:连接后首帧发 `Auth` 事件帧(token 经 `OXIDE_TEST262_WORKER_TOKEN`
//! 带外注入),读回 `AuthOk` 命令帧才进主循环;缺失 token、读超时、首帧非
//! `AuthOk` 均报错退出。握手必须在看门狗线程启动前完成,否则看门狗会把
//! `AuthOk` 帧当命令帧消费。
//!
//! 看门狗线程独立读控制帧(kill → 置标志),主线程在测试间隙检查标志后从
//! 主线程上下文发现场帧再退出。`KernelCore` 是 `!Send`,看门狗不得触引擎;
//! 现场帧内容来自主线程上下文(last-pc 采样行)。断连按死亡处理(恢复由
//! 监督者侧同构完成)。
use std::io::BufReader;
use std::net::TcpStream;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
@@ -15,7 +21,7 @@ use crate::config::RunConfig;
use crate::harness::{HarnessModuleCache, HarnessPrefixCache, HarnessSources, HARNESS};
use crate::judge::{categorize_fail, TestOutcome};
use crate::runner::{build_runner_engine, process_path, CURRENT_TEST_PATH};
use crate::shard_protocol::{encode_event, parse_command, FrameReader, ShardCommand, ShardEvent};
use crate::shard_protocol::{encode_event, parse_command, FrameReader, ShardCommand, ShardEvent, AUTH_READ_TIMEOUT};
use crate::stats::RunStats;
use oxide_net::Conn;
@@ -34,11 +40,12 @@ enum ChannelMsg {
Kill,
}
/// Worker 模式入口:连监督者端点,收窗口指派帧,in-process 执行,发帧。
/// Worker 模式入口:连监督者端点,过认证门,收窗口指派帧,in-process 执行,发帧。
///
/// 端点经 `OXIDE_TEST262_WORKER_ENDPOINT` 环境变量传入(host:port 形态)。
/// 看门狗线程独立读控制帧(kill → 置标志),主线程在测试间隙检查标志后
/// 发现场帧再退出。断连按死亡处理(恢复由监督者侧完成)。
/// 端点经 `OXIDE_TEST262_WORKER_ENDPOINT` 环境变量传入(host:port 形态),
/// token 经 `OXIDE_TEST262_WORKER_TOKEN` 带外注入(监督者前台打印,不经网络
/// 分发)。看门狗线程独立读控制帧(kill → 置标志),主线程在测试间隙检查
/// 标志后发现场帧再退出。断连按死亡处理(恢复由监督者侧完成)。
pub(crate) fn run_worker(config: &RunConfig) -> bool {
let endpoint = match std::env::var("OXIDE_TEST262_WORKER_ENDPOINT") {
Ok(ep) => ep,
@@ -47,6 +54,16 @@ pub(crate) fn run_worker(config: &RunConfig) -> bool {
return false;
}
};
// token 带外注入:缺失或为空在连接前报错退出,不占连接。
let token = match std::env::var("OXIDE_TEST262_WORKER_TOKEN") {
Ok(t) if !t.is_empty() => t,
_ => {
eprintln!(
"worker mode requires OXIDE_TEST262_WORKER_TOKEN (out-of-band from the supervisor's foreground print)"
);
return false;
}
};
let stream = match TcpStream::connect(&endpoint) {
Ok(s) => s,
@@ -57,17 +74,58 @@ pub(crate) fn run_worker(config: &RunConfig) -> bool {
};
let mut conn = Conn::Tcp(stream);
// 看门狗线程:独立读控制帧。指派帧经通道转主线程(主线程持引擎,
// 看门狗不得触引擎);kill 帧置标志并经通道发 kill 信号(主线程阻塞
// 在通道时也能收到,不依赖标志轮询)。
// 认证门:读超时 30 秒(半开连接不永久占进程),发 Auth 帧后读回首帧,
// 必须是 AuthOk,否则报错退出。握手必须在看门狗线程启动前完成:看门狗
// 独立读命令帧,提前启动会消费 AuthOk 帧。
if conn.set_read_timeout(Some(AUTH_READ_TIMEOUT)).is_err() {
eprintln!("worker: failed to set read timeout");
return false;
}
let auth_frame = encode_event(&ShardEvent::Auth { token });
if conn.write_all(auth_frame.as_bytes()).is_err() {
eprintln!("worker: failed to send auth frame");
return false;
}
let mut reader = FrameReader::new(BufReader::new(conn));
let first = match reader.read_frame() {
Ok(Some(frame)) => frame,
Ok(None) => {
eprintln!("worker: supervisor closed the connection before auth ok");
return false;
}
Err(e) => {
eprintln!("worker: failed to read auth ok frame: {e}");
return false;
}
};
if !matches!(parse_command(&first), Ok(ShardCommand::AuthOk)) {
eprintln!("worker: supervisor did not confirm auth (first frame is not authok)");
return false;
}
// 握手完成:清读超时(看门狗阻塞读不得超时),克隆写句柄供主线程发
// 事件帧,读句柄交给看门狗。
if reader.get_mut().get_mut().set_read_timeout(None).is_err() {
eprintln!("worker: failed to clear read timeout");
return false;
}
let writer = match reader.get_mut().get_mut().try_clone() {
Ok(w) => w,
Err(e) => {
eprintln!("worker: failed to clone conn for writer: {e}");
return false;
}
};
let mut conn = writer;
// 看门狗线程:独立读控制帧(读句柄移入)。指派帧经通道转主线程
// (主线程持引擎,看门狗不得触引擎);kill 帧置标志并经通道发 kill
// 信号(主线程阻塞在通道时也能收到,不依赖标志轮询)。
let kill_flag = Arc::new(AtomicBool::new(false));
let (msg_tx, msg_rx) = mpsc::channel::<ChannelMsg>();
let watchdog_kill = Arc::clone(&kill_flag);
let watchdog_tx = msg_tx;
let watchdog_conn = conn.try_clone().expect("failed to clone conn for watchdog");
let watchdog_buf = std::io::BufReader::new(watchdog_conn);
std::thread::spawn(move || {
let mut reader = FrameReader::new(watchdog_buf);
let mut reader = reader;
loop {
match reader.read_frame() {
Ok(Some(frame)) => match parse_command(&frame) {
@@ -79,6 +137,11 @@ pub(crate) fn run_worker(config: &RunConfig) -> bool {
}) => {
let _ = watchdog_tx.send(ChannelMsg::Assign(AssignWindow { wstart, paths }));
}
Ok(ShardCommand::AuthOk) => {
// AuthOk 只出现一次且已被主线程握手消费;重复到达
// 忽略(协议上监督者不会重发)。
continue;
}
Ok(ShardCommand::Kill) => {
watchdog_kill.store(true, Ordering::SeqCst);
let _ = watchdog_tx.send(ChannelMsg::Kill);
@@ -235,3 +298,17 @@ fn send_scene_frame(conn: &mut Conn) {
eprintln!("worker: failed to send scene frame: {e}");
}
}
#[cfg(test)]
mod tests {
use super::*;
/// token 缺失时 worker 在连接前报错退出(不占连接)。
#[test]
fn worker_missing_token_exits_before_connect() {
std::env::set_var("OXIDE_TEST262_WORKER_ENDPOINT", "127.0.0.1:1");
std::env::remove_var("OXIDE_TEST262_WORKER_TOKEN");
let config = RunConfig::new();
assert!(!run_worker(&config), "token 缺失应报错退出");
}
}