mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: --rm 支持 TCP 动态端口,绑定 0 端口后前台打印实际端口
This commit is contained in:
Generated
+20
-20
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1228,7 +1228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1237,7 +1237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1257,7 +1257,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1271,7 +1271,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1286,7 +1286,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1298,7 +1298,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1307,7 +1307,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1333,7 +1333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1342,11 +1342,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_net"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1361,7 +1361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1377,7 +1377,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1390,7 +1390,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1408,7 +1408,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1421,7 +1421,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.15.7"
|
||||
version = "0.15.8"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -125,9 +125,10 @@ enum ServerCommands {
|
||||
/// 常驻 worker 线程数;缺省取宿主核数。
|
||||
#[arg(long)]
|
||||
workers: Option<u32>,
|
||||
/// TCP 监听端口;缺省取 well-known 端口。
|
||||
#[arg(long, default_value_t = sidecar::WELL_KNOWN_PORT, conflicts_with = "rm")]
|
||||
port: u16,
|
||||
/// TCP 监听端口;0 表示动态端口(操作系统分配空闲端口),缺省取
|
||||
/// well-known 端口。
|
||||
#[arg(long)]
|
||||
port: Option<u16>,
|
||||
/// 前台形态:当前进程直接运行持久 server(守护形态 spawn 的子进程用,
|
||||
/// 不向用户暴露)。
|
||||
#[arg(long, hide = true)]
|
||||
@@ -340,8 +341,15 @@ fn server_command(command: ServerCommands) -> ExitCode {
|
||||
if let Some(n) = workers {
|
||||
config.worker_count = n as usize;
|
||||
}
|
||||
// 显式端口映射 TCP 端点(回环地址硬编码):0 为动态端口,
|
||||
// 由操作系统在绑定后分配空闲端口;不带端口旗标时保持 Unix
|
||||
// 缺省。
|
||||
if let Some(port) = port {
|
||||
config.endpoint = Endpoint::Tcp(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port));
|
||||
}
|
||||
start_result(run_server_rm(&config))
|
||||
} else if foreground {
|
||||
let port = port.unwrap_or(sidecar::WELL_KNOWN_PORT);
|
||||
let mut config = ServerConfig {
|
||||
endpoint: Endpoint::Tcp(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)),
|
||||
..ServerConfig::well_known()
|
||||
@@ -351,7 +359,7 @@ fn server_command(command: ServerCommands) -> ExitCode {
|
||||
}
|
||||
start_result(run_server(&config))
|
||||
} else {
|
||||
start_daemon(workers, port)
|
||||
start_daemon(workers, port.unwrap_or(sidecar::WELL_KNOWN_PORT))
|
||||
}
|
||||
}
|
||||
ServerCommands::Cleanup => match well_known_cleanup() {
|
||||
|
||||
@@ -272,7 +272,9 @@ pub fn run_server(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 建关闭标志并注册信号处理器(幂等);失败直接返回(无 sidecar 可回滚)。
|
||||
/// 2. 绑定监听器(进程唯一路径,与持久路径共用同一绑定函数)。
|
||||
/// 2. 绑定监听器(进程唯一路径,与持久路径共用同一绑定函数);TCP 臂取实际
|
||||
/// 本地地址、向前台标准输出打印端点(就绪信号的一部分),上下文端点文本
|
||||
/// 取实际地址。
|
||||
/// 3. 建内核(标准配置)、共享聚合计数器,派生 worker(参数与持久 server 一致)。
|
||||
/// 4. 空闲窗口一:等首个连接;空闲超时内无连接即干净退出(正常退出,非错误)。
|
||||
/// 5. 首个连接派生连接线程:处理完毕(EOF 或读超时)后置关闭标志并减在途计数。
|
||||
@@ -289,6 +291,7 @@ pub fn run_server(config: &ServerConfig) -> Result<(), ServerError> {
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 创建 socket 文件(Unix 臂),正常退出时删除;不写 sidecar。
|
||||
/// - TCP 臂向前台标准输出打印实际监听端点(host:port)。
|
||||
/// - 注册进程级信号处理器(SIGINT、SIGTERM,termination 特性下含 SIGHUP)。
|
||||
/// - 派生 N 个常驻 worker 线程与每连接一个 I/O 线程。
|
||||
///
|
||||
@@ -307,6 +310,18 @@ pub fn run_server_rm(config: &RmServerConfig) -> Result<(), ServerError> {
|
||||
// 绑定函数(删除重试逻辑保留只为共用)。
|
||||
let listener = bind_listener_endpoint(&config.endpoint)?;
|
||||
|
||||
// TCP 臂:取操作系统分配的实际端口(绑 0 端口时由内核分配),向前台
|
||||
// 标准输出打印实际端点(打印在空闲窗口之前,是就绪信号的一部分),
|
||||
// 上下文端点文本取实际地址(Info 响应不暴露未绑定的 0 端口)。
|
||||
let socket_path = match &listener {
|
||||
Listener::Tcp(_) => {
|
||||
let text = listener.local_addr().map_err(|e| ServerError::BindFailed(e.to_string()))?;
|
||||
println!("rm server 监听:{text}");
|
||||
text
|
||||
}
|
||||
Listener::Unix(_) => config.endpoint.describe(),
|
||||
};
|
||||
|
||||
// 建内核(标准配置)与共享聚合计数器(全体 worker 池的增减累加到同一聚合)。
|
||||
let kernel = KernelCore::new(KernelConfig::standard());
|
||||
let counters = PoolCounters::shared();
|
||||
@@ -327,7 +342,7 @@ pub fn run_server_rm(config: &RmServerConfig) -> Result<(), ServerError> {
|
||||
shutdown: Arc::clone(&shutdown),
|
||||
in_flight: Arc::new(AtomicUsize::new(0)),
|
||||
pool_counters: counters,
|
||||
socket_path: config.endpoint.describe(),
|
||||
socket_path,
|
||||
});
|
||||
|
||||
// 空闲窗口一:等首个连接;空闲超时内无连接即干净退出。
|
||||
@@ -809,7 +824,7 @@ fn wait_in_flight_drained(in_flight: &AtomicUsize, timeout: Duration) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use std::net::TcpStream;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
/// 测试全部用 Unix 端点;直接取 socket 路径。
|
||||
@@ -892,6 +907,49 @@ mod tests {
|
||||
handle
|
||||
}
|
||||
|
||||
/// 预取空闲端口:临时 `TcpListener` 绑 0 端口取操作系统分配的端口后释放。
|
||||
fn free_tcp_port() -> u16 {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("临时监听者绑定应成功");
|
||||
let port = listener.local_addr().expect("取端口应成功").port();
|
||||
drop(listener);
|
||||
port
|
||||
}
|
||||
|
||||
/// --rm TCP 配置:固定空闲端口、worker 数 1、300 毫秒空闲超时(与 Unix
|
||||
/// 配置同口径,不依赖真实超时值,断言带 5 秒裕量)。
|
||||
fn rm_tcp_config() -> RmServerConfig {
|
||||
RmServerConfig {
|
||||
endpoint: Endpoint::Tcp(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), free_tcp_port())),
|
||||
worker_count: 1,
|
||||
idle_timeout: Duration::from_millis(300),
|
||||
}
|
||||
}
|
||||
|
||||
/// 后台线程起真实 run_server_rm(TCP 臂)。
|
||||
///
|
||||
/// TCP 无 socket 文件可等,就绪由调用方轮询 `TcpStream::connect` 判定;
|
||||
/// 首个成功连接即 server 服务的单连接(监听器随后被丢弃),调用方须保留
|
||||
/// 该连接、不得探后即弃。
|
||||
fn start_rm_server_tcp(config: RmServerConfig) -> JoinHandle<Result<(), ServerError>> {
|
||||
let server_config = config.clone();
|
||||
std::thread::spawn(move || run_server_rm(&server_config))
|
||||
}
|
||||
|
||||
/// 连接 TCP rm server:轮询 connect 至成功(首个成功连接即 server 服务的
|
||||
/// 单连接,返回后由调用方保留使用)。
|
||||
fn connect_rm_tcp(addr: SocketAddr) -> TcpStream {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
match TcpStream::connect(addr) {
|
||||
Ok(stream) => return stream,
|
||||
Err(_) => {
|
||||
assert!(Instant::now() < deadline, "TCP rm server 应在 10 秒内就绪");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 信号处理器体:置位关闭标志。
|
||||
#[test]
|
||||
fn signal_shutdown_sets_flag() {
|
||||
@@ -1489,4 +1547,38 @@ mod tests {
|
||||
stop_server(&mut stream, handle);
|
||||
assert!(!unix_path(&config.endpoint).exists(), "socket 文件应被删除");
|
||||
}
|
||||
|
||||
/// --rm TCP 臂:eval 得完成值后客户端断开,server 在有界时间内正常退出
|
||||
/// (走绑定、打印、空闲窗口、单连接的完整 TCP 代码路径)。
|
||||
#[test]
|
||||
fn rm_tcp_eval_then_eof_exits() {
|
||||
let config = rm_tcp_config();
|
||||
let handle = start_rm_server_tcp(config.clone());
|
||||
let addr = match &config.endpoint {
|
||||
Endpoint::Tcp(addr) => *addr,
|
||||
_ => panic!("应为 TCP 端点"),
|
||||
};
|
||||
let mut stream = Conn::Tcp(connect_rm_tcp(addr));
|
||||
let response = send_and_recv(
|
||||
&mut stream,
|
||||
&ServerRequest::Eval {
|
||||
code: "1 + 1".into(),
|
||||
max_steps: None,
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
matches!(response, ServerResponse::EvalResult { ref value, .. } if value.as_deref() == Some("2")),
|
||||
"应得完成值为 2 的 EvalResult 帧:{response:?}"
|
||||
);
|
||||
|
||||
// 客户端断开:连接线程读到 EOF 退出,server 应在空闲超时加裕量内正常退出。
|
||||
drop(stream);
|
||||
let deadline = Instant::now() + config.idle_timeout + Duration::from_secs(5);
|
||||
while !handle.is_finished() {
|
||||
assert!(Instant::now() < deadline, "rm server 应在空闲超时加裕量内退出");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let result = handle.join().expect("rm server 线程应正常退出");
|
||||
assert!(result.is_ok(), "rm server 应正常退出:{result:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,22 @@ impl Listener {
|
||||
Listener::Tcp(l) => Ok(Conn::Tcp(l.accept()?.0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 取监听者绑定的本地地址文本形态。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 两臂委托标准库同名方法后取文本形态(Unix 臂为套接字路径,TCP 臂为
|
||||
/// host:port);TCP 臂绑 0 端口时返回操作系统分配的实际端口。
|
||||
pub fn local_addr(&self) -> io::Result<String> {
|
||||
match self {
|
||||
Listener::Unix(l) => {
|
||||
let addr = l.local_addr()?;
|
||||
let text = addr.as_pathname().map(|p| p.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
Ok(text)
|
||||
}
|
||||
Listener::Tcp(l) => Ok(l.local_addr()?.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Conn {
|
||||
@@ -223,6 +239,30 @@ mod tests {
|
||||
assert_eq!(&buf, b"hello-oxide");
|
||||
}
|
||||
|
||||
/// 动态端口:TCP 臂绑 0 端口后 `local_addr` 文本形态含操作系统分配的非 0 端口。
|
||||
#[test]
|
||||
fn tcp_local_addr_dynamic_port() {
|
||||
let listener = Listener::Tcp(TcpListener::bind("127.0.0.1:0").unwrap());
|
||||
let text = listener.local_addr().unwrap();
|
||||
let port: u16 = text.rsplit(':').next().expect("应含端口").parse().expect("端口应可解析");
|
||||
assert_ne!(port, 0, "操作系统应分配非 0 端口:{text}");
|
||||
}
|
||||
|
||||
/// Unix 臂 `local_addr` 返回套接字路径的文本形态(非空)。
|
||||
#[test]
|
||||
fn unix_local_addr() {
|
||||
let dir = std::env::temp_dir().join(format!("oxide_net_{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("unix_addr.sock");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
|
||||
let listener = Listener::Unix(UnixListener::bind(&path).unwrap());
|
||||
let text = listener.local_addr().unwrap();
|
||||
assert!(!text.is_empty(), "Unix 臂应得非空路径文本:{text}");
|
||||
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
/// 非阻塞 accept 空轮:置非阻塞后无连接时 accept 应返回 `WouldBlock`(两臂各验一次)。
|
||||
#[test]
|
||||
fn nonblocking_accept_would_block() {
|
||||
|
||||
Reference in New Issue
Block a user