From a08652252bf68420f2987e7c45cc210cf4ddc9f8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 6 Oct 2026 15:38:09 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B1=A0=E7=8A=B6=E6=80=81=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8A=A0=E9=AB=98=E6=B0=B4=E4=BD=8D=E8=B7=9F=E8=B8=AA?= =?UTF-8?q?=E4=B8=8E=20Status=20=E5=B3=B0=E5=80=BC=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 36 ++++----- Cargo.toml | 2 +- crates/oxide_cli/src/server/protocol.rs | 4 +- crates/oxide_cli/src/server/server.rs | 3 + crates/oxide_vm/src/vm_pool.rs | 101 +++++++++++++++++++++++- 5 files changed, 125 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b470b46..ab32512f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.28" +version = "0.8.30" dependencies = [ "chrono", "fast-float", @@ -1120,7 +1120,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.28" +version = "0.8.30" dependencies = [ "num-bigint", "oxide_log", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.28" +version = "0.8.30" dependencies = [ "ansi_term", "bumpalo", @@ -1159,7 +1159,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.28" +version = "0.8.30" dependencies = [ "lru", "oxide_bytecode", @@ -1168,7 +1168,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1187,7 +1187,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1201,7 +1201,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.28" +version = "0.8.30" dependencies = [ "num-bigint", "oxc_allocator", @@ -1216,7 +1216,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.28" +version = "0.8.30" dependencies = [ "bumpalo", "dashmap", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.28" +version = "0.8.30" dependencies = [ "tracing", "tracing-appender", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1276,7 +1276,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1292,7 +1292,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.28" +version = "0.8.30" dependencies = [ "num-bigint", "num-traits", @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.28" +version = "0.8.30" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.28" +version = "0.8.30" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.28" +version = "0.8.30" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 1d9fa9ad..2fdb9bf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.29" +version = "0.8.30" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_cli/src/server/protocol.rs b/crates/oxide_cli/src/server/protocol.rs index 335a930b..a8fd516b 100644 --- a/crates/oxide_cli/src/server/protocol.rs +++ b/crates/oxide_cli/src/server/protocol.rs @@ -90,10 +90,11 @@ pub enum ServerResponse { }, /// 版本响应。 Version { version: String }, - /// 状态响应:池可用/总数与运行时长。 + /// 状态响应:池可用/总数、峰值借出数与运行时长。 Status { pool_available: usize, pool_total: usize, + pool_peak: usize, uptime_ms: u64, }, /// 健康检查响应。 @@ -334,6 +335,7 @@ mod tests { ServerResponse::Status { pool_available: 2, pool_total: 4, + pool_peak: 3, uptime_ms: 1500, }, ServerResponse::Health { healthy: true }, diff --git a/crates/oxide_cli/src/server/server.rs b/crates/oxide_cli/src/server/server.rs index 6fd6479a..cd27dcc8 100644 --- a/crates/oxide_cli/src/server/server.rs +++ b/crates/oxide_cli/src/server/server.rs @@ -346,6 +346,7 @@ fn dispatch_control(request: &ServerRequest, ctx: &ServerContext) -> ServerRespo ServerRequest::Status => ServerResponse::Status { pool_available: ctx.pool_counters.available(), pool_total: ctx.pool_counters.total(), + pool_peak: ctx.pool_counters.peak(), uptime_ms: ctx.started_at.elapsed().as_millis() as u64, }, ServerRequest::Health => ServerResponse::Health { healthy: true }, @@ -467,10 +468,12 @@ mod tests { ServerResponse::Status { pool_available, pool_total, + pool_peak, uptime_ms, } => { assert!(pool_available <= pool_total, "空闲数应不超过总数:{pool_available}/{pool_total}"); assert!(pool_total >= 1, "总数应不小于 1(标准配置预热 1 个):{pool_total}"); + assert!(pool_peak <= pool_total, "峰值应不超过总数:{pool_peak}/{pool_total}"); assert!(uptime_ms < 10_000, "刚启动的 server 运行时长应小于 10 秒:{uptime_ms}"); } other => panic!("应得 Status 帧,实得 {other:?}"), diff --git a/crates/oxide_vm/src/vm_pool.rs b/crates/oxide_vm/src/vm_pool.rs index 8744cf1c..181f523f 100644 --- a/crates/oxide_vm/src/vm_pool.rs +++ b/crates/oxide_vm/src/vm_pool.rs @@ -13,13 +13,17 @@ struct VmPoolInner { total_count: usize, } -/// 池状态计数器:空闲数与已创建总数的跨线程可读快照。 +/// 池状态计数器:空闲数、在借数、峰值借出数与已创建总数的跨线程可读快照。 /// /// 池本体不是 Send/Sync(Vm 不跨线程),状态接口经独立原子暴露, /// 与队列变更同步更新;空闲数恒不大于总数(总数只增不减)。 +/// 安静态(无并发借出归还)下在借数恒等于总数减空闲数;峰值是每次 +/// 借出后精确在借数的最大值,单调不降。 pub struct PoolCounters { available: AtomicUsize, total: AtomicUsize, + in_use: AtomicUsize, + peak: AtomicUsize, } impl PoolCounters { @@ -30,6 +34,8 @@ impl PoolCounters { Arc::new(PoolCounters { available: AtomicUsize::new(0), total: AtomicUsize::new(0), + in_use: AtomicUsize::new(0), + peak: AtomicUsize::new(0), }) } @@ -42,6 +48,25 @@ impl PoolCounters { pub fn total(&self) -> usize { self.total.load(Ordering::Relaxed) } + + /// 读取历史峰值借出数(高水位,单调不降)。 + pub fn peak(&self) -> usize { + self.peak.load(Ordering::Relaxed) + } + + /// 记录一次借出:在借数加一,并抬高历史峰值。 + /// + /// 峰值取借出后精确在借数的最大值,单调不降;归还路径不触碰峰值。 + /// 诊断计数器,`Relaxed` 序,不参与同步语义。 + fn record_borrow(&self) { + let in_use = self.in_use.fetch_add(1, Ordering::Relaxed) + 1; + self.peak.fetch_max(in_use, Ordering::Relaxed); + } + + /// 记录一次归还:在借数减一。 + fn record_return(&self) { + self.in_use.fetch_sub(1, Ordering::Relaxed); + } } /// 共享 `Vm` 实例池:按需创建并复用 VM,避免每次执行重建 kernel 共享状态。 @@ -132,6 +157,7 @@ impl VmPool { if let Some(vm) = inner.available.pop() { vm_trace!("pool: reused vm, {} available", inner.available.len()); self.counters.available.fetch_sub(1, Ordering::Relaxed); + self.counters.record_borrow(); return VmGuard { vm: Some(vm), pool: Arc::clone(self), @@ -149,6 +175,7 @@ impl VmPool { vm_debug!("pool: growing to {} vms", inner.total_count); drop(inner); self.counters.total.fetch_add(1, Ordering::Relaxed); + self.counters.record_borrow(); let vm = Self::new_vm(&self.kernel_core); return VmGuard { vm: Some(vm), @@ -164,6 +191,7 @@ impl VmPool { inner.total_count += 1; drop(inner); self.counters.total.fetch_add(1, Ordering::Relaxed); + self.counters.record_borrow(); let vm = Self::new_vm(&self.kernel_core); return VmGuard { vm: Some(vm), @@ -189,6 +217,13 @@ impl VmPool { self.counters.total() } + /// 读取历史峰值借出数(高水位)。 + /// + /// 读原子快照,不锁池内互斥锁;峰值单调不降。 + pub fn peak_count(&self) -> usize { + self.counters.peak() + } + /// 跨线程可读的池状态句柄(原子快照,无锁)。 pub fn counters(&self) -> Arc { Arc::clone(&self.counters) @@ -234,6 +269,7 @@ impl Drop for VmGuard { } self.pool.counters.available.fetch_add(1, Ordering::Relaxed); + self.pool.counters.record_return(); self.pool.condvar.notify_one(); } } @@ -329,4 +365,67 @@ mod tests { assert_eq!(pool_a.available_count(), counters.available()); assert_eq!(pool_b.available_count(), counters.available()); } + + #[test] + fn test_peak_count_single_thread_exact() { + let kernel = test_kernel(); + let pool = VmPool::new(kernel, 2, None); + assert_eq!(pool.peak_count(), 0, "从未借出的池峰值应为零"); + + let g1 = pool.spawn(); + let g2 = pool.spawn(); + assert_eq!(pool.peak_count(), 2, "连续借出两个后峰值应为 2"); + drop(g1); + drop(g2); + + let g3 = pool.spawn(); + assert_eq!(pool.peak_count(), 2, "归还后再借一个峰值不回落"); + drop(g3); + } + + #[test] + fn test_peak_count_shared_aggregate() { + let kernel = test_kernel(); + let counters = PoolCounters::shared(); + let pool_a = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters)); + let pool_b = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters)); + + let g1 = pool_a.spawn(); + let g2 = pool_b.spawn(); + assert_eq!(counters.peak(), 2, "两池各借出一个后聚合峰值应为 2"); + drop(g1); + drop(g2); + + let g3 = pool_a.spawn(); + assert_eq!(counters.peak(), 2, "归还后再借一个聚合峰值不回落"); + drop(g3); + } + + #[test] + fn test_peak_count_concurrent_bounded() { + let kernel = test_kernel(); + let counters = PoolCounters::shared(); + + let handles: Vec<_> = (0..4) + .map(|_| { + let kernel = Arc::clone(&kernel); + let counters = Arc::clone(&counters); + std::thread::spawn(move || { + // 池本体不跨线程(Vm 不 Send),每线程自建池、共享同一聚合计数器。 + let pool = VmPool::with_counters(kernel, 0, Some(8), counters); + for _ in 0..50 { + let guard = pool.spawn(); + drop(guard); + } + }) + }) + .collect(); + for handle in handles { + handle.join().unwrap(); + } + + // 并发下精确峰值是竞争结果,只断言界:4 线程各至多持 1 个在借。 + let peak = counters.peak(); + assert!((1..=4).contains(&peak), "并发峰值应落在 1 到 4 之间:{peak}"); + } }