V8Runtime初始化
Code Mode 的 V8 初始化有两个时间尺度:进程级 initialize_v8 只执行一次并固定 JIT 模式;每个 cell 又在独立 runtime thread 中创建 isolate/context,安装受限 globals、module callbacks 和 RuntimeState,再编译/执行主 ES module。前者决定 V8 平台,后者决定一次 cell 的 JavaScript 世界。当前 runtime 链接 sandbox-enabled V8,但这指 V8 内存 sandbox,不等于 Codex 工具权限 sandbox。
本文承接CodeModeHost生命周期、CodeMode协议和CodeMode架构总览,面向理解 V8 isolate、Promise、microtask 和 Rust callback 的读者。范围是初始化与首次 module evaluation,不展开 CellActor 队列和每个 helper 的业务语义。当前 Apple Silicon 缺少 rusty_v8 v150.4.0 预编译 archive,动态测试不能作为本篇已验证事实;正文严格区分源码事实与测试边界。
1. 进程级平台
1.1 OnceLock
V8_INITIALIZATION 保存平台和 JIT mode。第一次调用执行 ICU、JIT flag、platform 和 V8 initialize;后续请求不同 mode 直接返回错误,因为 V8 不能在初始化后切换 JIT。
源码位置:codex-rs/code-mode-runtime/src/v8_init.rs :: initialize_v8、ensure_v8_initialized
static V8_INITIALIZATION: OnceLock<Result<V8Initialization, String>> = OnceLock::new();
pub fn initialize_v8(jit_mode: V8JitMode) -> Result<(), String> {
match V8_INITIALIZATION.get_or_init(|| initialize_v8_with_mode(jit_mode)) {
Ok(initialization) if initialization.jit_mode == jit_mode => Ok(()),
Ok(initialization) => Err(format!(
"V8 was already initialized with JIT {}",
initialization.jit_mode.description()
)),
Err(error_text) => Err(error_text.clone()),
}
}1.2 初始化顺序
ICU 必须先加载;Disabled mode 设置 --jitless,随后创建 shared platform、调用 initialize_platform 和 initialize。平台对象保存在 V8Initialization 中,直到进程结束。
源码位置:codex-rs/code-mode-runtime/src/v8_init.rs :: initialize_v8_with_mode
v8::icu::set_common_data_77(deno_core_icudata::ICU_DATA)
.map_err(|error_code| format!("failed to initialize ICU data: {error_code}"))?;
match jit_mode {
V8JitMode::Enabled => {}
V8JitMode::Disabled => v8::V8::set_flags_from_string("--jitless"),
}
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform.clone());
v8::V8::initialize();runtime 还包含链接时检查:调用 rusty_v8 暴露的 v8__V8__IsSandboxEnabled,要求当前库启用 V8 pointer compression sandbox。它验证 V8 自身的内存隔离构建选项,不验证 JavaScript nested tool 的审批或 filesystem 访问。
源码位置:codex-rs/code-mode-runtime/src/runtime/mod.rs :: linked_v8_has_sandbox_enabled
2. Runtime thread
spawn_runtime 先 ensure_v8_initialized,再建立 data command、control command、enabled tool metadata 和 runtime config,最后启动受监督 OS thread。isolate handle 通过容量 1 的 sync channel 返回;因此 caller 拿到 handle 时 isolate 已创建,可以从 CellActor 线程安全地调用 terminate_execution。
源码位置:codex-rs/code-mode-runtime/src/runtime/mod.rs :: spawn_runtime、spawn_supervised_runtime_thread
ensure_v8_initialized()?;
let (command_tx, command_rx) = std_mpsc::channel();
let (control_tx, control_rx) = std_mpsc::channel();
let (isolate_handle_tx, isolate_handle_rx) = std_mpsc::sync_channel(1);
let enabled_tools = request
.enabled_tools
.iter()
.map(enabled_tool_metadata)
.collect::<Vec<_>>();
spawn_supervised_runtime_thread(event_tx.clone(), task_failure_handler, move || {
run_runtime(config, event_tx, command_rx, control_rx,
pending_mode, isolate_handle_tx, runtime_command_tx);
});panic 被 catch_unwind,转换为 task failure event;runtime thread 未能发送 isolate handle 时,spawn_runtime 返回初始化错误。
data 与 control channel 分离是为了暂停 frontier:普通 ToolResponse/Timeout 进入 data channel,Resume/ Continue/Terminate 进入 control channel。PauseUntilResumed 模式发现暂时无 data command 时先发送 RuntimeEvent::Pending,再阻塞 control command;不会让 V8 在 yielded cell 后自行继续。
源码位置:codex-rs/code-mode-runtime/src/runtime/mod.rs :: next_runtime_command、PendingRuntimeMode
3. Isolate与context
run_runtime 在 runtime thread 使用默认 v8::CreateParams 创建 isolate、发送 thread-safe handle,然后创建 ContextScope。RuntimeState 放入 scope slot,保存 pending tool calls、timeouts、已提交 store 快照、本 cell store writes、enabled tools、外层 tool call ID 和 exit flag。
源码位置:codex-rs/code-mode-runtime/src/runtime/mod.rs :: run_runtime、RuntimeState
let isolate = &mut v8::Isolate::new(v8::CreateParams::default());
let isolate_handle = isolate.thread_safe_handle();
if isolate_handle_tx.send(isolate_handle).is_err() {
return;
}
isolate.set_host_import_module_dynamically_callback(
module_loader::dynamic_import_callback,
);
v8::scope!(let scope, isolate);
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
scope.set_slot(RuntimeState {
event_tx: event_tx.clone(),
pending_tool_calls: HashMap::new(),
pending_timeouts: HashMap::new(),
stored_values: config.stored_values,
stored_value_writes: HashMap::new(),
enabled_tools: config.enabled_tools,
next_tool_call_id: 1,
next_timeout_id: 1,
tool_call_id: config.tool_call_id,
runtime_command_tx,
exit_requested: false,
});3.1 Heap限制边界
domain/gRPC/V1 协议都能携带 max_heap_size_bytes,但当前 in-process facade 在构造 session 时将该字段 重置为 None,run_runtime 也没有把它写入 CreateParams。因此现阶段实际执行的是 yield-time limit, 不是 per-cell V8 heap limit;协议字段属于已预留但尚未贯通的能力。
源码位置:codex-rs/code-mode-runtime/src/service.rs :: InProcessCodeModeSession::with_delegate_and_limits
Self {
runtime: SessionRuntime::new(Arc::new(ProtocolDelegate { delegate })),
cell_execution_limits: CodeModeSessionCellExecutionLimits {
max_heap_size_bytes: None,
..cell_execution_limits
},
}4. Globals与权限
install_globals 删除 console、Atomics、SharedArrayBuffer 和 WebAssembly,再注入 tools、ALL_TOOLS、 clearTimeout/setTimeout、text/image/audio/generatedImage、store/load、notify、yield_control 和 exit。工具函数 来自 enabled-tools metadata,闭包 data 只保存 tool index,再由 callback 回查 RuntimeState。JavaScript 的 eval、Function、Promise 等普通语言能力仍存在,因此这不是通用“不可信 JS sandbox”;权限边界位于 nested delegate。
源码位置:codex-rs/code-mode-runtime/src/runtime/globals.rs :: install_globals、build_tools_object
delete_global(scope, global, "console")?;
delete_global(scope, global, "Atomics")?;
delete_global(scope, global, "SharedArrayBuffer")?;
delete_global(scope, global, "WebAssembly")?;
let tools = build_tools_object(scope)?;
let all_tools = build_all_tools_value(scope)?;
set_global(scope, global, "tools", tools.into())?;
set_global(scope, global, "ALL_TOOLS", all_tools)?;
set_global(scope, global, "clearTimeout", clear_timeout.into())?;
set_global(scope, global, "setTimeout", set_timeout.into())?;
set_global(scope, global, "text", text.into())?;
set_global(scope, global, "image", image.into())?;
set_global(scope, global, "audio", audio.into())?;
set_global(scope, global, "generatedImage", generated_image.into())?;
set_global(scope, global, "store", store.into())?;
set_global(scope, global, "load", load.into())?;
set_global(scope, global, "notify", notify.into())?;
set_global(scope, global, "yield_control", yield_control.into())?;
set_global(scope, global, "exit", exit.into())?;删除危险或不需要的全局不是 sandbox 的替代品;真实工具权限仍由 Core delegate 和 runtime approval 决定。
5. Module与Promise
主 source 被当作名为 exec_main.mjs 的 ES module 编译、instantiate、evaluate;evaluate 结果如果是 Promise, 就保存为 Global Promise。静态与动态 import callback 当前都调用同一个 resolver,而 resolver 总是抛出 Unsupported import in exec,所以 module loader 不提供 filesystem/network/module registry。之后每次 RuntimeCommand 都执行 microtask checkpoint,并重新检查顶层 Promise。
源码位置:codex-rs/code-mode-runtime/src/runtime/module_loader.rs :: evaluate_main_module、resolve_module、completion_state
let module = v8::script_compiler::compile_module(&tc, &mut source)
.ok_or_else(|| "unknown code mode exception".to_string())?;
module.instantiate_module(&tc, resolve_module_callback)
.ok_or_else(|| "failed to instantiate module".to_string())?;
let result = module.evaluate(&tc).ok_or_else(|| {
"unknown code mode exception".to_string()
})?;
tc.perform_microtask_checkpoint();
if result.is_promise() {
return Ok(Some(v8::Global::new(&tc, result.try_into()?)));
}源码位置:codex-rs/code-mode-runtime/src/runtime/module_loader.rs :: resolve_module
fn resolve_module<'s>(
scope: &mut v8::PinScope<'s, '_>,
specifier: &str,
) -> Option<v8::Local<'s, v8::Module>> {
if let Some(message) =
v8::String::new(scope, &format!("Unsupported import in exec: {specifier}"))
{
scope.throw_exception(message.into());
} else {
scope.throw_exception(v8::undefined(scope).into());
}
None
}6. Command循环
主 Promise pending 时,runtime 从 data/control channel 取得命令。ToolResponse 将 JSON 转为 V8 value 并 resolve 对应 PromiseResolver;ToolError 用字符串 reject;TimeoutFired 调 timer callback; ObservePendingFrontier 只触发一次 microtask 检查。每条命令后执行 checkpoint,再读取顶层 Promise 状态。
源码位置:codex-rs/code-mode-runtime/src/runtime/mod.rs :: run_runtime command loop
match command {
RuntimeCommand::Terminate => break,
RuntimeCommand::ToolResponse { id, result } => {
module_loader::resolve_tool_response(scope, &id, Ok(result))?;
}
RuntimeCommand::ToolError { id, error_text } => {
module_loader::resolve_tool_response(scope, &id, Err(error_text))?;
}
RuntimeCommand::TimeoutFired { id } => {
timers::invoke_timeout_callback(scope, id)?;
}
RuntimeCommand::ObservePendingFrontier => {}
}
scope.perform_microtask_checkpoint();completion_state 在 fulfilled 时返回本 cell 的 stored-value writes;rejected 时把 V8 value 转成 error text。 exit() 通过设置 exit_requested 并抛出私有 sentinel 实现,module loader 识别该组合后把它当作正常完成, 而不是把任意同名字符串都吞掉。
7. 终止与崩溃
普通 RuntimeCommand::Terminate 只能在 command loop 得到调度时退出。CPU-bound JavaScript 没有机会读 channel,因此 CellActor 还持有 v8::IsolateHandle,终止时调用 terminate_execution() 打断 isolate。 runtime thread 外层 catch_unwind 将 panic 上报给 owner failure handler,并发送 ThreadPanicked,避免 actor 永久等待。
源码位置:
codex-rs/code-mode-runtime/src/cell_actor/mod.rs::begin_terminationcodex-rs/code-mode-runtime/src/runtime/mod.rs::spawn_supervised_runtime_thread
fn begin_termination(
runtime_tx: &Sender<RuntimeCommand>,
runtime_control_tx: &Sender<RuntimeControlCommand>,
runtime_terminate_handle: &v8::IsolateHandle,
cancellation_token: &CancellationToken,
) {
cancellation_token.cancel();
let _ = runtime_tx.send(RuntimeCommand::Terminate);
let _ = runtime_control_tx.send(RuntimeControlCommand::Terminate);
let _ = runtime_terminate_handle.terminate_execution();
}8. 验证边界
Code Mode runtime service tests 依赖 V8;当前 Apple Silicon 目标的 rusty_v8 v150.4.0 archive 下载 404,动态初始化和 globals 测试无法进入断言。纯协议测试可证明 wire contract,但不能证明 isolate 初始化成功。
源码位置:
codex-rs/code-mode-runtime/tests/jit.rs::code_mode_runs_with_jit_disabledcodex-rs/code-mode-runtime/src/runtime/mod.rs::linked_v8_has_sandbox_enabled、terminate_execution_stops_cpu_bound_modulecodex-rs/code-mode-runtime/src/service_tests.rs::global_scope_contains_only_allowed_items、v8_console_is_not_exposed_on_global_this
cd codex-rs
cargo test -p codex-code-mode-runtime --test jit -- --test-threads=1
cargo test -p codex-code-mode-runtime --lib linked_v8_has_sandbox_enabled -- --test-threads=1
cargo test -p codex-code-mode-runtime --lib terminate_execution_stops_cpu_bound_module -- --test-threads=1
cargo test -p codex-code-mode-runtime --lib global_scope_contains_only_allowed_items -- --test-threads=1
cargo test -p codex-code-mode-runtime --lib v8_console_is_not_exposed_on_global_this -- --test-threads=1这些测试分别检查 JIT mode 固定、linked V8 sandbox flag、CPU-bound termination、global allowlist 和 console 删除。构建未完成时,源码审阅不能替代这些动态断言;尤其不能据协议中的 heap 字段推断 isolate 已经限制内存。
9. 源码排查
rg -n "V8_INITIALIZATION|initialize_v8_with_mode|ensure_v8_initialized" codex-rs/code-mode-runtime/src/v8_init.rs
rg -n "spawn_runtime|run_runtime|RuntimeState|perform_microtask_checkpoint" codex-rs/code-mode-runtime/src/runtime
rg -n "install_globals|delete_global|build_tools_object|generatedImage" codex-rs/code-mode-runtime/src/runtime/globals.rs
rg -n "resolve_module|evaluate_main_module|completion_state|Unsupported import" codex-rs/code-mode-runtime/src/runtime/module_loader.rs初始化主线是:OnceLock 固定进程级 V8 模式,runtime thread 创建 isolate/context,scope slot 保存 cell 状态,globals 投影 nested 能力,主 module 在禁止 import 的 resolver 下 evaluate,command loop 驱动 Promise 和 microtask,IsolateHandle 负责抢占式终止。下一篇CellActor与执行队列 将分析 observer、yield frontier、callback task 和 completion commit。
