mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: server 测试跨二进制全局路径加 fcntl 文件锁串行
This commit is contained in:
Generated
+20
-19
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,12 +1204,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
"clap",
|
||||
"ctrlc",
|
||||
"libc",
|
||||
"oxide_bytecode",
|
||||
"oxide_code_cache",
|
||||
"oxide_compiler",
|
||||
@@ -1226,7 +1227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1235,7 +1236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1255,7 +1256,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1269,7 +1270,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1284,7 +1285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1296,7 +1297,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1305,7 +1306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1319,7 +1320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1331,7 +1332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1340,7 +1341,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1355,7 +1356,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1371,7 +1372,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1384,7 +1385,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1402,7 +1403,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1415,7 +1416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.14.11"
|
||||
version = "0.14.12"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -22,6 +22,7 @@ oxide_engine = { path = "../oxide_engine" }
|
||||
oxide_log = { path = "../oxide_log" }
|
||||
bumpalo.workspace = true
|
||||
clap.workspace = true
|
||||
libc = "0.2"
|
||||
ctrlc.workspace = true
|
||||
rustyline.workspace = true
|
||||
ansi_term.workspace = true
|
||||
|
||||
@@ -1317,6 +1317,9 @@ mod tests {
|
||||
/// 正常退出,socket 文件被删除,全局 well-known 路径前后不变。
|
||||
#[test]
|
||||
fn rm_eval_then_eof_exits() {
|
||||
// 跨进程文件锁:与写全局路径的集成测试二进制串行,保证前后不变断言
|
||||
// 不被并发写污染。
|
||||
let _file_lock = sidecar::WellKnownLock::acquire().expect("全局路径文件锁应可取");
|
||||
let config = rm_config("eval_eof");
|
||||
let well_known_sidecar = sidecar::well_known_sidecar_path();
|
||||
let well_known_socket = sidecar::well_known_socket_path();
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
//! - 日志文件由 sidecar 路径按扩展名派生(`.log`),与 sidecar、socket 同主名
|
||||
//! 同目录;有意不随退出删除(跨重启的诊断工件),不在清理命令范围内。
|
||||
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Write};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
@@ -93,6 +94,59 @@ pub fn well_known_log_path() -> PathBuf {
|
||||
well_known_sidecar_path().with_extension("log")
|
||||
}
|
||||
|
||||
/// well-known 全局路径排他锁文件:`$TMPDIR/oxide-<uid>.lock`(与 sidecar 同主名)。
|
||||
pub fn well_known_lock_path() -> PathBuf {
|
||||
well_known_sidecar_path().with_extension("lock")
|
||||
}
|
||||
|
||||
/// well-known 全局路径排他锁:fcntl 文件锁,跨进程互斥。
|
||||
///
|
||||
/// 持锁期间保护 well-known 全局路径(sidecar、socket、日志)的读写;
|
||||
/// 锁随文件描述符关闭(Drop)自动释放,进程消亡时内核一并释放。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - fcntl 锁以进程为单位:同进程多线程并发取锁不互斥,进程内串行化
|
||||
/// 由调用方自己的进程内互斥负责。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 创建锁文件(不存在时)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 锁文件不随释放删除:删除会破坏他进程已打开描述符上的锁,互斥失效。
|
||||
pub struct WellKnownLock {
|
||||
/// 持有文件描述符:锁随描述符关闭(Drop)释放,字段存在即保活。
|
||||
#[allow(dead_code)]
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl WellKnownLock {
|
||||
/// 取 well-known 全局路径排他锁(阻塞直至持有)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 打开锁文件(不存在则创建)。
|
||||
/// 2. fcntl 阻塞取排他锁(F_SETLKW)。
|
||||
pub fn acquire() -> io::Result<Self> {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(well_known_lock_path())?;
|
||||
// SAFETY: 文件描述符在 file 生命周期内有效,锁结构体按值传给内核只读。
|
||||
let lock = libc::flock {
|
||||
l_type: libc::F_WRLCK as _,
|
||||
l_whence: libc::SEEK_SET as _,
|
||||
l_start: 0,
|
||||
l_len: 0,
|
||||
l_pid: 0,
|
||||
};
|
||||
let rc = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_SETLKW, &lock) };
|
||||
if rc < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(WellKnownLock { file })
|
||||
}
|
||||
}
|
||||
|
||||
/// O_EXCL 原子创建 sidecar(文件已存在时创建失败)。
|
||||
///
|
||||
/// # 步骤
|
||||
@@ -477,4 +531,23 @@ mod tests {
|
||||
assert_eq!(log.file_stem(), sidecar.file_stem(), "日志与 sidecar 应同主名");
|
||||
assert_eq!(log.parent(), sidecar.parent(), "日志与 sidecar 应同目录");
|
||||
}
|
||||
|
||||
/// 锁路径:`.lock` 扩展名,与 sidecar 路径同主名同目录。
|
||||
#[test]
|
||||
fn well_known_lock_path_derives_from_sidecar() {
|
||||
let lock = well_known_lock_path();
|
||||
let sidecar = well_known_sidecar_path();
|
||||
assert_eq!(lock.extension().and_then(|e| e.to_str()), Some("lock"), "锁路径应以 .lock 结尾");
|
||||
assert_eq!(lock.file_stem(), sidecar.file_stem(), "锁与 sidecar 应同主名");
|
||||
assert_eq!(lock.parent(), sidecar.parent(), "锁与 sidecar 应同目录");
|
||||
}
|
||||
|
||||
/// 全局路径锁:取锁与释放后重取均成功(fcntl 锁以进程为单位,
|
||||
/// 同进程重取不自我阻塞)。
|
||||
#[test]
|
||||
fn well_known_lock_acquire_release_reacquire() {
|
||||
let lock = WellKnownLock::acquire().expect("取锁应成功");
|
||||
drop(lock);
|
||||
let _again = WellKnownLock::acquire().expect("释放后重取应成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,40 @@
|
||||
//!
|
||||
//! start / restart / watchdog 端到端与 cleanup 走 well-known 全局路径
|
||||
//! (每用户单例):测试先探活,socket 存活即 panic 不抢占存活 server;
|
||||
//! 触碰全局路径的测试经同一把锁串行,避免相互干扰。
|
||||
//! 触碰全局路径的测试经双重锁(进程内互斥加跨进程文件锁)串行,避免相互
|
||||
//! 干扰(含与单测二进制的并行干扰)。
|
||||
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process::{Command, Output};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use oxide_cli::server::client::send_control_request;
|
||||
use oxide_cli::server::protocol::{self, FrameReader, ServerRequest, ServerResponse};
|
||||
use oxide_cli::server::sidecar;
|
||||
|
||||
/// well-known 全局路径互斥锁:同测试二进制内触碰全局路径的测试串行执行。
|
||||
/// well-known 全局路径进程内互斥锁:同测试二进制内触碰全局路径的测试串行
|
||||
/// 执行(fcntl 锁以进程为单位,不覆盖同进程多线程)。
|
||||
static WELL_KNOWN_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// well-known 全局路径双重锁守卫:进程内互斥(同二进制线程串行)加跨进程
|
||||
/// fcntl 文件锁(nextest 并行独立进程串行)。
|
||||
struct WellKnownGuard {
|
||||
_in_process: MutexGuard<'static, ()>,
|
||||
_cross_process: sidecar::WellKnownLock,
|
||||
}
|
||||
|
||||
/// 取 well-known 全局路径双重锁,守卫随 drop 释放。
|
||||
fn lock_well_known() -> WellKnownGuard {
|
||||
let in_process = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let cross_process = sidecar::WellKnownLock::acquire().expect("全局路径文件锁应可取");
|
||||
WellKnownGuard {
|
||||
_in_process: in_process,
|
||||
_cross_process: cross_process,
|
||||
}
|
||||
}
|
||||
|
||||
/// 运行 oxide 二进制并返回输出。
|
||||
fn oxide(args: &[&str]) -> Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_oxide"))
|
||||
@@ -93,7 +112,7 @@ fn server_help_lists_subcommands() {
|
||||
/// well-known 日志文件(无存活 server 时删除安全)。
|
||||
#[test]
|
||||
fn server_log_no_file_exit_1() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
assert!(
|
||||
!sidecar::is_server_alive(&sidecar::well_known_socket_path()),
|
||||
"存活 server 占用 well-known 路径,测试不抢占"
|
||||
@@ -113,7 +132,7 @@ fn server_log_no_file_exit_1() {
|
||||
/// 走 well-known 全局路径:先探活,存活 server 占用时 panic 不抢占。
|
||||
#[test]
|
||||
fn server_control_arms_no_server_exit_1() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
assert!(
|
||||
!sidecar::is_server_alive(&sidecar::well_known_socket_path()),
|
||||
"存活 server 占用 well-known 路径,测试不抢占"
|
||||
@@ -132,7 +151,7 @@ fn server_control_arms_no_server_exit_1() {
|
||||
/// 走 well-known 全局路径:先探活,存活 server 占用时 panic 不抢占。
|
||||
#[test]
|
||||
fn server_forge_no_server_exit_1() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
assert!(
|
||||
!sidecar::is_server_alive(&sidecar::well_known_socket_path()),
|
||||
"存活 server 占用 well-known 路径,测试不抢占"
|
||||
@@ -147,7 +166,7 @@ fn server_forge_no_server_exit_1() {
|
||||
/// cleanup 退 0(幂等:有残留则清理、无残留则报无残留),事后全局路径无文件。
|
||||
#[test]
|
||||
fn server_cleanup_exit_0() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
assert!(
|
||||
!sidecar::is_server_alive(&sidecar::well_known_socket_path()),
|
||||
"存活 server 占用 well-known 路径,测试不抢占"
|
||||
@@ -164,7 +183,7 @@ fn server_cleanup_exit_0() {
|
||||
/// 进程退出,socket 与 sidecar 文件被删。
|
||||
#[test]
|
||||
fn server_start_e2e() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
|
||||
@@ -213,7 +232,7 @@ fn server_start_e2e() {
|
||||
/// server 即通过)。
|
||||
#[test]
|
||||
fn server_start_when_running() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
|
||||
@@ -251,7 +270,7 @@ fn server_start_when_running() {
|
||||
/// 进程号不同且版本为构建期版本,新 server 健康与版本请求正常,收尾关闭后文件被删。
|
||||
#[test]
|
||||
fn server_restart_e2e() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
|
||||
@@ -331,7 +350,7 @@ fn server_restart_e2e() {
|
||||
/// (Info 级无 ERROR 行),关闭后日志文件有意保留。
|
||||
#[test]
|
||||
fn server_log_e2e() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
let log_path = sidecar::well_known_log_path();
|
||||
@@ -412,7 +431,7 @@ fn server_log_e2e() {
|
||||
/// 打印 gc 与清缓存行;关闭后 socket 与 sidecar 文件被删。
|
||||
#[test]
|
||||
fn server_forge_e2e() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
|
||||
@@ -471,7 +490,7 @@ fn server_forge_e2e() {
|
||||
/// 关闭后 watchdog 退 0 且 stdout 含崩溃提示与日志尾部。
|
||||
#[test]
|
||||
fn server_watchdog_e2e() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
|
||||
@@ -574,7 +593,7 @@ fn server_watchdog_e2e() {
|
||||
/// sidecar 消失、server 进程已退出(停 watchdog 即优雅停 server)。
|
||||
#[test]
|
||||
fn server_watchdog_sigint_stops_server() {
|
||||
let _guard = WELL_KNOWN_LOCK.lock().expect("全局路径锁不应中毒");
|
||||
let _guard = lock_well_known();
|
||||
let socket = sidecar::well_known_socket_path();
|
||||
let sidecar_path = sidecar::well_known_sidecar_path();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user