mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: server 信号处理,SIGINT/SIGTERM 置位关闭标志走优雅退出
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
//! 信号处理集成测试(SIGINT):真实信号路径触发优雅退出。
|
||||
//!
|
||||
//! 独立进程、进程内唯一 server 与唯一信号处理器:`ctrlc` 的处理器注册是
|
||||
//! 进程级一次性的,信号投递无歧义,测试确定。SIGTERM 路径见
|
||||
//! `server_signal_term.rs`(同型,独立进程)。
|
||||
|
||||
use std::process::Command;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use oxide_cli::server::server::{run_server, ServerConfig, ServerError};
|
||||
|
||||
/// 唯一临时路径(进程号加测试名):单进程内唯一,进程间以进程号隔离。
|
||||
/// worker 数取小值 2:与既有单测同口径,大值徒增预热成本。
|
||||
fn unique_paths(test_name: &str) -> ServerConfig {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"oxide_server_signal_int_{}_{}",
|
||||
std::process::id(),
|
||||
test_name
|
||||
));
|
||||
std::fs::create_dir_all(&dir).expect("测试目录创建应成功");
|
||||
ServerConfig {
|
||||
socket_path: dir.join("server.sock"),
|
||||
sidecar_path: dir.join("sidecar.json"),
|
||||
worker_count: 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// 后台线程起真实 run_server,等 socket 文件出现(bind 成功即就绪,
|
||||
/// 且蕴含信号处理器已注册——注册在绑定之前)。
|
||||
fn start_server(config: ServerConfig) -> JoinHandle<Result<(), ServerError>> {
|
||||
let server_config = config.clone();
|
||||
let handle = std::thread::spawn(move || run_server(&server_config));
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
while !config.socket_path.exists() {
|
||||
assert!(Instant::now() < deadline, "socket 文件未出现");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
/// 给本测试进程发 SIGINT(`kill` 二进制与 sidecar 的 `pid_alive` 同一口径)。
|
||||
fn send_sigint() {
|
||||
let status = Command::new("kill")
|
||||
.args(["-INT", &std::process::id().to_string()])
|
||||
.status()
|
||||
.expect("kill 应可执行");
|
||||
assert!(status.success(), "kill -INT 应成功:{status:?}");
|
||||
}
|
||||
|
||||
/// 等 server 线程退出,断言正常退出与 sidecar、socket 文件均被删除。
|
||||
fn wait_shutdown(config: &ServerConfig, handle: JoinHandle<Result<(), ServerError>>) {
|
||||
let deadline = Instant::now() + Duration::from_secs(30);
|
||||
while !handle.is_finished() {
|
||||
assert!(Instant::now() < deadline, "server 线程应在有界时间内退出");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let result = handle.join().expect("server 线程应正常退出");
|
||||
assert!(result.is_ok(), "server 应正常退出:{result:?}");
|
||||
assert!(!config.sidecar_path.exists(), "sidecar 文件应被删除");
|
||||
assert!(!config.socket_path.exists(), "socket 文件应被删除");
|
||||
}
|
||||
|
||||
/// SIGINT 触发优雅退出:在途归零、删 sidecar 与 socket 文件。
|
||||
#[test]
|
||||
fn sigint_graceful_shutdown() {
|
||||
let config = unique_paths("sigint");
|
||||
let handle = start_server(config.clone());
|
||||
send_sigint();
|
||||
wait_shutdown(&config, handle);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//! 信号处理集成测试(SIGTERM):真实信号路径触发优雅退出。
|
||||
//!
|
||||
//! 独立进程、进程内唯一 server 与唯一信号处理器:`ctrlc` 的处理器注册是
|
||||
//! 进程级一次性的,信号投递无歧义,测试确定。SIGINT 路径见
|
||||
//! `server_signal_int.rs`(同型,独立进程)。
|
||||
|
||||
use std::process::Command;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use oxide_cli::server::server::{run_server, ServerConfig, ServerError};
|
||||
|
||||
/// 唯一临时路径(进程号加测试名):单进程内唯一,进程间以进程号隔离。
|
||||
/// worker 数取小值 2:与既有单测同口径,大值徒增预热成本。
|
||||
fn unique_paths(test_name: &str) -> ServerConfig {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"oxide_server_signal_term_{}_{}",
|
||||
std::process::id(),
|
||||
test_name
|
||||
));
|
||||
std::fs::create_dir_all(&dir).expect("测试目录创建应成功");
|
||||
ServerConfig {
|
||||
socket_path: dir.join("server.sock"),
|
||||
sidecar_path: dir.join("sidecar.json"),
|
||||
worker_count: 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// 后台线程起真实 run_server,等 socket 文件出现(bind 成功即就绪,
|
||||
/// 且蕴含信号处理器已注册——注册在绑定之前)。
|
||||
fn start_server(config: ServerConfig) -> JoinHandle<Result<(), ServerError>> {
|
||||
let server_config = config.clone();
|
||||
let handle = std::thread::spawn(move || run_server(&server_config));
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
while !config.socket_path.exists() {
|
||||
assert!(Instant::now() < deadline, "socket 文件未出现");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
/// 给本测试进程发 SIGTERM(`kill` 二进制与 sidecar 的 `pid_alive` 同一口径)。
|
||||
fn send_sigterm() {
|
||||
let status = Command::new("kill")
|
||||
.args(["-TERM", &std::process::id().to_string()])
|
||||
.status()
|
||||
.expect("kill 应可执行");
|
||||
assert!(status.success(), "kill -TERM 应成功:{status:?}");
|
||||
}
|
||||
|
||||
/// 等 server 线程退出,断言正常退出与 sidecar、socket 文件均被删除。
|
||||
fn wait_shutdown(config: &ServerConfig, handle: JoinHandle<Result<(), ServerError>>) {
|
||||
let deadline = Instant::now() + Duration::from_secs(30);
|
||||
while !handle.is_finished() {
|
||||
assert!(Instant::now() < deadline, "server 线程应在有界时间内退出");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let result = handle.join().expect("server 线程应正常退出");
|
||||
assert!(result.is_ok(), "server 应正常退出:{result:?}");
|
||||
assert!(!config.sidecar_path.exists(), "sidecar 文件应被删除");
|
||||
assert!(!config.socket_path.exists(), "socket 文件应被删除");
|
||||
}
|
||||
|
||||
/// SIGTERM 触发优雅退出:在途归零、删 sidecar 与 socket 文件。
|
||||
#[test]
|
||||
fn sigterm_graceful_shutdown() {
|
||||
let config = unique_paths("sigterm");
|
||||
let handle = start_server(config.clone());
|
||||
send_sigterm();
|
||||
wait_shutdown(&config, handle);
|
||||
}
|
||||
Reference in New Issue
Block a user