Download codex-rs/exec-server/src/process.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/src/process.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/exec-server/src/process.rs
-
curl -L -o process.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/src/process.rs
10.7 kB
| use std::collections::VecDeque; | |
| use std::future::Future; | |
| use std::pin::Pin; | |
| use std::sync::Arc; | |
| use std::sync::Mutex as StdMutex; | |
| use codex_network_proxy::NetworkPolicyDecider; | |
| use codex_sandboxing::SandboxType; | |
| use tokio::sync::broadcast; | |
| use tokio::sync::watch; | |
| use crate::ExecServerError; | |
| use crate::ProcessId; | |
| use crate::protocol::ExecParams; | |
| use crate::protocol::ProcessOutputChunk; | |
| use crate::protocol::ProcessSandboxType; | |
| use crate::protocol::ProcessSignal; | |
| use crate::protocol::ReadResponse; | |
| use crate::protocol::WriteResponse; | |
| pub struct StartedExecProcess { | |
| pub process: Arc<dyn ExecProcess>, | |
| /// `None` means the exec-server peer did not report its sandbox type. | |
| pub sandbox_type: Option<SandboxType>, | |
| } | |
| pub(crate) fn sandbox_type_from_protocol( | |
| sandbox_type: Option<ProcessSandboxType>, | |
| ) -> Option<SandboxType> { | |
| match sandbox_type { | |
| None => None, | |
| Some(ProcessSandboxType::None) => Some(SandboxType::None), | |
| Some(ProcessSandboxType::MacosSeatbelt) => Some(SandboxType::MacosSeatbelt), | |
| Some(ProcessSandboxType::LinuxSeccomp) => Some(SandboxType::LinuxSeccomp), | |
| Some(ProcessSandboxType::WindowsRestrictedToken) => { | |
| Some(SandboxType::WindowsRestrictedToken) | |
| } | |
| Some(ProcessSandboxType::WindowsMxc) => Some(SandboxType::WindowsMxc), | |
| } | |
| } | |
| /// Pushed process events for consumers that want to follow process output as it | |
| /// arrives instead of polling retained output with [`ExecProcess::read`]. | |
| /// | |
| /// The stream is scoped to one [`ExecProcess`] handle. `Output` events carry | |
| /// stdout, stderr, or pty bytes. `Exited` reports the process exit status, while | |
| /// `Closed` means all output streams have ended and no more output events will | |
| /// arrive. `Failed` is used when the process session cannot continue, for | |
| /// example because the remote environment connection disconnected. | |
| pub enum ExecProcessEvent { | |
| Output(ProcessOutputChunk), | |
| Exited { | |
| seq: u64, | |
| exit_code: i32, | |
| sandbox_denied: Option<bool>, | |
| }, | |
| Closed { | |
| seq: u64, | |
| }, | |
| Failed(String), | |
| } | |
| /// Replay buffer plus live fan-out for pushed process events. | |
| /// | |
| /// New subscribers first drain a bounded replay history, then continue on the | |
| /// live broadcast channel. The history is bounded by event count and retained | |
| /// output bytes: count protects against many tiny events, while bytes protects | |
| /// against a few very large output chunks. | |
| pub(crate) struct ExecProcessEventLog { | |
| inner: Arc<ExecProcessEventLogInner>, | |
| } | |
| struct ExecProcessEventLogInner { | |
| history: StdMutex<ExecProcessEventHistory>, | |
| live_tx: broadcast::Sender<ExecProcessEvent>, | |
| event_capacity: usize, | |
| byte_capacity: usize, | |
| } | |
| struct ExecProcessEventHistory { | |
| events: VecDeque<ExecProcessEvent>, | |
| retained_bytes: usize, | |
| } | |
| impl ExecProcessEvent { | |
| /// Sequence number used to order process-owned events. | |
| /// | |
| /// `Failed` is intentionally unsequenced because it is synthesized by the | |
| /// client when the session or transport fails, not emitted by the process. | |
| pub(crate) fn seq(&self) -> Option<u64> { | |
| match self { | |
| ExecProcessEvent::Output(chunk) => Some(chunk.seq), | |
| ExecProcessEvent::Exited { seq, .. } | ExecProcessEvent::Closed { seq } => Some(*seq), | |
| ExecProcessEvent::Failed(_) => None, | |
| } | |
| } | |
| fn retained_len(&self) -> usize { | |
| match self { | |
| ExecProcessEvent::Output(chunk) => chunk.chunk.0.len(), | |
| ExecProcessEvent::Failed(message) => message.len(), | |
| ExecProcessEvent::Exited { .. } | ExecProcessEvent::Closed { .. } => 0, | |
| } | |
| } | |
| } | |
| impl ExecProcessEventLog { | |
| pub(crate) fn new(event_capacity: usize, byte_capacity: usize) -> Self { | |
| let (live_tx, _live_rx) = broadcast::channel(event_capacity); | |
| Self { | |
| inner: Arc::new(ExecProcessEventLogInner { | |
| history: StdMutex::new(ExecProcessEventHistory::default()), | |
| live_tx, | |
| event_capacity, | |
| byte_capacity, | |
| }), | |
| } | |
| } | |
| pub(crate) fn publish(&self, event: ExecProcessEvent) { | |
| let mut history = self | |
| .inner | |
| .history | |
| .lock() | |
| .unwrap_or_else(std::sync::PoisonError::into_inner); | |
| history.retained_bytes += event.retained_len(); | |
| history.events.push_back(event.clone()); | |
| while history.events.len() > self.inner.event_capacity | |
| || history.retained_bytes > self.inner.byte_capacity | |
| { | |
| let Some(evicted) = history.events.pop_front() else { | |
| break; | |
| }; | |
| history.retained_bytes = history | |
| .retained_bytes | |
| .saturating_sub(evicted.retained_len()); | |
| } | |
| let _ = self.inner.live_tx.send(event); | |
| } | |
| pub(crate) fn subscribe(&self) -> ExecProcessEventReceiver { | |
| let history = self | |
| .inner | |
| .history | |
| .lock() | |
| .unwrap_or_else(std::sync::PoisonError::into_inner); | |
| let live_rx = self.inner.live_tx.subscribe(); | |
| let replay = history.events.iter().cloned().collect(); | |
| ExecProcessEventReceiver { | |
| replay, | |
| live_rx, | |
| _keepalive: None, | |
| } | |
| } | |
| } | |
| pub struct ExecProcessEventReceiver { | |
| replay: VecDeque<ExecProcessEvent>, | |
| live_rx: broadcast::Receiver<ExecProcessEvent>, | |
| _keepalive: Option<broadcast::Sender<ExecProcessEvent>>, | |
| } | |
| impl ExecProcessEventReceiver { | |
| /// Returns a receiver that remains open without yielding events. | |
| pub fn empty() -> Self { | |
| let (live_tx, live_rx) = broadcast::channel(1); | |
| Self { | |
| replay: VecDeque::new(), | |
| live_rx, | |
| _keepalive: Some(live_tx), | |
| } | |
| } | |
| /// Returns the next replayed or live event. | |
| /// | |
| /// `Lagged` means this receiver fell behind the bounded live channel. The | |
| /// caller should recover through [`ExecProcess::read`] using the last | |
| /// delivered sequence number, then continue receiving pushed events. | |
| pub async fn recv(&mut self) -> Result<ExecProcessEvent, broadcast::error::RecvError> { | |
| if let Some(event) = self.replay.pop_front() { | |
| return Ok(event); | |
| } | |
| self.live_rx.recv().await | |
| } | |
| } | |
| /// Handle for an executor-managed process. | |
| /// | |
| /// Implementations must support both retained-output reads and pushed events: | |
| /// `read` is the request/response API for callers that want to page through | |
| /// buffered output, while `subscribe_events` is the streaming API for callers | |
| /// that want output and lifecycle changes delivered as they happen. | |
| pub trait ExecProcess: Send + Sync { | |
| fn process_id(&self) -> &ProcessId; | |
| fn subscribe_wake(&self) -> watch::Receiver<u64>; | |
| 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<'_, ()>; | |
| } | |
| pub type ExecProcessFuture<'a, T> = | |
| Pin<Box<dyn Future<Output = Result<T, ExecServerError>> + Send + 'a>>; | |
| pub trait ExecBackend: Send + Sync { | |
| fn start(&self, params: ExecParams) -> ExecBackendFuture<'_>; | |
| /// Captures a local shell snapshot without starting the requested command. | |
| /// Failures must remain retryable by real commands. Remote backends do not | |
| /// support this operation; callers should leave them on the lazy path. | |
| fn prewarm_shell_snapshot(&self, _params: ExecParams) -> ExecProcessFuture<'_, ()> { | |
| Box::pin(async { | |
| Err(ExecServerError::Protocol( | |
| "exec backend does not support shell snapshot prewarming".to_string(), | |
| )) | |
| }) | |
| } | |
| /// Starts a process with an authoritative controller-side policy decider. | |
| fn start_with_network_policy_decider( | |
| &self, | |
| _params: ExecParams, | |
| _decider: Arc<dyn NetworkPolicyDecider>, | |
| ) -> ExecBackendFuture<'_> { | |
| Box::pin(async { | |
| Err(ExecServerError::Protocol( | |
| "exec backend does not support remote network policy decisions".to_string(), | |
| )) | |
| }) | |
| } | |
| } | |
| pub type ExecBackendFuture<'a> = | |
| Pin<Box<dyn Future<Output = Result<StartedExecProcess, ExecServerError>> + Send + 'a>>; | |
| mod tests { | |
| use pretty_assertions::assert_eq; | |
| use tokio::time::Duration; | |
| use tokio::time::timeout; | |
| use super::ExecProcessEvent; | |
| use super::ExecProcessEventLog; | |
| use super::ExecProcessEventReceiver; | |
| use crate::protocol::ExecOutputStream; | |
| use crate::protocol::ProcessOutputChunk; | |
| async fn empty_event_receiver_stays_open() { | |
| let mut events = ExecProcessEventReceiver::empty(); | |
| assert!( | |
| timeout(Duration::from_millis(10), events.recv()) | |
| .await | |
| .is_err() | |
| ); | |
| } | |
| async fn event_history_replay_is_bounded_by_retained_bytes() { | |
| let log = ExecProcessEventLog::new(/*event_capacity*/ 8, /*byte_capacity*/ 3); | |
| log.publish(ExecProcessEvent::Output(ProcessOutputChunk { | |
| seq: 1, | |
| stream: ExecOutputStream::Stdout, | |
| chunk: b"large".to_vec().into(), | |
| })); | |
| log.publish(ExecProcessEvent::Exited { | |
| seq: 2, | |
| exit_code: 0, | |
| sandbox_denied: Some(false), | |
| }); | |
| log.publish(ExecProcessEvent::Closed { seq: 3 }); | |
| let mut events = log.subscribe(); | |
| let replay = vec![ | |
| timeout(Duration::from_secs(1), events.recv()) | |
| .await | |
| .expect("exit event replay should not time out") | |
| .expect("exit event replay should be available"), | |
| timeout(Duration::from_secs(1), events.recv()) | |
| .await | |
| .expect("closed event replay should not time out") | |
| .expect("closed event replay should be available"), | |
| ]; | |
| assert_eq!( | |
| replay, | |
| vec![ | |
| ExecProcessEvent::Exited { | |
| seq: 2, | |
| exit_code: 0, | |
| sandbox_denied: Some(false), | |
| }, | |
| ExecProcessEvent::Closed { seq: 3 }, | |
| ] | |
| ); | |
| } | |
| } | |