Skip to main content

lenso_kernel/
driver.rs

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
10/// A task owned by a Runtime Driver's single-threaded local lane.
11pub type LocalTask = Pin<Box<dyn Future<Output = ()> + 'static>>;
12
13/// Result of joining a Runtime Driver task.
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum TaskOutcome {
16    /// The task completed normally.
17    Completed,
18    /// The task observed cooperative cancellation.
19    Cancelled,
20    /// The task or its Runtime Driver wrapper terminated abnormally.
21    Failed,
22}
23
24/// Driver-owned handle used to cancel and join local work.
25#[derive(Debug)]
26pub struct DriverTask {
27    pub(super) abort: AbortHandle,
28    pub(super) completion: oneshot::Receiver<TaskOutcome>,
29}
30
31impl DriverTask {
32    /// Creates a task handle from Driver-owned cancellation and completion primitives.
33    pub fn new(abort: AbortHandle, completion: oneshot::Receiver<TaskOutcome>) -> Self {
34        Self { abort, completion }
35    }
36
37    /// Requests cooperative cancellation of this task.
38    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
57/// Host facilities required to advance the portable Kernel.
58pub trait RuntimeDriver: Clone + 'static {
59    /// Returns the current monotonic instant relative to Driver startup.
60    fn now(&self) -> Duration;
61
62    /// Waits until the supplied monotonic instant.
63    fn sleep_until(&self, deadline: Duration) -> LocalBoxFuture<'static, ()>;
64
65    /// Cooperatively yields to other work on the local task lane.
66    fn yield_now(&self) -> LocalBoxFuture<'static, ()>;
67
68    /// Waits for Runner control work or until the supplied monotonic instant.
69    ///
70    /// Drivers with an event source should override this to park the lane. The
71    /// yielding default preserves compatibility with deterministic and embedded
72    /// Drivers that advance work cooperatively.
73    fn wait_for_runtime_event(&self, _deadline: Duration) -> LocalBoxFuture<'static, ()> {
74        self.yield_now()
75    }
76
77    /// Supplies deterministic or entropy-backed jitter bounded by the policy value.
78    fn jitter(&self, _maximum: Duration) -> Duration {
79        Duration::ZERO
80    }
81
82    /// Schedules Kernel-owned work on the local task lane.
83    fn spawn_local(&self, task: LocalTask) -> Result<DriverTask, SpawnError>;
84
85    /// Reports whether the embedding Runner requested shutdown.
86    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/// A bounded, per-binding Operation admission state.
138#[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/// The result of bounded cleanup after a graceful shutdown request.
471#[derive(Clone, Debug, Eq, PartialEq)]
472pub enum ShutdownOutcome {
473    /// Every managed task, resource, and Module generation was cleaned up.
474    Clean,
475    /// Cleanup completed but a Module or resource reported a Runtime Failure.
476    RuntimeFailure { error: RuntimeFailure },
477    /// The global shutdown deadline expired; remaining work was terminated.
478    Timeout,
479}
480
481/// A successful terminal result returned to the embedding Runner.
482#[derive(Clone, Debug, Eq, PartialEq)]
483pub enum TerminalOutcome {
484    /// The App completed a bounded clean shutdown.
485    CleanShutdown,
486    /// The App could not start because a Module reported a startup failure.
487    StartupFailure { error: RuntimeFailure },
488    /// The running App reported a Runtime Failure during terminal cleanup.
489    RuntimeFailure { error: RuntimeFailure },
490    /// The running App failed and cleanup reported a second Runtime Failure.
491    RuntimeFailureDuringShutdown {
492        error: RuntimeFailure,
493        cleanup_error: RuntimeFailure,
494    },
495    /// The running App failed and cleanup then exceeded its global deadline.
496    RuntimeFailureWithShutdownTimeout { error: RuntimeFailure },
497    /// The App exceeded its one global shutdown deadline.
498    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}