| 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>, |
| |
| 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), |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub enum ExecProcessEvent { |
| Output(ProcessOutputChunk), |
| Exited { |
| seq: u64, |
| exit_code: i32, |
| sandbox_denied: Option<bool>, |
| }, |
| Closed { |
| seq: u64, |
| }, |
| Failed(String), |
| } |
|
|
| |
| |
| |
| |
| |
| |
| #[derive(Clone)] |
| pub(crate) struct ExecProcessEventLog { |
| inner: Arc<ExecProcessEventLogInner>, |
| } |
|
|
| struct ExecProcessEventLogInner { |
| history: StdMutex<ExecProcessEventHistory>, |
| live_tx: broadcast::Sender<ExecProcessEvent>, |
| event_capacity: usize, |
| byte_capacity: usize, |
| } |
|
|
| #[derive(Default)] |
| struct ExecProcessEventHistory { |
| events: VecDeque<ExecProcessEvent>, |
| retained_bytes: usize, |
| } |
|
|
| impl ExecProcessEvent { |
| |
| |
| |
| |
| 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 { |
| |
| pub fn empty() -> Self { |
| let (live_tx, live_rx) = broadcast::channel(1); |
| Self { |
| replay: VecDeque::new(), |
| live_rx, |
| _keepalive: Some(live_tx), |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| 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 |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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<'_>; |
|
|
| |
| |
| |
| fn prewarm_shell_snapshot(&self, _params: ExecParams) -> ExecProcessFuture<'_, ()> { |
| Box::pin(async { |
| Err(ExecServerError::Protocol( |
| "exec backend does not support shell snapshot prewarming".to_string(), |
| )) |
| }) |
| } |
|
|
| |
| 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>>; |
|
|
| #[cfg(test)] |
| 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; |
|
|
| #[tokio::test] |
| async fn empty_event_receiver_stays_open() { |
| let mut events = ExecProcessEventReceiver::empty(); |
|
|
| assert!( |
| timeout(Duration::from_millis(10), events.recv()) |
| .await |
| .is_err() |
| ); |
| } |
|
|
| #[tokio::test] |
| async fn event_history_replay_is_bounded_by_retained_bytes() { |
| let log = ExecProcessEventLog::new( 8, 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 }, |
| ] |
| ); |
| } |
| } |
|
|