feat: server 执行请求路径,六段纯函数加每请求步数覆盖

This commit is contained in:
2026-10-06 15:02:47 +00:00
parent 196bb77516
commit 635c8144f6
10 changed files with 314 additions and 47 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.14.5", "hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.8.27" version = "0.8.28"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.8.27" version = "0.8.28"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+181
View File
@@ -0,0 +1,181 @@
//! 执行请求路径:parse → compile → spawn → run → format → drop。
//!
//! `handle_eval` 是纯函数,参数全注入、无全局状态;每请求用单一
//! `catch_unwind` 包裹整条路径,panic 载荷转成错误帧,不 panic、不退出。
//!
//! # release 边界
//! 根 `Cargo.toml` 的 `[profile.release]` 为 `panic=abort`:release 下
//! `catch_unwind` 是空操作、panic 直接终止进程,watchdog 是 release 崩溃后
//! 恢复的唯一自动手段;debug 下本路径完整生效,是日常验证环境。
use std::sync::Arc;
use oxide_compiler::compiler::{compiled_module_hash, Compiler};
use oxide_kernel::kernel::KernelCore;
use oxide_parser::Allocator;
use oxide_vm::vm_pool::VmPool;
use super::protocol::ServerResponse;
/// 执行请求处理:解析 → 编译 → 取 VM → 执行 → 渲染完成值 → 归还池的六段路径。
///
/// # 步骤
/// 1. parse:局部分配器加 `oxide_parser::parse`,失败时各条错误换行拼接成错误帧。
/// 2. compile:`Compiler` 加 `CodeForge` 结构哈希缓存,失败以原文成错误帧。
/// 3. spawn:从池取 `VmGuard`(闭包内创建,panic 时随展开被 drop)。
/// 4. run:设每请求步数覆盖(含 None)后 `run`,`Err` 即错误帧。
/// 5. format:成功值经 `crate::format_js_value` 渲染成完成值文本,`eval_ok` 封装。
/// 6. drop:guard 在闭包出口 drop,干净 VM 经 `full_reset` 回池复用、被污染 VM 丢弃并新建替补。
///
/// # 边界与前提
/// - guard 在 `catch_unwind` 闭包内创建:panic 时 guard 随展开在闭包边界被 drop,
/// `VmGuard::drop` 的 `thread::panicking()` 检查此刻为真,VM 按 dirty 丢弃。
///
/// # 副作用
/// - 无(纯函数,参数全注入)。
///
/// # 注意事项
/// - release 下 `panic=abort` 使 `catch_unwind` 空操作,watchdog 是崩溃后恢复的唯一自动手段。
pub fn handle_eval(code: &str, max_steps: Option<u64>, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>) -> ServerResponse {
let response = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
// parse:局部分配器,解析失败各条换行拼接成错误帧。
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, code) {
Ok(program) => program,
Err(errors) => {
let message = errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join("\n");
return ServerResponse::eval_err(message);
}
};
// compile:结构哈希缓存,失败以原文成错误帧。
let compiler = Compiler::new();
let hash = compiled_module_hash(&program);
let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) {
Ok(module) => module,
Err(message) => return ServerResponse::eval_err(message),
};
// spawn:从池取 VM;guard 在闭包内创建,panic 时随展开被 drop。
let mut guard = pool.spawn();
// run:设每请求步数覆盖(含 None)后执行。
guard.vm_mut().set_max_steps(max_steps);
let result = guard.vm_mut().run(&module);
// format:渲染完成值,`Err` 即错误帧。
match result {
Ok(value) => {
let text = crate::format_js_value(
guard.vm(),
kernel.perm_interner().as_ref(),
kernel.shape_forge().as_ref(),
value,
);
ServerResponse::eval_ok(text)
}
Err(message) => ServerResponse::eval_err(message),
}
// drop:guard 在闭包出口 drop,干净 VM 经 full_reset 回池复用、dirty 丢弃。
}));
match response {
Ok(resp) => resp,
Err(payload) => ServerResponse::eval_err(format!("internal panic: {}", panic_payload_str(&payload))),
}
}
/// 从 `catch_unwind` 的 panic payload 提取可读文本:`&str` 与 `String` 两种
/// 常见形态,其余返回占位文本。
fn panic_payload_str(payload: &Box<dyn std::any::Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
return (*s).to_string();
}
if let Some(s) = payload.downcast_ref::<String>() {
return s.clone();
}
"non-string payload".into()
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_kernel::kernel::KernelConfig;
/// 测试环境:minimal 内核加单 VM 池(与池既有测试同口径)。
fn test_env() -> (Arc<KernelCore>, Arc<VmPool>) {
let kernel = KernelCore::new(KernelConfig::minimal());
let pool = VmPool::new(Arc::clone(&kernel), 1, Some(2));
(kernel, pool)
}
/// 成功渲染:"1 + 1" 得完成值 "2",无错误。
#[test]
fn eval_success_renders_value() {
let (kernel, pool) = test_env();
let response = handle_eval("1 + 1", None, &kernel, &pool);
match response {
ServerResponse::EvalResult { ref value, ref error } => {
assert_eq!(value.as_deref(), Some("2"), "完成值应为 2:{response:?}");
assert!(error.is_none(), "不应有错误:{response:?}");
}
other => panic!("应得 EvalResult 帧,实得 {other:?}"),
}
}
/// 解析错误帧:"function(" 得非空错误,无完成值。
#[test]
fn eval_parse_error_frame() {
let (kernel, pool) = test_env();
let response = handle_eval("function(", None, &kernel, &pool);
match response {
ServerResponse::EvalResult { ref value, ref error } => {
assert!(value.is_none(), "不应有完成值:{response:?}");
assert!(error.as_deref().map(|e| !e.is_empty()).unwrap_or(false), "错误应非空:{response:?}");
}
other => panic!("应得 EvalResult 帧,实得 {other:?}"),
}
}
/// 运行时错误帧:throw 得含 uncaught 与 boom 的错误。
#[test]
fn eval_runtime_error_frame() {
let (kernel, pool) = test_env();
let response = handle_eval("throw new Error('boom')", None, &kernel, &pool);
match response {
ServerResponse::EvalResult { ref value, ref error } => {
assert!(value.is_none(), "不应有完成值:{response:?}");
let err = error.as_deref().expect("应有错误");
assert!(err.contains("uncaught"), "错误应含 uncaught:{err}");
assert!(err.contains("boom"), "错误应含 boom:{err}");
}
other => panic!("应得 EvalResult 帧,实得 {other:?}"),
}
}
/// 步数超限帧:for(;;){} 加 Some(1000) 得含 step limit 的错误。
#[test]
fn eval_step_limit_frame() {
let (kernel, pool) = test_env();
let response = handle_eval("for(;;){}", Some(1000), &kernel, &pool);
match response {
ServerResponse::EvalResult { ref value, ref error } => {
assert!(value.is_none(), "不应有完成值:{response:?}");
let err = error.as_deref().expect("应有错误");
assert!(err.contains("step limit"), "错误应含 step limit:{err}");
}
other => panic!("应得 EvalResult 帧,实得 {other:?}"),
}
}
/// 池复用:连续两次成功 eval 后池可用数不变。
#[test]
fn eval_pool_reuse() {
let (kernel, pool) = test_env();
let before = pool.available_count();
handle_eval("1 + 1", None, &kernel, &pool);
handle_eval("2 + 2", None, &kernel, &pool);
let after = pool.available_count();
assert_eq!(before, after, "池可用数应不变:before={before} after={after}");
}
}
+7 -4
View File
@@ -1,11 +1,14 @@
//! 持久 server 子系统:协议数据面、身份注册与 server 进程主体。 //! 持久 server 子系统:协议数据面、身份注册、执行路径与 server 进程主体。
//! //!
//! 持久 server 是常驻进程,复用预热池、近零 spawn 成本; //! 持久 server 是常驻进程,复用预热池、近零 spawn 成本;
//! 通信走 Unix socket + NDJSON(换行分隔 JSON)。 //! 通信走 Unix socket + NDJSON(换行分隔 JSON)。
//! 模块分三层:协议数据面(帧格式与 serde 结构体)、身份注册(sidecar 文件 //! 模块分四层:协议数据面(帧格式与 serde 结构体)、身份注册(sidecar 文件
//! 与 liveness 检查原语)、server 进程主体(accept 循环、控制请求直接处理、 //! 与 liveness 检查原语)、执行请求路径(parse → compile → spawn → run →
//! 优雅退出)。执行路径与并发模型在后续任务。 //! format → drop 六段纯函数)、server 进程主体(accept 循环、控制请求直接
//! 处理、优雅退出)。并发模型在后续任务。
/// 执行请求路径:parse → compile → spawn → run → format → drop 六段纯函数。
pub mod eval;
/// 协议数据面:NDJSON 帧格式与 serde 结构体。 /// 协议数据面:NDJSON 帧格式与 serde 结构体。
pub mod protocol; pub mod protocol;
/// server 进程主体:accept 循环、控制请求分派与优雅退出。 /// server 进程主体:accept 循环、控制请求分派与优雅退出。
+25 -22
View File
@@ -22,8 +22,9 @@ use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use oxide_kernel::kernel::{KernelConfig, KernelCore}; use oxide_kernel::kernel::{KernelConfig, KernelCore};
use oxide_vm::vm_pool::{PoolCounters, VmPool}; use oxide_vm::vm_pool::VmPool;
use super::eval;
use super::protocol::{self, FrameReader, ProtocolError, ServerRequest, ServerResponse}; use super::protocol::{self, FrameReader, ProtocolError, ServerRequest, ServerResponse};
use super::sidecar::{self, ClaimResult}; use super::sidecar::{self, ClaimResult};
@@ -72,12 +73,12 @@ impl ServerConfig {
} }
} }
/// server 运行上下文:池状态、启动时刻、关闭标志、在途计数。 /// server 运行上下文:共享内核、启动时刻、关闭标志、在途计数。
/// ///
/// 池本体不进上下文(Vm 不跨线程),只持池的原子状态句柄; /// 内核跨线程共享(`Arc<KernelCore>` 是 Send/Sync);池本体不进上下文
/// 内核不跨线程共享(执行路径由后续任务接线)。 /// (Vm 不跨线程),每连接线程自建池(见 `handle_connection`)。
struct ServerContext { struct ServerContext {
pool_counters: Arc<PoolCounters>, kernel: Arc<KernelCore>,
started_at: Instant, started_at: Instant,
shutdown: Arc<AtomicBool>, shutdown: Arc<AtomicBool>,
in_flight: Arc<AtomicUsize>, in_flight: Arc<AtomicUsize>,
@@ -114,12 +115,11 @@ pub fn run_server(config: &ServerConfig) -> Result<(), ServerError> {
// 绑定监听器:stale socket 文件删一次重试,失败回滚 sidecar。 // 绑定监听器:stale socket 文件删一次重试,失败回滚 sidecar。
let listener = bind_listener(config)?; let listener = bind_listener(config)?;
// 建内核与池:标准配置,池按 min_pool_size 预热。 // 建内核(标准配置);池每连接线程自建(Vm 不跨线程,池本体不共享)。
let kernel = KernelCore::new(KernelConfig::standard()); let kernel = KernelCore::new(KernelConfig::standard());
let pool = VmPool::new(Arc::clone(&kernel), kernel.config.min_pool_size, kernel.config.max_pool_size);
let ctx = Arc::new(ServerContext { let ctx = Arc::new(ServerContext {
pool_counters: pool.counters(), kernel: Arc::clone(&kernel),
started_at: Instant::now(), started_at: Instant::now(),
shutdown: Arc::new(AtomicBool::new(false)), shutdown: Arc::new(AtomicBool::new(false)),
in_flight: Arc::new(AtomicUsize::new(0)), in_flight: Arc::new(AtomicUsize::new(0)),
@@ -132,9 +132,8 @@ pub fn run_server(config: &ServerConfig) -> Result<(), ServerError> {
// 在途归零:轮询至零,5 秒安全超时后记警告继续。 // 在途归零:轮询至零,5 秒安全超时后记警告继续。
wait_in_flight_drained(&ctx.in_flight, Duration::from_secs(5)); wait_in_flight_drained(&ctx.in_flight, Duration::from_secs(5));
// 按序释放:上下文(持池状态句柄)→ 池 → 内核。 // 按序释放:上下文(持内核句柄)→ 内核。
drop(ctx); drop(ctx);
drop(pool);
drop(kernel); drop(kernel);
// 删 sidecar 与 socket 文件(尽力而为,文件已不存在不视为错误)。 // 删 sidecar 与 socket 文件(尽力而为,文件已不存在不视为错误)。
@@ -232,12 +231,15 @@ fn handle_connection(stream: UnixStream, ctx: &ServerContext) {
// 30 秒读超时:半开连接不永久占住线程。 // 30 秒读超时:半开连接不永久占住线程。
let _ = stream.set_read_timeout(Some(Duration::from_secs(30))); let _ = stream.set_read_timeout(Some(Duration::from_secs(30)));
// 每连接自建池(Vm 不跨线程,池是每线程资产),按内核配置预热。
let pool = VmPool::new(Arc::clone(&ctx.kernel), ctx.kernel.config.min_pool_size, ctx.kernel.config.max_pool_size);
let mut reader = FrameReader::new(io::BufReader::new(stream)); let mut reader = FrameReader::new(io::BufReader::new(stream));
// 读帧:EOF、读超时、非法 UTF-8 均退出。 // 读帧:EOF、读超时、非法 UTF-8 均退出。
while let Ok(Some(frame)) = reader.read_frame() { while let Ok(Some(frame)) = reader.read_frame() {
// 分派:帧长超限按协议约定关连接;畸形帧以 Error 帧答复后继续。 // 分派:帧长超限按协议约定关连接;畸形帧以 Error 帧答复后继续。
let response = match protocol::parse_request(&frame) { let response = match protocol::parse_request(&frame) {
Ok(request) => dispatch_control(&request, ctx), Ok(request) => dispatch_control(&request, ctx, &pool),
Err(ProtocolError::FrameTooLarge) => break, Err(ProtocolError::FrameTooLarge) => break,
Err(error) => error.to_error_frame(), Err(error) => error.to_error_frame(),
}; };
@@ -255,17 +257,17 @@ fn handle_connection(stream: UnixStream, ctx: &ServerContext) {
} }
} }
/// 控制请求分派:五类控制请求直接应答,不 spawn 虚拟机。 /// 控制请求分派:五类控制请求直接应答;执行请求走 `handle_eval`。
/// ///
/// 执行请求暂以协议错误帧答复(执行路径由后续任务补齐)。 /// 池是每连接资产(`handle_connection` 创建),Status 响应读本连接池状态。
fn dispatch_control(request: &ServerRequest, ctx: &ServerContext) -> ServerResponse { fn dispatch_control(request: &ServerRequest, ctx: &ServerContext, pool: &Arc<VmPool>) -> ServerResponse {
match request { match request {
ServerRequest::Version => ServerResponse::Version { ServerRequest::Version => ServerResponse::Version {
version: env!("CARGO_PKG_VERSION").to_string(), version: env!("CARGO_PKG_VERSION").to_string(),
}, },
ServerRequest::Status => ServerResponse::Status { ServerRequest::Status => ServerResponse::Status {
pool_available: ctx.pool_counters.available(), pool_available: pool.available_count(),
pool_total: ctx.pool_counters.total(), pool_total: pool.total_count(),
uptime_ms: ctx.started_at.elapsed().as_millis() as u64, uptime_ms: ctx.started_at.elapsed().as_millis() as u64,
}, },
ServerRequest::Health => ServerResponse::Health { healthy: true }, ServerRequest::Health => ServerResponse::Health { healthy: true },
@@ -279,9 +281,7 @@ fn dispatch_control(request: &ServerRequest, ctx: &ServerContext) -> ServerRespo
ctx.shutdown.store(true, Ordering::SeqCst); ctx.shutdown.store(true, Ordering::SeqCst);
ServerResponse::Shutdown ServerResponse::Shutdown
} }
ServerRequest::Eval { .. } => ServerResponse::Error { ServerRequest::Eval { code, max_steps } => eval::handle_eval(code, *max_steps, &ctx.kernel, pool),
message: "执行路径尚未实现".to_string(),
},
} }
} }
@@ -449,9 +449,9 @@ mod tests {
stop_server(&mut stream, handle); stop_server(&mut stream, handle);
} }
/// 执行请求得 Error 帧(尚未实现),不 panic。 /// 执行请求得 EvalResult 帧:"1 + 1" 得完成值 "2",不 panic。
#[test] #[test]
fn eval_not_implemented() { fn eval_request_returns_value() {
let config = unique_paths("eval"); let config = unique_paths("eval");
let handle = start_server(config.clone()); let handle = start_server(config.clone());
let mut stream = connect(&config); let mut stream = connect(&config);
@@ -462,7 +462,10 @@ mod tests {
max_steps: None, max_steps: None,
}, },
); );
assert!(matches!(response, ServerResponse::Error { .. }), "应得 Error 帧:{response:?}"); assert!(
matches!(response, ServerResponse::EvalResult { ref value, .. } if value.as_deref() == Some("2")),
"应得完成值为 2 的 EvalResult 帧:{response:?}"
);
stop_server(&mut stream, handle); stop_server(&mut stream, handle);
} }
+3 -1
View File
@@ -92,7 +92,9 @@ impl Vm {
pub(crate) fn dispatch(&mut self) -> Result<JsValue, String> { pub(crate) fn dispatch(&mut self) -> Result<JsValue, String> {
// config.max_steps / max_alloc_bytes 逐指令只读且循环内不变:提到循环外, // config.max_steps / max_alloc_bytes 逐指令只读且循环内不变:提到循环外,
// 免每次经 kernel_core Arc 指针追寻读取(热点内仅有的 config 访问)。 // 免每次经 kernel_core Arc 指针追寻读取(热点内仅有的 config 访问)。
let max_steps = self.kernel_core.config.max_steps; // 步数上限取每请求覆盖值.or(内核配置):覆盖优先、None 回退内核配置,
// 未设覆盖的宿主(CLI/REPL/bench/test262)读到的值与改动前逐位一致。
let max_steps = self.max_steps_override.or(self.kernel_core.config.max_steps);
let max_alloc_bytes = self.kernel_core.config.max_alloc_bytes; let max_alloc_bytes = self.kernel_core.config.max_alloc_bytes;
let mut steps: u64 = 0; let mut steps: u64 = 0;
// 小重入泵送盲区:native 终端循环泵送短 JS 重入时,顶层 steps 不推进、 // 小重入泵送盲区:native 终端循环泵送短 JS 重入时,顶层 steps 不推进、
+11
View File
@@ -391,6 +391,10 @@ pub struct Vm {
/// `pc/opcode/flat_id/frames`,供监督者超时/崩溃杀子进程后读回挂死点; /// `pc/opcode/flat_id/frames`,供监督者超时/崩溃杀子进程后读回挂死点;
/// 默认 None(零输出零分配)。 /// 默认 None(零输出零分配)。
pub(crate) pc_watch: Option<PathBuf>, pub(crate) pc_watch: Option<PathBuf>,
/// 每请求步数上限覆盖:执行路径按请求设置,None 回退内核配置。
/// 池回收的 `full_reset` 路径清位;`run()` 入口的 `clear_execution_state`
/// 刻意不清(清在那会把执行路径刚设的覆盖抹掉)。
pub(crate) max_steps_override: Option<u64>,
} }
impl Drop for Vm { impl Drop for Vm {
@@ -467,6 +471,13 @@ impl Vm {
self.pc_watch = path; self.pc_watch = path;
} }
/// 设置每请求步数上限覆盖:dispatch 主循环取 `覆盖值.or(内核配置)`,
/// 覆盖优先、None 回退内核配置。执行路径按请求必设(含 None),池回收的
/// `full_reset` 路径清位。
pub fn set_max_steps(&mut self, limit: Option<u64>) {
self.max_steps_override = limit;
}
/// 设置指令周期采样周期(2 的幂,0 关闭)。开启后 dispatch 主循环每 /// 设置指令周期采样周期(2 的幂,0 关闭)。开启后 dispatch 主循环每
/// `period` 条指令记一条样本(flat_id、pc、opcode、frames 深度),run 末 /// `period` 条指令记一条样本(flat_id、pc、opcode、frames 深度),run 末
/// 按 flat_id 聚合并向 stderr 输出 top-K 直方图;关闭时热路径仅一次 /// 按 flat_id 聚合并向 stderr 输出 top-K 直方图;关闭时热路径仅一次
+9 -1
View File
@@ -179,6 +179,12 @@ impl VmGuard {
pub fn vm_mut(&mut self) -> &mut Vm { pub fn vm_mut(&mut self) -> &mut Vm {
self.vm.as_mut().expect("VmGuard has no VM") self.vm.as_mut().expect("VmGuard has no VM")
} }
/// 显式标记被借出的 VM 为 dirty:`Drop` 时丢弃该 VM 并新建替补,
/// 不对其执行 `full_reset`。供并发与未来显式弃用场景使用。
pub fn mark_dirty(&mut self) {
self.dirty = true;
}
} }
impl Drop for VmGuard { impl Drop for VmGuard {
@@ -189,7 +195,9 @@ impl Drop for VmGuard {
let mut inner = self.pool.inner.lock().unwrap(); let mut inner = self.pool.inner.lock().unwrap();
if self.dirty { // panic 展开期(thread::panicking 为真)被 drop 的 VM 一律按 dirty 丢弃:
// 损坏 VM 上执行 full_reset 可能二次 panic,unwind 中二次 panic 即 abort。
if self.dirty || std::thread::panicking() {
vm_debug!("pool: discarding dirty vm"); vm_debug!("pool: discarding dirty vm");
let new_vm = self.pool.replace_vm(); let new_vm = self.pool.replace_vm();
inner.available.push(new_vm); inner.available.push(new_vm);
+5
View File
@@ -166,6 +166,7 @@ impl Vm {
saved_table_gen_stack: Vec::new(), saved_table_gen_stack: Vec::new(),
trace_instructions: false, trace_instructions: false,
pc_watch: None, pc_watch: None,
max_steps_override: None,
}; };
vm.init_generator_intrinsics(); vm.init_generator_intrinsics();
vm.init_promise_intrinsics(); vm.init_promise_intrinsics();
@@ -309,6 +310,7 @@ impl Vm {
saved_table_gen_stack: Vec::new(), saved_table_gen_stack: Vec::new(),
trace_instructions: false, trace_instructions: false,
pc_watch: None, pc_watch: None,
max_steps_override: None,
}; };
vm.init_generator_intrinsics(); vm.init_generator_intrinsics();
vm.init_promise_intrinsics(); vm.init_promise_intrinsics();
@@ -429,6 +431,9 @@ impl Vm {
self.symbols.reset(); self.symbols.reset();
self.root_reg_limit = 0; self.root_reg_limit = 0;
self.active_reg_limit = 0; self.active_reg_limit = 0;
// 步数上限覆盖随池回收清位:执行路径按请求必设覆盖(含 None),
// 此处清位保证回收的 VM 不带旧覆盖回到默认态,循环 VM 回到内核配置。
self.max_steps_override = None;
} }
/// 释放全部 session 堆数据:session 对象(本体 + 堆数据 + upvalue 列表) /// 释放全部 session 堆数据:session 对象(本体 + 堆数据 + upvalue 列表)
+54
View File
@@ -1114,3 +1114,57 @@ fn full_reset_clears_symbol_state() {
assert!(vm.symbols.symbol_descriptions.is_empty()); assert!(vm.symbols.symbol_descriptions.is_empty());
assert!(vm.symbols.symbol_registry.is_empty()); assert!(vm.symbols.symbol_registry.is_empty());
} }
/// 编译源文本为模块(供步数上限测试直接 `run` 并检查 `Result`)。
fn compile_module(source: &str) -> Arc<oxide_bytecode::module::CompiledModule> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
let module = oxide_compiler::compiler::Compiler::new()
.compile(&program)
.expect("compile failed");
Arc::new(module)
}
/// 步数覆盖超限:`set_max_steps(Some(100))` 加无限循环返回 `Err`,文本含 `step limit`。
#[test]
fn max_steps_override_exceeded_returns_err() {
let mut vm = Vm::new();
vm.set_max_steps(Some(100));
let module = compile_module("for(;;){}");
let result = vm.run(&module);
assert!(result.is_err(), "无限循环带步数覆盖应返回 Err:{result:?}");
let err = result.unwrap_err();
assert!(err.contains("step limit"), "错误文本应含 step limit:{err}");
}
/// 覆盖随 `full_reset` 清除:`Some(100)` 超限失败后 `full_reset`,再跑 50 轮循环
/// (步数远超 100)返回 `Ok`,证明覆盖值未残留(回退内核配置无上限)。
#[test]
fn full_reset_clears_max_steps_override() {
let mut vm = Vm::new();
vm.set_max_steps(Some(100));
let infinite = compile_module("for(;;){}");
assert!(vm.run(&infinite).is_err(), "无限循环应超限");
vm.full_reset();
// 50 轮循环步数远超 100:覆盖清除后无上限,应返回 Ok。
let moderate = compile_module("let n = 0; for (let i = 0; i < 50; i++) { n += i; } n");
assert!(vm.run(&moderate).is_ok(), "full_reset 后 50 轮循环不应被旧覆盖限制");
}
/// `set_max_steps(None)` 回退内核配置:内核配置 `max_steps` 置 `Some(100)` 的独立
/// kernel 上,覆盖置 None 后超限仍被限(`or` 回退语义)。
#[test]
fn max_steps_none_falls_back_to_kernel_config() {
let mut config = KernelConfig::minimal();
config.max_steps = Some(100);
let core = KernelCore::new(config);
let mut vm = Vm::with_kernel_core(Arc::clone(&core));
vm.set_max_steps(None);
let module = compile_module("for(;;){}");
let result = vm.run(&module);
assert!(result.is_err(), "内核配置限步下无限循环应返回 Err:{result:?}");
let err = result.unwrap_err();
assert!(err.contains("step limit"), "错误文本应含 step limit:{err}");
}