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        if let Err(error) = ensure_context_active(driver, context) {
208            return Err(error);
209        }
210
211        if self.state.queued.get() >= self.limits.queue_capacity() {
212            return Err(RuntimeFailure::ResourceExhausted {
213                capability,
214                operation: operation.to_owned(),
215            });
216        }
217
218        let (wakeup, waiter) = oneshot::channel();
219        let waiter_state = Rc::new(QueueWaiter {
220            status: Cell::new(QueueWaiterStatus::Waiting),
221            wakeup: RefCell::new(Some(wakeup)),
222        });
223        self.state.queued.set(self.state.queued.get() + 1);
224        self.state
225            .waiters
226            .borrow_mut()
227            .push_back(waiter_state.clone());
228        let queued = QueuedAdmission {
229            state: self.state.clone(),
230            waiter_state,
231            waiter,
232        };
233        queued.wait(driver, context).await
234    }
235}
236
237#[derive(Debug)]
238pub(super) struct QueuedAdmission {
239    pub(super) state: Rc<RequestAdmissionState>,
240    pub(super) waiter_state: Rc<QueueWaiter>,
241    pub(super) waiter: oneshot::Receiver<()>,
242}
243
244impl QueuedAdmission {
245    pub(super) async fn wait(
246        mut self,
247        driver: &DriverControl,
248        context: &InvocationContext,
249    ) -> Result<RequestPermit, RuntimeFailure> {
250        let result = await_with_context(driver, context, &mut self.waiter).await;
251        match result {
252            Ok(Ok(())) => {
253                if self.waiter_state.status.get() == QueueWaiterStatus::Woken {
254                    self.waiter_state.status.set(QueueWaiterStatus::Acquired);
255                    self.state.queued.set(self.state.queued.get() - 1);
256                    Ok(RequestPermit {
257                        state: self.state.clone(),
258                    })
259                } else {
260                    Err(RuntimeFailure::Cancelled {
261                        request_id: context.request_id(),
262                    })
263                }
264            }
265            Ok(Err(_)) => Err(RuntimeFailure::Cancelled {
266                request_id: context.request_id(),
267            }),
268            Err(error) => Err(error),
269        }
270    }
271}
272
273impl Drop for QueuedAdmission {
274    fn drop(&mut self) {
275        let previous = self
276            .waiter_state
277            .status
278            .replace(QueueWaiterStatus::Cancelled);
279        match previous {
280            QueueWaiterStatus::Waiting => {
281                self.state.queued.set(self.state.queued.get() - 1);
282            }
283            QueueWaiterStatus::Woken => {
284                self.state.queued.set(self.state.queued.get() - 1);
285                self.state.active.set(self.state.active.get() - 1);
286                wake_next(&self.state);
287            }
288            QueueWaiterStatus::Acquired | QueueWaiterStatus::Cancelled => {}
289        }
290        self.state
291            .waiters
292            .borrow_mut()
293            .retain(|waiter| !Rc::ptr_eq(waiter, &self.waiter_state));
294    }
295}
296
297#[derive(Debug)]
298pub(super) struct RequestPermit {
299    pub(super) state: Rc<RequestAdmissionState>,
300}
301
302impl Drop for RequestPermit {
303    fn drop(&mut self) {
304        self.state.active.set(self.state.active.get() - 1);
305        wake_next(&self.state);
306    }
307}
308
309pub(super) fn wake_next(state: &Rc<RequestAdmissionState>) {
310    loop {
311        let Some(waiter) = state.waiters.borrow_mut().pop_front() else {
312            return;
313        };
314        if waiter.status.replace(QueueWaiterStatus::Woken) != QueueWaiterStatus::Waiting {
315            continue;
316        }
317        state.active.set(state.active.get() + 1);
318        let sent = waiter
319            .wakeup
320            .borrow_mut()
321            .take()
322            .is_some_and(|wakeup| wakeup.send(()).is_ok());
323        if sent {
324            return;
325        }
326        waiter.status.set(QueueWaiterStatus::Cancelled);
327        state.active.set(state.active.get() - 1);
328        state.queued.set(state.queued.get() - 1);
329    }
330}
331
332pub(super) async fn await_with_context<F: Future>(
333    driver: &DriverControl,
334    context: &InvocationContext,
335    future: F,
336) -> Result<F::Output, RuntimeFailure> {
337    ensure_context_active(driver, context)?;
338
339    let work = future.fuse();
340    futures::pin_mut!(work);
341    if let Some(output) = poll_fn(|context| match work.as_mut().poll(context) {
342        Poll::Ready(output) => Poll::Ready(Some(output)),
343        Poll::Pending => Poll::Ready(None),
344    })
345    .await
346    {
347        return Ok(output);
348    }
349    let cancellation = context.cancellation.cancelled().fuse();
350    let deadline: LocalBoxFuture<'static, ()> = context.deadline().map_or_else(
351        || Box::pin(pending::<()>()) as LocalBoxFuture<'static, ()>,
352        |deadline| (driver.sleep_until)(deadline),
353    );
354    let deadline = deadline.fuse();
355    futures::pin_mut!(cancellation, deadline);
356
357    match select(select(work, cancellation), deadline).await {
358        Either::Left((Either::Left((output, _)), _)) => Ok(output),
359        Either::Left((Either::Right(((), _)), _)) => Err(RuntimeFailure::Cancelled {
360            request_id: context.request_id(),
361        }),
362        Either::Right(((), _)) => Err(RuntimeFailure::DeadlineExceeded {
363            request_id: context.request_id(),
364        }),
365    }
366}
367
368pub(super) async fn await_with_generation_context<F: Future>(
369    driver: &DriverControl,
370    context: &InvocationContext,
371    generation_cancellation: CancellationToken,
372    capability: &'static str,
373    future: F,
374) -> Result<F::Output, RuntimeFailure> {
375    ensure_context_active(driver, context)?;
376    if generation_cancellation.is_cancelled() {
377        return Err(RuntimeFailure::Unavailable { capability });
378    }
379
380    let work = future.fuse();
381    futures::pin_mut!(work);
382    if let Some(output) = poll_fn(|context| match work.as_mut().poll(context) {
383        Poll::Ready(output) => Poll::Ready(Some(output)),
384        Poll::Pending => Poll::Ready(None),
385    })
386    .await
387    {
388        return Ok(output);
389    }
390    let cancellation = context.cancellation.cancelled().fuse();
391    let generation_cancellation = generation_cancellation.cancelled().fuse();
392    let deadline: LocalBoxFuture<'static, ()> = context.deadline().map_or_else(
393        || Box::pin(pending::<()>()) as LocalBoxFuture<'static, ()>,
394        |deadline| (driver.sleep_until)(deadline),
395    );
396    let deadline = deadline.fuse();
397    futures::pin_mut!(cancellation, generation_cancellation, deadline);
398
399    match select(
400        select(select(work, cancellation), generation_cancellation),
401        deadline,
402    )
403    .await
404    {
405        Either::Left((Either::Left((Either::Left((output, _)), _)), _)) => Ok(output),
406        Either::Left((Either::Left((Either::Right(((), _)), _)), _)) => {
407            Err(RuntimeFailure::Cancelled {
408                request_id: context.request_id(),
409            })
410        }
411        Either::Left((Either::Right(((), _)), _)) => {
412            Err(RuntimeFailure::Unavailable { capability })
413        }
414        Either::Right(((), _)) => Err(RuntimeFailure::DeadlineExceeded {
415            request_id: context.request_id(),
416        }),
417    }
418}
419
420pub(super) fn is_module_failure(error: &RuntimeFailure) -> bool {
421    matches!(error, RuntimeFailure::ModuleFailure { .. })
422}
423
424pub(super) fn schedule_module_supervision_after_failure(
425    runtime: &Rc<NativeAppRuntime>,
426    instance_key: &str,
427    error: RuntimeFailure,
428) -> RuntimeFailure {
429    if is_module_failure(&error)
430        && begin_module_supervision(runtime, instance_key).unwrap_or(false)
431        && let Err(schedule_error) = schedule_module_supervision(runtime, instance_key)
432    {
433        return handle_supervision_schedule_failure(runtime, instance_key, schedule_error);
434    }
435    error
436}
437
438pub(super) fn handle_supervision_schedule_failure(
439    runtime: &Rc<NativeAppRuntime>,
440    instance_key: &str,
441    error: RuntimeFailure,
442) -> RuntimeFailure {
443    let must_fail = runtime
444        .supervision
445        .borrow()
446        .get(instance_key)
447        .is_some_and(|state| state.criticality.is_critical() || state.required_path);
448    if must_fail {
449        runtime.terminal_failure.replace(Some(error.clone()));
450        runtime.begin_shutdown();
451    }
452    error
453}
454
455pub(super) fn ensure_context_active(
456    driver: &DriverControl,
457    context: &InvocationContext,
458) -> Result<(), RuntimeFailure> {
459    if context.is_cancelled() {
460        return Err(RuntimeFailure::Cancelled {
461            request_id: context.request_id(),
462        });
463    }
464    if context.is_expired((driver.now)()) {
465        return Err(RuntimeFailure::DeadlineExceeded {
466            request_id: context.request_id(),
467        });
468    }
469    Ok(())
470}
471
472/// The result of bounded cleanup after a graceful shutdown request.
473#[derive(Clone, Debug, Eq, PartialEq)]
474pub enum ShutdownOutcome {
475    /// Every managed task, resource, and Module generation was cleaned up.
476    Clean,
477    /// Cleanup completed but a Module or resource reported a Runtime Failure.
478    RuntimeFailure { error: RuntimeFailure },
479    /// The global shutdown deadline expired; remaining work was terminated.
480    Timeout,
481}
482
483/// A successful terminal result returned to the embedding Runner.
484#[derive(Clone, Debug, Eq, PartialEq)]
485pub enum TerminalOutcome {
486    /// The App completed a bounded clean shutdown.
487    CleanShutdown,
488    /// The App could not start because a Module reported a startup failure.
489    StartupFailure { error: RuntimeFailure },
490    /// The running App reported a Runtime Failure during terminal cleanup.
491    RuntimeFailure { error: RuntimeFailure },
492    /// The running App failed and cleanup reported a second Runtime Failure.
493    RuntimeFailureDuringShutdown {
494        error: RuntimeFailure,
495        cleanup_error: RuntimeFailure,
496    },
497    /// The running App failed and cleanup then exceeded its global deadline.
498    RuntimeFailureWithShutdownTimeout { error: RuntimeFailure },
499    /// The App exceeded its one global shutdown deadline.
500    ShutdownTimeout,
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506    use crate::DeterministicDriver;
507
508    #[test]
509    fn ready_work_does_not_register_cancellation_waiters() {
510        let driver = DeterministicDriver::new();
511        let control = DriverControl::new(&driver);
512        let caller_cancellation = CancellationToken::new();
513        let generation_cancellation = CancellationToken::new();
514        let context = InvocationContext::new(
515            1,
516            Some(Duration::from_millis(10)),
517            caller_cancellation.clone(),
518        );
519        let observed = Rc::new(Cell::new((usize::MAX, usize::MAX)));
520        let observed_waiters = observed.clone();
521        let work_caller = caller_cancellation.clone();
522        let work_generation = generation_cancellation.clone();
523        let work = poll_fn(move |_| {
524            observed_waiters.set((
525                work_caller.state.waiters.borrow().len(),
526                work_generation.state.waiters.borrow().len(),
527            ));
528            Poll::Ready("done")
529        });
530
531        let outcome = driver.run(await_with_generation_context(
532            &control,
533            &context,
534            generation_cancellation,
535            "test.capability",
536            work,
537        ));
538
539        assert_eq!(outcome, Ok("done"));
540        assert_eq!(observed.get(), (0, 0));
541    }
542}