fix: clear_transient 补清 overflow 态(id 空间完整复位)+ kernel 增加 active_vms 计数,sweep/重建/kernel-drop 三处 debug_assert 守卫'清空时点无存活 VM'

This commit is contained in:
2026-09-11 21:42:55 +00:00
parent a8c668002b
commit 542236a15a
5 changed files with 107 additions and 5 deletions
+44 -4
View File
@@ -1,6 +1,7 @@
#![allow(clippy::arc_with_non_send_sync)]
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use oxide_types::mem::P;
@@ -136,6 +137,10 @@ pub struct KernelCore {
pub shape_forge: Arc<ShapeForge>,
pub code_forge: Arc<CodeForge>,
pub prop_forge: Arc<PropForge>,
/// 边界不变量守卫计数:当前持有本 kernel `Arc` 的 Vm 数。
/// `Vm` 构造器增、`Drop for Vm` 减(恰好一次),供 sweep / kernel 重建 /
/// kernel drop 处断言"无存活 VM"边界。
active_vms: AtomicUsize,
}
impl KernelCore {
@@ -159,6 +164,7 @@ impl KernelCore {
shape_forge,
code_forge,
prop_forge,
active_vms: AtomicUsize::new(0),
});
kernel_info!("KernelCore initialized: max_cached_modules={}, min_pool={}", max_cached, min_pool);
core
@@ -221,12 +227,19 @@ impl KernelCore {
///
/// # 边界与前提
/// - 调用点须无存活 VM:对象头与 IC 词持 shape id,清空使 id 空间复位,跨复位
/// 存活的 VM 会因 id 复用碰撞静默错槽;引擎不自动重建也不自动守卫,该前提
/// 由宿主契约承担(runner 由循环结构满足:VM 每测试新建即弃,sweep 点在
/// VM 作用域外)。
/// 存活的 VM 会因 id 复用碰撞静默错槽;该前提由 debug_assert 守卫(开发期
/// fail-fast),release 构建无断言,宿主可经 [`Self::active_vms`] 自检;
/// - 引擎不自动重建:id 空间的另一类复位(kernel 整体重建)由宿主在
/// "无存活 VM" 边界驱动(runner 由循环结构满足:VM 每测试新建即弃,
/// 重建/sweep 点都在 VM 作用域外)。
pub fn sweep_runner_forges(&self) {
// id 空间复位在有存活 VM 时是静默错槽隐患,守卫先于阈值判断。
debug_assert!(
self.active_vms.load(Ordering::Relaxed) == 0,
"sweep_runner_forges requires no live VMs (shape id space reset)"
);
// 键 interner 是 append-only(无逐次清理);仅当瞬时 shape/prop 表超阈值
// 时清表(批内兜底),调用点无存活 VM 的前提见文档。
// 时清表(批内兜底)。
if self.shape_forge.len() > 50_000 {
self.shape_forge.clear_transient();
self.prop_forge.clear();
@@ -263,6 +276,33 @@ impl KernelCore {
let cap = self.config.perm_interner_max_entries?;
(self.perm_interner.entry_count() > cap).then_some(cap.next_power_of_two().saturating_mul(2))
}
/// 边界不变量守卫计数:登记一个 Vm 出生(`Vm` 两条构造路径完整构造后调用)。
pub fn note_vm_started(&self) {
self.active_vms.fetch_add(1, Ordering::Relaxed);
}
/// 边界不变量守卫计数:注销一个 Vm 死亡(`Drop for Vm` 恰好调用一次)。
pub fn note_vm_ended(&self) {
self.active_vms.fetch_sub(1, Ordering::Relaxed);
}
/// 读取当前持有本 kernel 的存活 Vm 数;release 宿主可在 id 空间复位边界自检。
pub fn active_vms(&self) -> usize {
self.active_vms.load(Ordering::Relaxed)
}
}
impl Drop for KernelCore {
fn drop(&mut self) {
// kernel 能 drop 说明持 Arc 的 Vm 已全部归零;计数非零 = 纯计数漂移
// (note_vm_started/note_vm_ended 配对挂接回归探测)。
debug_assert_eq!(
self.active_vms.load(Ordering::Relaxed),
0,
"KernelCore dropped with live VMs (note_vm_ended counter drift)"
);
}
}
/// 每会话可变状态:内置原型对象与全局对象。
+23 -1
View File
@@ -159,16 +159,21 @@ impl ShapeForge {
self.len() == 0
}
/// 清空所有非根 shape、transition 与 position 缓存,id 计数器复位。
/// 清空所有非根 shape、transition、position 缓存与 overflow 态,id 计数器复位。
///
/// 用于 session 重建:丢弃一次性对象产生的形状,只保留跨 session 共享的根节点。
/// overflow 态一并复位(不清则旧 overflow 条目把 key 映射回已被截断的 id,
/// 造成静默错深度与去重失效),id 空间完整复位。
pub fn clear_transient(&self) {
let mut shapes = self.shapes.write().unwrap();
if shapes.len() > 1 {
shapes.truncate(1);
}
drop(shapes);
self.transitions.clear();
self.positions.clear();
self.overflow_active.store(false, Ordering::Relaxed);
self.overflow_map.write().unwrap().clear();
self.next_id.store(2, Ordering::Relaxed);
}
@@ -320,6 +325,23 @@ mod tests {
assert_eq!(forge.shapes.read().unwrap().len(), 1);
}
/// overflow 态复位钉:曾触发 overflow 后清空,overflow 标志与映射须一并清空,
/// 后续 make_shape 从新空间取小 id(而非旧 overflow 映射 id)。
#[test]
fn clear_transient_resets_overflow_state() {
let forge = ShapeForge::new();
let key = ShapeForge::pack_key(EMPTY_SHAPE_ID, 1_000_500);
forge.overflow_active.store(true, Ordering::Relaxed);
forge.overflow_map.write().unwrap().insert(key, 0xDEAD);
forge.clear_transient();
assert!(!forge.overflow_active.load(Ordering::Relaxed));
assert!(forge.overflow_map.read().unwrap().is_empty());
let id = forge.make_shape(EMPTY_SHAPE_ID, 1_000_500);
assert_eq!(id, 2);
}
#[test]
fn two_branches_share_ancestor() {
let forge = ShapeForge::new();
+2
View File
@@ -1802,6 +1802,8 @@ fn run_tests() -> bool {
if tests_since_kernel_reset >= kernel_batch {
// 重建即全新 forge(结构性必清):旧核连同其表整体丢弃,
// 先 sweep 是对 doomed 核白做一次 O(50k) 清理。
// 重建边界契约:旧核上无存活 VM(VM 每测试局部,此处作用域外)。
debug_assert!(kernel.active_vms() == 0, "kernel rebuild requires no live VMs");
kernel = build_runner_kernel();
tests_since_kernel_reset = 0;
} else if done % 500 == 0 {
+2
View File
@@ -571,6 +571,8 @@ pub struct Vm {
impl Drop for Vm {
fn drop(&mut self) {
// 边界守卫计数:与构造器登记恰好配对(Rust 所有权保证恰好一次)。
self.kernel_core.note_vm_ended();
// 直接 drop(test262 每测试新建即弃)不经 reset/full_reset 路径:
// 统一收尾释放全部 session 堆数据与内建原型属性区,防逐测试累积泄漏。
self.teardown_intrinsic_protos();
+36
View File
@@ -145,6 +145,8 @@ impl Vm {
vm.init_async_generator_intrinsics();
// Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。
vm.session.record_snapshot();
// 边界守卫计数:VM 完整构造后登记,与 `Drop for Vm` 的注销恰好配对。
vm.kernel_core.note_vm_started();
vm_info!("Vm created");
vm
}
@@ -260,6 +262,8 @@ impl Vm {
vm.init_async_generator_intrinsics();
// Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。
vm.session.record_snapshot();
// 边界守卫计数:VM 完整构造后登记,与 `Drop for Vm` 的注销恰好配对。
vm.kernel_core.note_vm_started();
vm_info!("Vm created (pool)");
vm
}
@@ -820,6 +824,38 @@ mod tests {
vm.run(&module).expect("vm run failed")
}
/// 计数往返:两条构造路径(独立核/共享核)的登记与 Drop 注销恰好配对,
/// 全部 drop 后计数归零,kernel 可干净 drop(Drop 断言无残留)。
#[test]
fn active_vms_count_roundtrip() {
let vm = Vm::new();
assert_eq!(vm.kernel_core.active_vms(), 1);
drop(vm);
let core = KernelCore::new(KernelConfig::minimal());
assert_eq!(core.active_vms(), 0);
let v1 = Vm::with_kernel_core(Arc::clone(&core));
let v2 = Vm::with_kernel_core(Arc::clone(&core));
let v3 = Vm::with_kernel_core(Arc::clone(&core));
assert_eq!(core.active_vms(), 3);
drop(v3);
assert_eq!(core.active_vms(), 2);
drop(v1);
drop(v2);
assert_eq!(core.active_vms(), 0);
drop(core);
}
/// 守卫判别:持活 VM 调 sweep 触发 debug_assert。VM 声明在 kernel 之后,
/// panic unwind 时先注销计数再 drop kernel,drop 断言不受干扰。
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "no live VMs"))]
fn sweep_runner_forges_rejects_live_vm() {
let core = KernelCore::new(KernelConfig::minimal());
let _vm = Vm::with_kernel_core(Arc::clone(&core));
core.sweep_runner_forges();
}
#[test]
fn full_reset_with_session_objects_forces_global_rebuild() {
let mut vm = Vm::new();