Skip to content

V8Runtime初始化

追踪Code Mode的进程级V8初始化、sandbox-enabled链接、runtime thread、isolate/context、globals、主模块与Promise循环。

基于rust-v0.150.0
CodexRustExecutionCodeModeV8

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

rust
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

rust
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

rust
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

rust
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

rust
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

rust
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

rust
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

rust
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

rust
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_termination
  • codex-rs/code-mode-runtime/src/runtime/mod.rs :: spawn_supervised_runtime_thread
rust
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_disabled
  • codex-rs/code-mode-runtime/src/runtime/mod.rs :: linked_v8_has_sandbox_enabled、terminate_execution_stops_cpu_bound_module
  • codex-rs/code-mode-runtime/src/service_tests.rs :: global_scope_contains_only_allowed_items、v8_console_is_not_exposed_on_global_this
text
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. 源码排查 ​

text
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。