1use std::future::poll_fn;
2
3use super::{
4 AbortHandle, CancellationToken, Cell, Context, Duration, Either, Future, FutureExt,
5 InvocationContext, LocalBoxFuture, NativeAppRuntime, Pin, Poll, Rc, RefCell,
6 RequestAdmissionPlan, RuntimeFailure, SpawnError, VecDeque, begin_module_supervision, oneshot,
7 pending, schedule_module_supervision, select,
8};
9
10pub type LocalTask = Pin<Box<dyn Future<Output = ()> + 'static>>;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum TaskOutcome {
16 Completed,
18 Cancelled,
20 Failed,
22}
23
24#[derive(Debug)]
26pub struct DriverTask {
27 pub(super) abort: AbortHandle,
28 pub(super) completion: oneshot::Receiver<TaskOutcome>,
29}
30
31impl DriverTask {
32 pub fn new(abort: AbortHandle, completion: oneshot::Receiver<TaskOutcome>) -> Self {
34 Self { abort, completion }
35 }
36
37 pub fn cancel(&self) {
39 self.abort.abort();
40 }
41
42 pub(super) fn abort_handle(&self) -> AbortHandle {
43 self.abort.clone()
44 }
45}
46
47impl Future for DriverTask {
48 type Output = TaskOutcome;
49
50 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
51 Pin::new(&mut self.completion)
52 .poll(context)
53 .map(|outcome| outcome.unwrap_or(TaskOutcome::Failed))
54 }
55}
56
57pub trait RuntimeDriver: Clone + 'static {
59 fn now(&self) -> Duration;
61
62 fn sleep_until(&self, deadline: Duration) -> LocalBoxFuture<'static, ()>;
64
65 fn yield_now(&self) -> LocalBoxFuture<'static, ()>;
67
68 fn wait_for_runtime_event(&self, _deadline: Duration) -> LocalBoxFuture<'static, ()> {
74 self.yield_now()
75 }
76
77 fn jitter(&self, _maximum: Duration) -> Duration {
79 Duration::ZERO
80 }
81
82 fn spawn_local(&self, task: LocalTask) -> Result<DriverTask, SpawnError>;
84
85 fn shutdown_requested(&self) -> bool;
87}
88
89#[derive(Clone)]
90pub(super) struct DriverControl {
91 pub(super) now: Rc<dyn Fn() -> Duration>,
92 pub(super) sleep_until: Rc<dyn Fn(Duration) -> LocalBoxFuture<'static, ()>>,
93 pub(super) yield_now: Rc<dyn Fn() -> LocalBoxFuture<'static, ()>>,
94 pub(super) jitter: Rc<dyn Fn(Duration) -> Duration>,
95 pub(super) spawn_local: Rc<dyn Fn(LocalTask) -> Result<DriverTask, SpawnError>>,
96}
97
98impl std::fmt::Debug for DriverControl {
99 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 formatter
101 .debug_struct("DriverControl")
102 .finish_non_exhaustive()
103 }
104}
105
106impl DriverControl {
107 pub(super) fn new<D: RuntimeDriver>(driver: &D) -> Self {
108 let now_driver = driver.clone();
109 let sleep_driver = driver.clone();
110 let yield_driver = driver.clone();
111 let jitter_driver = driver.clone();
112 let spawn_driver = driver.clone();
113 Self {
114 now: Rc::new(move || now_driver.now()),
115 sleep_until: Rc::new(move |deadline| sleep_driver.sleep_until(deadline)),
116 yield_now: Rc::new(move || yield_driver.yield_now()),
117 jitter: Rc::new(move |maximum| jitter_driver.jitter(maximum)),
118 spawn_local: Rc::new(move |task| spawn_driver.spawn_local(task)),
119 }
120 }
121}
122
123pub(super) async fn wait_until<F: Future>(
124 driver: &DriverControl,
125 deadline: Duration,
126 future: F,
127) -> Option<F::Output> {
128 let work = future.fuse();
129 let timer = (driver.sleep_until)(deadline).fuse();
130 futures::pin_mut!(work, timer);
131 match select(work, timer).await {
132 Either::Left((output, _)) => Some(output),
133 Either::Right(((), _)) => None,
134 }
135}
136
137#[derive(Clone, Debug)]
139pub(super) struct RequestAdmission {
140 pub(super) limits: RequestAdmissionPlan,
141 pub(super) state: Rc<RequestAdmissionState>,
142}
143
144#[derive(Debug, Default)]
145pub(super) struct RequestAdmissionState {
146 pub(super) active: Cell<usize>,
147 pub(super) queued: Cell<usize>,
148 pub(super) waiters: RefCell<VecDeque<Rc<QueueWaiter>>>,
149}
150
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
152pub(super) enum QueueWaiterStatus {
153 Waiting,
154 Woken,
155 Acquired,
156 Cancelled,
157}
158
159#[derive(Debug)]
160pub(super) struct QueueWaiter {
161 pub(super) status: Cell<QueueWaiterStatus>,
162 pub(super) wakeup: RefCell<Option<oneshot::Sender<()>>>,
163}
164
165impl RequestAdmission {
166 pub(super) fn new(limits: RequestAdmissionPlan) -> Self {
167 Self {
168 limits,
169 state: Rc::new(RequestAdmissionState::default()),
170 }
171 }
172
173 pub(super) fn queue_depth(&self) -> usize {
174 self.state.queued.get()
175 }
176
177 pub(crate) fn try_acquire(
178 &self,
179 capability: &'static str,
180 operation: &str,
181 context: &InvocationContext,
182 driver: &DriverControl,
183 ) -> Result<RequestPermit, RuntimeFailure> {
184 ensure_context_active(driver, context)?;
185 if self.state.active.get() < self.limits.max_concurrency() {
186 self.state.active.set(self.state.active.get() + 1);
187 return Ok(RequestPermit {
188 state: self.state.clone(),
189 });
190 }
191 Err(RuntimeFailure::ResourceExhausted {
192 capability,
193 operation: operation.to_owned(),
194 })
195 }
196
197 pub(super) async fn acquire(
198 &self,
199 capability: &'static str,
200 operation: &str,
201 context: &InvocationContext,
202 driver: &DriverControl,
203 ) -> Result<RequestPermit, RuntimeFailure> {
204 if let Ok(permit) = self.try_acquire(capability, operation, context, driver) {
205 return Ok(permit);
206 }
207 ensure_context_active(driver, context)?;
208
209 if self.state.queued.get() >= self.limits.queue_capacity() {
210 return Err(RuntimeFailure::ResourceExhausted {
211 capability,
212 operation: operation.to_owned(),
213 });
214 }
215
216 let (wakeup, waiter) = oneshot::channel();
217 let waiter_state = Rc::new(QueueWaiter {
218 status: Cell::new(QueueWaiterStatus::Waiting),
219 wakeup: RefCell::new(Some(wakeup)),
220 });
221 self.state.queued.set(self.state.queued.get() + 1);
222 self.state
223 .waiters
224 .borrow_mut()
225 .push_back(waiter_state.clone());
226 let queued = QueuedAdmission {
227 state: self.state.clone(),
228 waiter_state,
229 waiter,
230 };
231 queued.wait(driver, context).await
232 }
233}
234
235#[derive(Debug)]
236pub(super) struct QueuedAdmission {
237 pub(super) state: Rc<RequestAdmissionState>,
238 pub(super) waiter_state: Rc<QueueWaiter>,
239 pub(super) waiter: oneshot::Receiver<()>,
240}
241
242impl QueuedAdmission {
243 pub(super) async fn wait(
244 mut self,
245 driver: &DriverControl,
246 context: &InvocationContext,
247 ) -> Result<RequestPermit, RuntimeFailure> {
248 let result = await_with_context(driver, context, &mut self.waiter).await;
249 match result {
250 Ok(Ok(())) => {
251 if self.waiter_state.status.get() == QueueWaiterStatus::Woken {
252 self.waiter_state.status.set(QueueWaiterStatus::Acquired);
253 self.state.queued.set(self.state.queued.get() - 1);
254 Ok(RequestPermit {
255 state: self.state.clone(),
256 })
257 } else {
258 Err(RuntimeFailure::Cancelled {
259 request_id: context.request_id(),
260 })
261 }
262 }
263 Ok(Err(_)) => Err(RuntimeFailure::Cancelled {
264 request_id: context.request_id(),
265 }),
266 Err(error) => Err(error),
267 }
268 }
269}
270
271impl Drop for QueuedAdmission {
272 fn drop(&mut self) {
273 let previous = self
274 .waiter_state
275 .status
276 .replace(QueueWaiterStatus::Cancelled);
277 match previous {
278 QueueWaiterStatus::Waiting => {
279 self.state.queued.set(self.state.queued.get() - 1);
280 }
281 QueueWaiterStatus::Woken => {
282 self.state.queued.set(self.state.queued.get() - 1);
283 self.state.active.set(self.state.active.get() - 1);
284 wake_next(&self.state);
285 }
286 QueueWaiterStatus::Acquired | QueueWaiterStatus::Cancelled => {}
287 }
288 self.state
289 .waiters
290 .borrow_mut()
291 .retain(|waiter| !Rc::ptr_eq(waiter, &self.waiter_state));
292 }
293}
294
295#[derive(Debug)]
296pub(super) struct RequestPermit {
297 pub(super) state: Rc<RequestAdmissionState>,
298}
299
300impl Drop for RequestPermit {
301 fn drop(&mut self) {
302 self.state.active.set(self.state.active.get() - 1);
303 wake_next(&self.state);
304 }
305}
306
307pub(super) fn wake_next(state: &Rc<RequestAdmissionState>) {
308 loop {
309 let Some(waiter) = state.waiters.borrow_mut().pop_front() else {
310 return;
311 };
312 if waiter.status.replace(QueueWaiterStatus::Woken) != QueueWaiterStatus::Waiting {
313 continue;
314 }
315 state.active.set(state.active.get() + 1);
316 let sent = waiter
317 .wakeup
318 .borrow_mut()
319 .take()
320 .is_some_and(|wakeup| wakeup.send(()).is_ok());
321 if sent {
322 return;
323 }
324 waiter.status.set(QueueWaiterStatus::Cancelled);
325 state.active.set(state.active.get() - 1);
326 state.queued.set(state.queued.get() - 1);
327 }
328}
329
330pub(super) async fn await_with_context<F: Future>(
331 driver: &DriverControl,
332 context: &InvocationContext,
333 future: F,
334) -> Result<F::Output, RuntimeFailure> {
335 ensure_context_active(driver, context)?;
336
337 let work = future.fuse();
338 futures::pin_mut!(work);
339 if let Some(output) = poll_fn(|context| match work.as_mut().poll(context) {
340 Poll::Ready(output) => Poll::Ready(Some(output)),
341 Poll::Pending => Poll::Ready(None),
342 })
343 .await
344 {
345 return Ok(output);
346 }
347 let cancellation = context.cancellation.cancelled().fuse();
348 let deadline: LocalBoxFuture<'static, ()> = context.deadline().map_or_else(
349 || Box::pin(pending::<()>()) as LocalBoxFuture<'static, ()>,
350 |deadline| (driver.sleep_until)(deadline),
351 );
352 let deadline = deadline.fuse();
353 futures::pin_mut!(cancellation, deadline);
354
355 match select(select(work, cancellation), deadline).await {
356 Either::Left((Either::Left((output, _)), _)) => Ok(output),
357 Either::Left((Either::Right(((), _)), _)) => Err(RuntimeFailure::Cancelled {
358 request_id: context.request_id(),
359 }),
360 Either::Right(((), _)) => Err(RuntimeFailure::DeadlineExceeded {
361 request_id: context.request_id(),
362 }),
363 }
364}
365
366pub(super) async fn await_with_generation_context<F: Future>(
367 driver: &DriverControl,
368 context: &InvocationContext,
369 generation_cancellation: CancellationToken,
370 capability: &'static str,
371 future: F,
372) -> Result<F::Output, RuntimeFailure> {
373 ensure_context_active(driver, context)?;
374 if generation_cancellation.is_cancelled() {
375 return Err(RuntimeFailure::Unavailable { capability });
376 }
377
378 let work = future.fuse();
379 futures::pin_mut!(work);
380 if let Some(output) = poll_fn(|context| match work.as_mut().poll(context) {
381 Poll::Ready(output) => Poll::Ready(Some(output)),
382 Poll::Pending => Poll::Ready(None),
383 })
384 .await
385 {
386 return Ok(output);
387 }
388 let cancellation = context.cancellation.cancelled().fuse();
389 let generation_cancellation = generation_cancellation.cancelled().fuse();
390 let deadline: LocalBoxFuture<'static, ()> = context.deadline().map_or_else(
391 || Box::pin(pending::<()>()) as LocalBoxFuture<'static, ()>,
392 |deadline| (driver.sleep_until)(deadline),
393 );
394 let deadline = deadline.fuse();
395 futures::pin_mut!(cancellation, generation_cancellation, deadline);
396
397 match select(
398 select(select(work, cancellation), generation_cancellation),
399 deadline,
400 )
401 .await
402 {
403 Either::Left((Either::Left((Either::Left((output, _)), _)), _)) => Ok(output),
404 Either::Left((Either::Left((Either::Right(((), _)), _)), _)) => {
405 Err(RuntimeFailure::Cancelled {
406 request_id: context.request_id(),
407 })
408 }
409 Either::Left((Either::Right(((), _)), _)) => {
410 Err(RuntimeFailure::Unavailable { capability })
411 }
412 Either::Right(((), _)) => Err(RuntimeFailure::DeadlineExceeded {
413 request_id: context.request_id(),
414 }),
415 }
416}
417
418pub(super) fn is_module_failure(error: &RuntimeFailure) -> bool {
419 matches!(error, RuntimeFailure::ModuleFailure { .. })
420}
421
422pub(super) fn schedule_module_supervision_after_failure(
423 runtime: &Rc<NativeAppRuntime>,
424 instance_key: &str,
425 error: RuntimeFailure,
426) -> RuntimeFailure {
427 if is_module_failure(&error)
428 && begin_module_supervision(runtime, instance_key).unwrap_or(false)
429 && let Err(schedule_error) = schedule_module_supervision(runtime, instance_key)
430 {
431 return handle_supervision_schedule_failure(runtime, instance_key, schedule_error);
432 }
433 error
434}
435
436pub(super) fn handle_supervision_schedule_failure(
437 runtime: &Rc<NativeAppRuntime>,
438 instance_key: &str,
439 error: RuntimeFailure,
440) -> RuntimeFailure {
441 let must_fail = runtime
442 .supervision
443 .borrow()
444 .get(instance_key)
445 .is_some_and(|state| state.criticality.is_critical() || state.required_path);
446 if must_fail {
447 runtime.terminal_failure.replace(Some(error.clone()));
448 runtime.begin_shutdown();
449 }
450 error
451}
452
453pub(super) fn ensure_context_active(
454 driver: &DriverControl,
455 context: &InvocationContext,
456) -> Result<(), RuntimeFailure> {
457 if context.is_cancelled() {
458 return Err(RuntimeFailure::Cancelled {
459 request_id: context.request_id(),
460 });
461 }
462 if context.is_expired((driver.now)()) {
463 return Err(RuntimeFailure::DeadlineExceeded {
464 request_id: context.request_id(),
465 });
466 }
467 Ok(())
468}
469
470#[derive(Clone, Debug, Eq, PartialEq)]
472pub enum ShutdownOutcome {
473 Clean,
475 RuntimeFailure { error: RuntimeFailure },
477 Timeout,
479}
480
481#[derive(Clone, Debug, Eq, PartialEq)]
483pub enum TerminalOutcome {
484 CleanShutdown,
486 StartupFailure { error: RuntimeFailure },
488 RuntimeFailure { error: RuntimeFailure },
490 RuntimeFailureDuringShutdown {
492 error: RuntimeFailure,
493 cleanup_error: RuntimeFailure,
494 },
495 RuntimeFailureWithShutdownTimeout { error: RuntimeFailure },
497 ShutdownTimeout,
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::DeterministicDriver;
505
506 #[test]
507 fn ready_work_does_not_register_cancellation_waiters() {
508 let driver = DeterministicDriver::new();
509 let control = DriverControl::new(&driver);
510 let caller_cancellation = CancellationToken::new();
511 let generation_cancellation = CancellationToken::new();
512 let context = InvocationContext::new(
513 1,
514 Some(Duration::from_millis(10)),
515 caller_cancellation.clone(),
516 );
517 let observed = Rc::new(Cell::new((usize::MAX, usize::MAX)));
518 let observed_waiters = observed.clone();
519 let work_caller = caller_cancellation.clone();
520 let work_generation = generation_cancellation.clone();
521 let work = poll_fn(move |_| {
522 observed_waiters.set((
523 work_caller.state.waiters.borrow().len(),
524 work_generation.state.waiters.borrow().len(),
525 ));
526 Poll::Ready("done")
527 });
528
529 let outcome = driver.run(await_with_generation_context(
530 &control,
531 &context,
532 generation_cancellation,
533 "test.capability",
534 work,
535 ));
536
537 assert_eq!(outcome, Ok("done"));
538 assert_eq!(observed.get(), (0, 0));
539 }
540}