runmat_runtime/execution/
services.rs1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Mutex;
4
5use runmat_execution::{
6 CancellationReason, ExecutionScopeId, FutureHandle, FutureId, JobHandle, OutputContract,
7 TaskHandle, TaskId,
8};
9use runmat_value::Value;
10
11use super::ExecutionServiceError;
12
13static NEXT_SERVICE_NONCE: AtomicU64 = AtomicU64::new(1);
14
15#[derive(Clone, Debug, PartialEq)]
16pub struct DeferredCall {
17 pub function: usize,
18 pub arguments: Vec<Value>,
19 pub requested_outputs: usize,
20 pub program_revision: Option<runmat_execution::ProgramRevision>,
21 pub program: Option<Vec<u8>>,
26}
27
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct DurableJobOptions {
30 pub idempotency_key: Option<String>,
31 pub retention_millis: u64,
32}
33
34#[derive(Clone, Debug, PartialEq)]
35pub enum AwaitAction {
36 Passthrough(Value),
37 Pending(Value),
43 ExecuteFuture {
44 handle: FutureHandle,
45 call: DeferredCall,
46 },
47 Completed(Value),
48}
49
50pub trait RuntimeExecutionServices {
51 fn scope_id(&self) -> ExecutionScopeId;
52 fn requires_program_capture(&self) -> bool {
53 false
54 }
55 fn create_future(&self, call: DeferredCall) -> Result<FutureHandle, ExecutionServiceError>;
56 fn spawn(&self, future: &FutureHandle) -> Result<TaskHandle, ExecutionServiceError>;
57 fn submit_job(
58 &self,
59 _call: DeferredCall,
60 _options: DurableJobOptions,
61 ) -> Result<JobHandle, ExecutionServiceError> {
62 Err(ExecutionServiceError::Failed(
63 "durable jobs are unavailable in this execution backend".into(),
64 ))
65 }
66 fn await_job(&self, _job: &JobHandle) -> Result<Value, ExecutionServiceError> {
67 Err(ExecutionServiceError::Failed(
68 "durable jobs are unavailable in this execution backend".into(),
69 ))
70 }
71 fn begin_await(&self, value: Value) -> Result<AwaitAction, ExecutionServiceError>;
72 fn complete_future(
73 &self,
74 future: &FutureHandle,
75 result: Result<Value, ExecutionServiceError>,
76 ) -> Result<(), ExecutionServiceError>;
77 fn cancel(
78 &self,
79 value: &Value,
80 reason: CancellationReason,
81 ) -> Result<(), ExecutionServiceError>;
82 fn drain_scope(&self, reason: CancellationReason);
83}
84
85#[derive(Clone, Debug)]
86enum FutureState {
87 Deferred(DeferredCall),
88 Running,
89 Completed(Result<Value, ExecutionServiceError>),
90 Cancelled,
91}
92
93#[derive(Clone, Debug)]
94struct TaskRecord {
95 future_id: FutureId,
96 generation: u64,
97 cancelled: bool,
98}
99
100#[derive(Debug)]
101struct ServiceState {
102 next_future: u64,
103 next_task: u64,
104 futures: HashMap<FutureId, FutureState>,
105 tasks: HashMap<TaskId, TaskRecord>,
106}
107
108#[derive(Debug)]
114pub struct RuntimeExecutionService {
115 scope_id: ExecutionScopeId,
116 state: Mutex<ServiceState>,
117}
118
119impl RuntimeExecutionService {
120 pub fn new() -> Self {
121 let nonce = NEXT_SERVICE_NONCE.fetch_add(1, Ordering::Relaxed);
122 Self {
123 scope_id: ExecutionScopeId::derive(&[&nonce.to_be_bytes()]),
124 state: Mutex::new(ServiceState {
125 next_future: 0,
126 next_task: 0,
127 futures: HashMap::new(),
128 tasks: HashMap::new(),
129 }),
130 }
131 }
132
133 fn validate_scope(&self, scope_id: ExecutionScopeId) -> Result<(), ExecutionServiceError> {
134 if scope_id == self.scope_id {
135 Ok(())
136 } else {
137 Err(ExecutionServiceError::ForeignScope)
138 }
139 }
140}
141
142impl Default for RuntimeExecutionService {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl RuntimeExecutionServices for RuntimeExecutionService {
149 fn scope_id(&self) -> ExecutionScopeId {
150 self.scope_id
151 }
152
153 fn create_future(&self, call: DeferredCall) -> Result<FutureHandle, ExecutionServiceError> {
154 let requested_outputs = u16::try_from(call.requested_outputs)
155 .map_err(|_| ExecutionServiceError::InvalidOutputContract)?;
156 let mut state = self.state.lock().expect("execution service state poisoned");
157 let sequence = state.next_future;
158 state.next_future = state.next_future.wrapping_add(1);
159 let id = FutureId::derive(&[self.scope_id.bytes(), &sequence.to_be_bytes()]);
160 state.futures.insert(id, FutureState::Deferred(call));
161 Ok(FutureHandle {
162 id,
163 scope_id: self.scope_id,
164 outputs: OutputContract { requested_outputs },
165 })
166 }
167
168 fn spawn(&self, future: &FutureHandle) -> Result<TaskHandle, ExecutionServiceError> {
169 self.validate_scope(future.scope_id)?;
170 let mut state = self.state.lock().expect("execution service state poisoned");
171 match state.futures.get(&future.id) {
172 Some(FutureState::Deferred(_))
173 if !state.tasks.values().any(|task| task.future_id == future.id) => {}
174 Some(_) => {
175 return Err(ExecutionServiceError::Failed(
176 "future has already been scheduled".into(),
177 ));
178 }
179 None => return Err(ExecutionServiceError::UnknownHandle),
180 }
181 let sequence = state.next_task;
182 state.next_task = state.next_task.wrapping_add(1);
183 let id = TaskId::derive(&[self.scope_id.bytes(), &sequence.to_be_bytes()]);
184 let generation = 1;
185 state.tasks.insert(
186 id,
187 TaskRecord {
188 future_id: future.id,
189 generation,
190 cancelled: false,
191 },
192 );
193 Ok(TaskHandle {
194 id,
195 scope_id: self.scope_id,
196 generation,
197 outputs: future.outputs.clone(),
198 })
199 }
200
201 fn begin_await(&self, value: Value) -> Result<AwaitAction, ExecutionServiceError> {
202 if let Value::Job(handle) = &value {
203 return self.await_job(handle).map(AwaitAction::Completed);
204 }
205 let (future, task_id) = match value {
206 Value::Future(handle) => (handle, None),
207 Value::Task(task) => {
208 self.validate_scope(task.scope_id)?;
209 let state = self.state.lock().expect("execution service state poisoned");
210 let record = state
211 .tasks
212 .get(&task.id)
213 .ok_or(ExecutionServiceError::UnknownHandle)?;
214 if record.generation != task.generation {
215 return Err(ExecutionServiceError::UnknownHandle);
216 }
217 if record.cancelled {
218 return Err(ExecutionServiceError::Cancelled);
219 }
220 (
221 FutureHandle {
222 id: record.future_id,
223 scope_id: self.scope_id,
224 outputs: task.outputs,
225 },
226 Some(task.id),
227 )
228 }
229 value => return Ok(AwaitAction::Passthrough(value)),
230 };
231 self.validate_scope(future.scope_id)?;
232 let mut state = self.state.lock().expect("execution service state poisoned");
233 let record = state
234 .futures
235 .get_mut(&future.id)
236 .ok_or(ExecutionServiceError::UnknownHandle)?;
237 match record {
238 FutureState::Deferred(call) => {
239 let call = call.clone();
240 *record = FutureState::Running;
241 Ok(AwaitAction::ExecuteFuture {
242 handle: future,
243 call,
244 })
245 }
246 FutureState::Running => Err(ExecutionServiceError::Failed(
247 "execution is already being awaited".to_string(),
248 )),
249 FutureState::Completed(result) => {
250 let result = result.clone();
251 if let Some(task_id) = task_id {
252 state.tasks.remove(&task_id);
253 }
254 result.map(AwaitAction::Completed)
255 }
256 FutureState::Cancelled => Err(ExecutionServiceError::Cancelled),
257 }
258 }
259
260 fn complete_future(
261 &self,
262 future: &FutureHandle,
263 result: Result<Value, ExecutionServiceError>,
264 ) -> Result<(), ExecutionServiceError> {
265 self.validate_scope(future.scope_id)?;
266 let mut state = self.state.lock().expect("execution service state poisoned");
267 let record = state
268 .futures
269 .get_mut(&future.id)
270 .ok_or(ExecutionServiceError::UnknownHandle)?;
271 if !matches!(record, FutureState::Running) {
272 return Err(ExecutionServiceError::UnknownHandle);
273 }
274 *record = FutureState::Completed(result);
275 state.tasks.retain(|_, task| task.future_id != future.id);
276 Ok(())
277 }
278
279 fn cancel(
280 &self,
281 value: &Value,
282 _reason: CancellationReason,
283 ) -> Result<(), ExecutionServiceError> {
284 let mut state = self.state.lock().expect("execution service state poisoned");
285 match value {
286 Value::Future(handle) => {
287 self.validate_scope(handle.scope_id)?;
288 let record = state
289 .futures
290 .get_mut(&handle.id)
291 .ok_or(ExecutionServiceError::UnknownHandle)?;
292 *record = FutureState::Cancelled;
293 }
294 Value::Task(handle) => {
295 self.validate_scope(handle.scope_id)?;
296 let future_id = {
297 let record = state
298 .tasks
299 .get_mut(&handle.id)
300 .ok_or(ExecutionServiceError::UnknownHandle)?;
301 if record.generation != handle.generation {
302 return Err(ExecutionServiceError::UnknownHandle);
303 }
304 record.cancelled = true;
305 record.future_id
306 };
307 if let Some(future) = state.futures.get_mut(&future_id) {
308 *future = FutureState::Cancelled;
309 }
310 }
311 _ => return Err(ExecutionServiceError::UnknownHandle),
312 }
313 Ok(())
314 }
315
316 fn drain_scope(&self, _reason: CancellationReason) {
317 let mut state = self.state.lock().expect("execution service state poisoned");
318 for future in state.futures.values_mut() {
319 if !matches!(future, FutureState::Completed(_)) {
320 *future = FutureState::Cancelled;
321 }
322 }
323 for task in state.tasks.values_mut() {
324 task.cancelled = true;
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn services_reject_handles_from_another_scope() {
335 let first = RuntimeExecutionService::new();
336 let second = RuntimeExecutionService::new();
337 let future = first
338 .create_future(DeferredCall {
339 function: 1,
340 arguments: vec![],
341 requested_outputs: 1,
342 program_revision: None,
343 program: None,
344 })
345 .unwrap();
346 assert_eq!(
347 second.spawn(&future),
348 Err(ExecutionServiceError::ForeignScope)
349 );
350 }
351
352 #[test]
353 fn future_task_lifecycle_is_backed_by_service_state() {
354 let service = RuntimeExecutionService::new();
355 let future = service
356 .create_future(DeferredCall {
357 function: 7,
358 arguments: vec![Value::Num(3.0)],
359 requested_outputs: 1,
360 program_revision: None,
361 program: None,
362 })
363 .unwrap();
364 let task = service.spawn(&future).unwrap();
365 let action = service.begin_await(Value::Task(task.clone())).unwrap();
366 let AwaitAction::ExecuteFuture { handle, call } = action else {
367 panic!("expected deferred execution");
368 };
369 assert_eq!(handle, future);
370 assert_eq!(call.function, 7);
371 service
372 .complete_future(&future, Ok(Value::Num(9.0)))
373 .unwrap();
374 assert_eq!(
375 service.begin_await(Value::Task(task)),
376 Err(ExecutionServiceError::UnknownHandle)
377 );
378 }
379
380 #[test]
381 fn cloned_invocation_context_inherits_exact_service() {
382 let service: std::rc::Rc<dyn RuntimeExecutionServices> =
383 std::rc::Rc::new(RuntimeExecutionService::new());
384 let parent = crate::context::RuntimeContext::new(service);
385 let nested = parent.clone();
386 assert_eq!(parent.execution().scope_id(), nested.execution().scope_id());
387 }
388
389 #[test]
390 fn independently_created_thread_sessions_have_distinct_scopes() {
391 let first = std::thread::spawn(|| RuntimeExecutionService::new().scope_id())
392 .join()
393 .unwrap();
394 let second = std::thread::spawn(|| RuntimeExecutionService::new().scope_id())
395 .join()
396 .unwrap();
397 assert_ne!(first, second);
398 }
399
400 #[test]
401 fn scope_drain_cancels_unfinished_children() {
402 let service = RuntimeExecutionService::new();
403 let future = service
404 .create_future(DeferredCall {
405 function: 1,
406 arguments: vec![],
407 requested_outputs: 1,
408 program_revision: None,
409 program: None,
410 })
411 .unwrap();
412 service.drain_scope(CancellationReason::Shutdown);
413 assert_eq!(
414 service.begin_await(Value::Future(future)),
415 Err(ExecutionServiceError::Cancelled)
416 );
417 }
418}