本地与远程执行Backend
Codex 不在上层代码里分别维护“本地进程 API”和“远程进程 API”。Environment 选择 concrete backend,ExecBackend 只规定 start,返回的 ExecProcess 再统一 read、write、signal、terminate 和事件订阅。差异集中在连接建立、路径解释、sandbox 能力和断线恢复,而不是调用方的接口形状。
本文承接ExecServer架构、ExecServer连接与握手和ExecServer进程RPC,面向理解 Rust trait、Arc 和异步 transport 的读者。范围是 local/remote backend 的选择与契约,不重复 process RPC 字段和握手消息。测试使用同一组断言分别运行 local 与 remote,用于识别真正共享的保证和不能外推的差异。
1. Environment选择
EnvironmentManager 保存 concrete environments,local environment 由本地 runtime paths 构造,remote environment 由 provider/transport 配置构造。调用方先选 environment,再取得 execution/filesystem/http backend。Environment 将三种能力放在同一个具体环境对象中,确保它们共享同一 remote connection/readiness 语义。
源码位置:codex-rs/exec-server/src/environment.rs :: EnvironmentManager
pub struct EnvironmentManager {
default_environment: Option<String>,
pub(super) environments: RwLock<HashMap<String, Arc<Environment>>>,
local_environment: Option<Arc<Environment>>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
http_client_factory: HttpClientFactory,
}2. 统一Backend契约
2.1 start返回
ExecBackend 只要求 start 返回 StartedExecProcess;sandbox type 是可选元数据,旧 peer 不报告时为 None。调用方不需要知道 child 是在当前进程还是另一台 executor 上创建。
源码位置:codex-rs/exec-server/src/process.rs :: ExecBackend、StartedExecProcess
pub struct StartedExecProcess {
pub process: Arc<dyn ExecProcess>,
pub sandbox_type: Option<SandboxType>,
}
pub trait ExecBackend: Send + Sync {
fn start(&self, params: ExecParams) -> ExecBackendFuture<'_>;
}2.2 process操作
local 与 remote 都返回 ExecProcess,共享 read/write/signal/terminate 和 subscribe_events。事件 Lagged 时,双方都应通过 read 的 sequence 恢复。
源码位置:codex-rs/exec-server/src/process.rs :: ExecProcess
pub trait ExecProcess: Send + Sync {
fn process_id(&self) -> &ProcessId;
fn subscribe_events(&self) -> ExecProcessEventReceiver;
fn read(&self, after_seq: Option<u64>, max_bytes: Option<usize>, wait_ms: Option<u64>)
-> ExecProcessFuture<'_, ReadResponse>;
fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>;
fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()>;
fn terminate(&self) -> ExecProcessFuture<'_, ()>;
}3. 本地实现
local backend 是 LocalProcess,在 exec-server 进程内维护 process map,直接调用 codex_sandboxing::spawn_process;返回 LocalExecProcess 作为 trait adapter。
源码位置:codex-rs/exec-server/src/local_process.rs :: LocalProcess::start
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError> {
let (response, wake_tx, events) = self.start_process(params).await
.map_err(map_handler_error)?;
let sandbox_type = sandbox_type_from_protocol(response.sandbox_type);
Ok(StartedExecProcess {
process: Arc::new(LocalExecProcess {
process_id: response.process_id,
backend: self.clone(),
wake_tx,
events,
}),
sandbox_type,
})
}本地失败主要来自 argv/cwd/sandbox spawn 和本地资源;没有 transport reconnect 这一层。
4. 远程实现
remote backend 通过 environment 的 transport/client 把同一 ExecParams 发给远端 exec-server,返回远端 process 的代理。上层仍只看到 ExecProcess,但断线会表现为 transport failure;恢复由 LazyRemoteExecServerClient 的 reconnect/session resume 处理,process owner 仍在 executor。
源码位置:codex-rs/exec-server/src/remote_process.rs :: RemoteProcess::start、RemoteExecProcess
async fn start(
&self,
params: ExecParams,
network_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
) -> Result<StartedExecProcess, crate::ExecServerError> {
let client = self.client.get().await?;
let session = client.start_process(params, network_policy_decider).await?;
let sandbox_type = sandbox_type_from_protocol(session.sandbox_type());
Ok(StartedExecProcess {
process: Arc::new(RemoteExecProcess { session }),
sandbox_type,
})
}该片段展示 remote process 的真实模式:连接对象负责 RPC,environment 负责选择 transport,返回的 Session 再实现 ExecProcess。HTTP、filesystem 和 process 都遵循“能力 facade + environment-owned transport”,但它们的 payload 和恢复状态各自独立。
5. 共享与差异
| 维度 | 共同契约 | 本地差异 | 远程差异 |
|---|---|---|---|
| 启动结果 | StartedExecProcess | 直接 spawn | transport/backend start |
| 进程操作 | ExecProcess | 本地 session | RPC proxy |
| 输出 | read + events | 本地 retained queue | 远端 replay/live events |
| sandbox type | optional metadata | 当前 executor 判定 | peer 报告或缺省 None |
| 失败 | typed ExecServerError | path/spawn/resource | disconnect/reconnect/protocol |
表格只描述源码已有契约,不代表 local 与 remote 的性能、延迟或所有 sandbox 能力相同。
6. 验证
6.1 同一断言
exec_process_starts_and_exits、exec_process_streams_output 和 exec_process_write_then_read 使用 use_remote: bool 参数化,同一 arrange/action/assert 分别运行 local 与 remote。它证明高层 process contract 一致,不证明 transport 实现相同。
源码位置:codex-rs/exec-server/tests/exec_process.rs :: assert_exec_process_starts_and_exits、assert_exec_process_streams_output
cd codex-rs
cargo test -p codex-exec-server --test exec_process exec_process_starts_and_exits -- --test-threads=1
cargo test -p codex-exec-server --test exec_process exec_process_streams_output -- --test-threads=1
cargo test -p codex-exec-server --test exec_process exec_process_write_then_read -- --test-threads=1
cargo test -p codex-exec-server --lib environment::tests -- --test-threads=16.2 差异边界
remote 专用测试覆盖 inherited fd 拒绝和 transport disconnect recovery;local 不适用这些断言。它们证明 remote 的额外失败边界,而不是 local 缺陷。
源码位置:codex-rs/core/src/unified_exec/mod_tests.rs :: remote_exec_server_rejects_inherited_fd_launches、codex-rs/exec-server/tests/exec_process.rs :: remote_exec_process_recovers_after_transport_disconnect
cd codex-rs
cargo test -p codex-core --lib remote_exec_server_rejects_inherited_fd_launches -- --test-threads=1
cargo test -p codex-exec-server --test exec_process remote_exec_process_recovers_after_transport_disconnect -- --test-threads=1remote_exec_server_rejects_inherited_fd_launches 需要远程测试环境;没有该 fixture 时测试会按条件跳过,不能把跳过当作远程断言已经执行。
7. 源码排查
rg -n "struct EnvironmentManager|LOCAL_ENVIRONMENT_ID|REMOTE_ENVIRONMENT_ID|get_exec_backend" codex-rs/exec-server/src/environment.rs
rg -n "trait ExecBackend|trait ExecProcess|StartedExecProcess" codex-rs/exec-server/src/process.rs
rg -n "use_remote|create_for_tests\(Some|remote_exec_server" codex-rs/exec-server/tests codex-rs/core/src/unified_exec本地与远程的共同学习主线是 trait contract;真正差异在 environment 选择、transport、路径/继承 fd、断线恢复和 sandbox 能力。下一篇ExecServer并发与集成测试将分析 request admission、并发 lane 和跨 transport 集成测试。
