fix: test262 runner 进程 RSS 无界增长——session 收尾统一释放内建裸堆区/upvalue 别名 + 每测试分配上限 256MiB(任务 116)

- 泄漏修复:KernelSession 收尾(teardown_intrinsic_protos/free_session_upvalues)
  释放内建对象 Box::into_raw 裸堆区(release_raw_heap 四区置空幂等)与
  session upvalue 原件/晋升克隆别名去重释放;bindings 原生盒负载边、
  session_gc 边记录、vm_props 属性区同步收尾
- 失控测试封顶:runner kernel max_alloc_bytes=256MiB(dispatch 采样口径
  run_alloc_bytes/run_alloc_bytes_full),超限计 memory-limit 失败而非拖垮
  进程 RSS(arena 高水位 GB 级 × 多 worker = 宿主 OOM)
- 归属:RSS 泄漏 pre-existing(HEAD 无 WIP 同现);identifiers escaped
  unicode 挂死族另立 B132(pre-existing,supervise 超时自愈)
- 验证:build/clippy/四 crate 测试全绿零新增告警;language 前 5000 测试
  单窗峰值 330MB(旧 ~4.5MB/测试 单调增长→88GB OOM 签名消失),
  窗口间峰值 0.17-1.6GB 有界、逐测试释放回基线
This commit is contained in:
2026-09-11 03:55:59 +00:00
parent a5258f2739
commit 227dfe008f
19 changed files with 566 additions and 100 deletions
@@ -117,7 +117,10 @@ pub fn bind_async_disposable_stack(core: &Arc<KernelCore>, session: &KernelSessi
ctor.set_data_meta(0u32, PropAttributes::new(false, false, false));
ctor.set_data_meta(1u32, PropAttributes::new(false, false, true));
ctor.set_data_meta(2u32, PropAttributes::new(false, false, true));
JsValue::from_js_object(Box::into_raw(ctor))
let ctor_ptr = Box::into_raw(ctor);
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
world.track_leaked_object(ctor_ptr);
JsValue::from_js_object(ctor_ptr)
};
write_proto_constructor(core, proto, ctor_val);
@@ -135,7 +135,10 @@ pub fn bind_disposable_stack(core: &Arc<KernelCore>, session: &KernelSession, gl
ctor.set_data_meta(0u32, PropAttributes::new(false, false, false));
ctor.set_data_meta(1u32, PropAttributes::new(false, false, true));
ctor.set_data_meta(2u32, PropAttributes::new(false, false, true));
JsValue::from_js_object(Box::into_raw(ctor))
let ctor_ptr = Box::into_raw(ctor);
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
world.track_leaked_object(ctor_ptr);
JsValue::from_js_object(ctor_ptr)
};
write_proto_constructor(core, proto, ctor_val);
@@ -43,6 +43,8 @@ fn bind_error_subtype_constructor(
ctor.set_data_meta(2u32, PropAttributes::new(false, false, true));
let ctor_ptr = Box::into_raw(ctor);
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
session.builtin_world().track_leaked_object(ctor_ptr);
let proto = unsafe { &mut *proto_ptr };
let proto_ctor_shape = sh.make_shape(proto.shape_id(), sf.intern("constructor").0);
@@ -34,5 +34,8 @@ pub fn bind_iterator(core: &Arc<KernelCore>, session: &KernelSession, global: &m
&[("from", oxide_builtins::iterator::iterator_from::<crate::vm::Vm> as *const (), 1)],
);
bind_global_value(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator)));
let iterator_ptr = Box::into_raw(iterator);
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
session.builtin_world().track_leaked_object(iterator_ptr);
bind_global_value(core, global, "Iterator", JsValue::from_js_object(iterator_ptr));
}
+4 -1
View File
@@ -62,5 +62,8 @@ pub fn bind_reflect(core: &Arc<KernelCore>, session: &KernelSession, global: &mu
],
);
bind_global_value(core, global, "Reflect", JsValue::from_js_object(Box::into_raw(reflect)));
let reflect_ptr = Box::into_raw(reflect);
// 登记进 world 释放表:session 收尾统一释放对象本体与属性区。
session.builtin_world().track_leaked_object(reflect_ptr);
bind_global_value(core, global, "Reflect", JsValue::from_js_object(reflect_ptr));
}
+25 -12
View File
@@ -129,14 +129,15 @@ pub(crate) fn apply_binding_table(
/// 2. 为属性名开 shape 槽位并写入访问器 meta。
///
/// # 注意事项
/// getter 函数对象经 `Box::into_raw` 持有,与 `bind_method` 的方法 wrapper 同一生命周期约定
/// (内置对象在 session 生命周期内不被回收)。
/// getter 函数对象登记进 world 释放表,与 `bind_method` 的方法 wrapper 同一生命周期约定
/// (session 收尾时统一释放)。
pub(crate) fn bind_accessor_getter(
core: &Arc<KernelCore>, session: &KernelSession, proto: &mut JsObject, name: &str, getter_fn: *const (),
) {
let shape_forge = core.shape_forge().as_ref();
let string_forge = core.perm_interner().as_ref();
let fn_proto_val = JsValue::from_js_object(session.builtin_world().function_proto.as_ptr() as *mut JsObject);
let world = session.builtin_world();
let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject);
let getter_name = format!("get {name}");
let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
@@ -161,7 +162,9 @@ pub(crate) fn bind_accessor_getter(
let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
getter.set_data_meta(length_pos, PropAttributes::new(false, false, true));
let getter_val = JsValue::from_js_object(Box::into_raw(getter));
let getter_ptr = Box::into_raw(getter);
world.track_leaked_object(getter_ptr);
let getter_val = JsValue::from_js_object(getter_ptr);
let si = string_forge.intern(name).0;
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
@@ -180,8 +183,8 @@ pub(crate) fn bind_accessor_getter(
/// 2. 为键开 shape 槽位并写入访问器 meta(enumerable=false, configurable=true)。
///
/// # 注意事项
/// getter/setter 函数对象经 `Box::into_raw` 持有,与 `bind_accessor_getter` 的
/// 方法 wrapper 同一生命周期约定(内置对象在 session 生命周期内不被回收)。
/// getter/setter 函数对象登记进 world 释放表,session 收尾时统一释放,
/// 与 `bind_accessor_getter` 的 getter 及方法 wrapper 同一生命周期约定。
#[expect(clippy::too_many_arguments)]
pub(crate) fn bind_accessor_getset(
core: &Arc<KernelCore>, session: &KernelSession, proto: &mut JsObject, key: u32, getter_name: &str,
@@ -189,7 +192,8 @@ pub(crate) fn bind_accessor_getset(
) {
let shape_forge = core.shape_forge().as_ref();
let string_forge = core.perm_interner().as_ref();
let fn_proto_val = JsValue::from_js_object(session.builtin_world().function_proto.as_ptr() as *mut JsObject);
let world = session.builtin_world();
let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject);
let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
getter.set_function(true);
@@ -211,7 +215,9 @@ pub(crate) fn bind_accessor_getset(
getter.ensure_hash_props().push(JsValue::int(0));
let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
getter.set_data_meta(length_pos, PropAttributes::new(false, false, true));
let getter_val = JsValue::from_js_object(Box::into_raw(getter));
let getter_ptr = Box::into_raw(getter);
world.track_leaked_object(getter_ptr);
let getter_val = JsValue::from_js_object(getter_ptr);
let mut setter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
setter.set_function(true);
@@ -230,7 +236,9 @@ pub(crate) fn bind_accessor_getset(
setter.ensure_hash_props().push(JsValue::int(1));
let length_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
setter.set_data_meta(length_pos, PropAttributes::new(false, false, true));
let setter_val = JsValue::from_js_object(Box::into_raw(setter));
let setter_ptr = Box::into_raw(setter);
world.track_leaked_object(setter_ptr);
let setter_val = JsValue::from_js_object(setter_ptr);
// 访问器属性槽:enumerable=false、configurable=true(get/set 函数对象已持有)。
let new_shape = shape_forge.make_shape(proto.shape_id(), key);
@@ -301,7 +309,7 @@ pub(crate) fn bind_well_known_method(
method_name,
fn_ptr,
nargs,
world.fn_proto_val(),
world,
);
}
@@ -617,6 +625,7 @@ fn bind_error_subtype_global(
ctor.set_data_meta(1u32, PropAttributes::new(false, false, true));
let ctor_ptr = Box::into_raw(ctor);
session.builtin_world().track_leaked_object(ctor_ptr);
bind_existing_global(core, global, name, JsValue::from_js_object(ctor_ptr));
}
@@ -702,7 +711,9 @@ fn bind_reflect_global(core: &Arc<KernelCore>, session: &KernelSession, global:
),
],
);
bind_existing_global(core, global, "Reflect", JsValue::from_js_object(Box::into_raw(reflect)));
let reflect_ptr = Box::into_raw(reflect);
session.builtin_world().track_leaked_object(reflect_ptr);
bind_existing_global(core, global, "Reflect", JsValue::from_js_object(reflect_ptr));
}
fn bind_iterator_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
@@ -722,7 +733,9 @@ fn bind_iterator_global(core: &Arc<KernelCore>, session: &KernelSession, global:
core,
&[("from", oxide_builtins::iterator::iterator_from::<crate::vm::Vm> as *const (), 1)],
);
bind_existing_global(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator)));
let iterator_ptr = Box::into_raw(iterator);
session.builtin_world().track_leaked_object(iterator_ptr);
bind_existing_global(core, global, "Iterator", JsValue::from_js_object(iterator_ptr));
// 迭代器原型方法(%IteratorPrototype% 的 @@iterator 与各集合原型 next)由
// `bind_iterator_protos` 在 object 家族重建时统一安装,这里不重复绑定,
// 避免保留原型经 dirty reset 时属性槽膨胀。