codex_exec_server/
process.rs1use std::collections::VecDeque;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::sync::Mutex as StdMutex;
6
7use tokio::sync::broadcast;
8use tokio::sync::watch;
9
10use crate::ExecServerError;
11use crate::ProcessId;
12use crate::protocol::ExecParams;
13use crate::protocol::ProcessOutputChunk;
14use crate::protocol::ProcessSignal;
15use crate::protocol::ReadResponse;
16use crate::protocol::WriteResponse;
17
18pub struct StartedExecProcess {
19 pub process: Arc<dyn ExecProcess>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum ExecProcessEvent {
32 Output(ProcessOutputChunk),
33 Exited {
34 seq: u64,
35 exit_code: i32,
36 sandbox_denied: Option<bool>,
37 },
38 Closed {
39 seq: u64,
40 },
41 Failed(String),
42}
43
44#[derive(Clone)]
51pub(crate) struct ExecProcessEventLog {
52 inner: Arc<ExecProcessEventLogInner>,
53}
54
55struct ExecProcessEventLogInner {
56 history: StdMutex<ExecProcessEventHistory>,
57 live_tx: broadcast::Sender<ExecProcessEvent>,
58 event_capacity: usize,
59 byte_capacity: usize,
60}
61
62#[derive(Default)]
63struct ExecProcessEventHistory {
64 events: VecDeque<ExecProcessEvent>,
65 retained_bytes: usize,
66}
67
68impl ExecProcessEvent {
69 pub(crate) fn seq(&self) -> Option<u64> {
74 match self {
75 ExecProcessEvent::Output(chunk) => Some(chunk.seq),
76 ExecProcessEvent::Exited { seq, .. } | ExecProcessEvent::Closed { seq } => Some(*seq),
77 ExecProcessEvent::Failed(_) => None,
78 }
79 }
80
81 fn retained_len(&self) -> usize {
82 match self {
83 ExecProcessEvent::Output(chunk) => chunk.chunk.0.len(),
84 ExecProcessEvent::Failed(message) => message.len(),
85 ExecProcessEvent::Exited { .. } | ExecProcessEvent::Closed { .. } => 0,
86 }
87 }
88}
89
90impl ExecProcessEventLog {
91 pub(crate) fn new(event_capacity: usize, byte_capacity: usize) -> Self {
92 let (live_tx, _live_rx) = broadcast::channel(event_capacity);
93 Self {
94 inner: Arc::new(ExecProcessEventLogInner {
95 history: StdMutex::new(ExecProcessEventHistory::default()),
96 live_tx,
97 event_capacity,
98 byte_capacity,
99 }),
100 }
101 }
102
103 pub(crate) fn publish(&self, event: ExecProcessEvent) {
104 let mut history = self
105 .inner
106 .history
107 .lock()
108 .unwrap_or_else(std::sync::PoisonError::into_inner);
109 history.retained_bytes += event.retained_len();
110 history.events.push_back(event.clone());
111 while history.events.len() > self.inner.event_capacity
112 || history.retained_bytes > self.inner.byte_capacity
113 {
114 let Some(evicted) = history.events.pop_front() else {
115 break;
116 };
117 history.retained_bytes = history
118 .retained_bytes
119 .saturating_sub(evicted.retained_len());
120 }
121
122 let _ = self.inner.live_tx.send(event);
123 }
124
125 pub(crate) fn subscribe(&self) -> ExecProcessEventReceiver {
126 let history = self
127 .inner
128 .history
129 .lock()
130 .unwrap_or_else(std::sync::PoisonError::into_inner);
131 let live_rx = self.inner.live_tx.subscribe();
132 let replay = history.events.iter().cloned().collect();
133
134 ExecProcessEventReceiver {
135 replay,
136 live_rx,
137 _keepalive: None,
138 }
139 }
140}
141
142pub struct ExecProcessEventReceiver {
143 replay: VecDeque<ExecProcessEvent>,
144 live_rx: broadcast::Receiver<ExecProcessEvent>,
145 _keepalive: Option<broadcast::Sender<ExecProcessEvent>>,
146}
147
148impl ExecProcessEventReceiver {
149 pub fn empty() -> Self {
151 let (live_tx, live_rx) = broadcast::channel(1);
152 Self {
153 replay: VecDeque::new(),
154 live_rx,
155 _keepalive: Some(live_tx),
156 }
157 }
158
159 pub async fn recv(&mut self) -> Result<ExecProcessEvent, broadcast::error::RecvError> {
165 if let Some(event) = self.replay.pop_front() {
166 return Ok(event);
167 }
168
169 self.live_rx.recv().await
170 }
171}
172
173pub trait ExecProcess: Send + Sync {
180 fn process_id(&self) -> &ProcessId;
181
182 fn subscribe_wake(&self) -> watch::Receiver<u64>;
183
184 fn subscribe_events(&self) -> ExecProcessEventReceiver;
185
186 fn read(
187 &self,
188 after_seq: Option<u64>,
189 max_bytes: Option<usize>,
190 wait_ms: Option<u64>,
191 ) -> ExecProcessFuture<'_, ReadResponse>;
192
193 fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>;
194
195 fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()>;
196
197 fn terminate(&self) -> ExecProcessFuture<'_, ()>;
198}
199
200pub type ExecProcessFuture<'a, T> =
201 Pin<Box<dyn Future<Output = Result<T, ExecServerError>> + Send + 'a>>;
202
203pub trait ExecBackend: Send + Sync {
204 fn start(&self, params: ExecParams) -> ExecBackendFuture<'_>;
205}
206
207pub type ExecBackendFuture<'a> =
208 Pin<Box<dyn Future<Output = Result<StartedExecProcess, ExecServerError>> + Send + 'a>>;
209
210#[cfg(test)]
211mod tests {
212 use pretty_assertions::assert_eq;
213 use tokio::time::Duration;
214 use tokio::time::timeout;
215
216 use super::ExecProcessEvent;
217 use super::ExecProcessEventLog;
218 use super::ExecProcessEventReceiver;
219 use crate::protocol::ExecOutputStream;
220 use crate::protocol::ProcessOutputChunk;
221
222 #[tokio::test]
223 async fn empty_event_receiver_stays_open() {
224 let mut events = ExecProcessEventReceiver::empty();
225
226 assert!(
227 timeout(Duration::from_millis(10), events.recv())
228 .await
229 .is_err()
230 );
231 }
232
233 #[tokio::test]
234 async fn event_history_replay_is_bounded_by_retained_bytes() {
235 let log = ExecProcessEventLog::new(8, 3);
236
237 log.publish(ExecProcessEvent::Output(ProcessOutputChunk {
238 seq: 1,
239 stream: ExecOutputStream::Stdout,
240 chunk: b"large".to_vec().into(),
241 }));
242 log.publish(ExecProcessEvent::Exited {
243 seq: 2,
244 exit_code: 0,
245 sandbox_denied: Some(false),
246 });
247 log.publish(ExecProcessEvent::Closed { seq: 3 });
248
249 let mut events = log.subscribe();
250 let replay = vec![
251 timeout(Duration::from_secs(1), events.recv())
252 .await
253 .expect("exit event replay should not time out")
254 .expect("exit event replay should be available"),
255 timeout(Duration::from_secs(1), events.recv())
256 .await
257 .expect("closed event replay should not time out")
258 .expect("closed event replay should be available"),
259 ];
260
261 assert_eq!(
262 replay,
263 vec![
264 ExecProcessEvent::Exited {
265 seq: 2,
266 exit_code: 0,
267 sandbox_denied: Some(false),
268 },
269 ExecProcessEvent::Closed { seq: 3 },
270 ]
271 );
272 }
273}