Skip to main content

lenso_native_adapter/
generation_facilities.rs

1//! Explicit typed authority shared by one generated Host generation.
2use std::{
3    any::{Any, TypeId},
4    cell::{Cell, RefCell},
5    collections::{BTreeMap, BTreeSet},
6    fmt,
7    rc::Rc,
8    task::Poll,
9};
10
11#[cfg(test)]
12mod tests;
13
14use futures::{
15    future::{LocalBoxFuture, poll_fn},
16    task::AtomicWaker,
17};
18use lenso_kernel::RuntimeFailure;
19use serde_json::Value;
20
21/// Owner-defined authority. Called after admission closes and every lease drains.
22pub trait NativeGenerationResource: fmt::Debug + 'static {
23    fn revoke(&self) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>>;
24}
25
26struct Entry {
27    type_id: TypeId,
28    configuration: Value,
29    value: Rc<dyn Any>,
30    revoke: Rc<dyn Fn() -> LocalBoxFuture<'static, Result<(), RuntimeFailure>>>,
31}
32
33#[derive(Default)]
34struct State {
35    closed: Cell<bool>,
36    leases: Cell<usize>,
37    wake: AtomicWaker,
38    entries: RefCell<BTreeMap<(String, String), Entry>>,
39    order: RefCell<Vec<(String, String)>>,
40    constructing: RefCell<BTreeSet<(String, String)>>,
41    retirement: RefCell<Option<LocalBoxFuture<'static, Result<(), RuntimeFailure>>>>,
42    outcome: RefCell<Option<Result<(), RuntimeFailure>>>,
43    retiring: Cell<bool>,
44}
45
46struct PollGuard(Rc<State>);
47impl Drop for PollGuard {
48    fn drop(&mut self) {
49        self.0.retiring.set(false);
50    }
51}
52
53struct Reservation {
54    state: Rc<State>,
55    key: (String, String),
56}
57impl Drop for Reservation {
58    fn drop(&mut self) {
59        self.state.constructing.borrow_mut().remove(&self.key);
60        self.state.wake.wake();
61    }
62}
63
64/// Host-owned guard; a new guard is required for every complete App generation.
65pub struct NativeGenerationScope {
66    state: Rc<State>,
67}
68
69impl fmt::Debug for NativeGenerationScope {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.debug_struct("NativeGenerationScope")
72            .field("closed", &self.state.closed.get())
73            .field("leases", &self.state.leases.get())
74            .finish_non_exhaustive()
75    }
76}
77
78impl Default for NativeGenerationScope {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl NativeGenerationScope {
85    pub fn new() -> Self {
86        Self {
87            state: Rc::default(),
88        }
89    }
90
91    /// Private resolver input for source-owned factories; not an ambient registry.
92    pub fn context(&self) -> NativeGenerationContext {
93        NativeGenerationContext {
94            state: self.state.clone(),
95        }
96    }
97
98    /// Stops new authority acquisition before the Kernel begins shutdown.
99    pub fn close(&self) {
100        self.state.closed.set(true);
101        self.state.wake.wake();
102    }
103
104    /// Drain and revoke exactly once. Dropping this future retains physical cleanup.
105    pub async fn retire(&self) -> Result<(), RuntimeFailure> {
106        self.close();
107        if self.state.retiring.replace(true) {
108            return Err(invalid("generation retirement already being polled"));
109        }
110        let _guard = PollGuard(self.state.clone());
111        poll_fn(|cx| {
112            if let Some(result) = self.state.outcome.borrow().as_ref() {
113                return Poll::Ready(result.clone());
114            }
115            self.state.wake.register(cx.waker());
116            if self.state.leases.get() != 0 || !self.state.constructing.borrow().is_empty() {
117                return Poll::Pending;
118            }
119            if self.state.retirement.borrow().is_none() {
120                let resources: Vec<_> = self
121                    .state
122                    .order
123                    .borrow()
124                    .iter()
125                    .map(|key| self.state.entries.borrow()[key].revoke.clone())
126                    .collect();
127                *self.state.retirement.borrow_mut() = Some(Box::pin(async move {
128                    let mut failure = None;
129                    for resource in resources.into_iter().rev() {
130                        if let Err(error) = resource().await
131                            && failure.is_none()
132                        {
133                            failure = Some(error);
134                        }
135                    }
136                    failure.map_or(Ok(()), Err)
137                }));
138            }
139            let result = self
140                .state
141                .retirement
142                .borrow_mut()
143                .as_mut()
144                .expect("retirement initialized")
145                .as_mut()
146                .poll(cx);
147            if let Poll::Ready(result) = result {
148                if result.is_ok() {
149                    self.state.entries.borrow_mut().clear();
150                    self.state.order.borrow_mut().clear();
151                }
152                *self.state.outcome.borrow_mut() = Some(result.clone());
153                Poll::Ready(result)
154            } else {
155                Poll::Pending
156            }
157        })
158        .await
159    }
160}
161
162impl Drop for NativeGenerationScope {
163    fn drop(&mut self) {
164        self.close();
165        // Uncertain/cancelled cleanup must never implicitly destroy authority or
166        // its pending physical future. The process can retire this quarantine.
167        if !self.state.entries.borrow().is_empty() || !self.state.constructing.borrow().is_empty() {
168            std::mem::forget(self.state.clone());
169        }
170    }
171}
172
173/// Explicit Host-generation context passed only to admitted owner factories.
174#[derive(Clone)]
175pub struct NativeGenerationContext {
176    state: Rc<State>,
177}
178
179impl fmt::Debug for NativeGenerationContext {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.debug_struct("NativeGenerationContext")
182            .finish_non_exhaustive()
183    }
184}
185
186impl NativeGenerationContext {
187    pub fn resolve<T: NativeGenerationResource>(
188        &self,
189        owner: &str,
190        reference: &str,
191        configuration: &Value,
192        create: impl FnOnce() -> Result<T, RuntimeFailure>,
193    ) -> Result<NativeGenerationHandle<T>, RuntimeFailure> {
194        if self.state.closed.get() || !valid_reference(owner) || !valid_reference(reference) {
195            return Err(invalid("invalid or closed generation authority reference"));
196        }
197        let key = (owner.to_owned(), reference.to_owned());
198        if let Some(entry) = self.state.entries.borrow().get(&key) {
199            if entry.type_id != TypeId::of::<T>() || &entry.configuration != configuration {
200                return Err(invalid(
201                    "generation authority owner/reference has conflicting type or configuration",
202                ));
203            }
204            return Ok(NativeGenerationHandle {
205                value: entry
206                    .value
207                    .clone()
208                    .downcast::<T>()
209                    .map_err(|_| invalid("generation authority type mismatch"))?,
210                state: self.state.clone(),
211            });
212        }
213        if !self.state.constructing.borrow_mut().insert(key.clone()) {
214            return Err(invalid(
215                "generation authority constructor reentered its own reference",
216            ));
217        }
218        let _reservation = Reservation {
219            state: self.state.clone(),
220            key: key.clone(),
221        };
222        let value = Rc::new(create()?);
223        let revoke = value.clone();
224        self.state.order.borrow_mut().push(key.clone());
225        self.state.entries.borrow_mut().insert(
226            key,
227            Entry {
228                type_id: TypeId::of::<T>(),
229                configuration: configuration.clone(),
230                value: value.clone(),
231                revoke: Rc::new(move || revoke.revoke()),
232            },
233        );
234        if self.state.closed.get() {
235            return Err(invalid(
236                "generation authority closed during construction; rollback required",
237            ));
238        }
239        Ok(NativeGenerationHandle {
240            value,
241            state: self.state.clone(),
242        })
243    }
244}
245
246/// Typed authority grant; cloning it does not grant admission or extend a lease.
247pub struct NativeGenerationHandle<T> {
248    value: Rc<T>,
249    state: Rc<State>,
250}
251
252impl<T> Clone for NativeGenerationHandle<T> {
253    fn clone(&self) -> Self {
254        Self {
255            value: self.value.clone(),
256            state: self.state.clone(),
257        }
258    }
259}
260impl<T> fmt::Debug for NativeGenerationHandle<T> {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        f.debug_struct("NativeGenerationHandle")
263            .finish_non_exhaustive()
264    }
265}
266
267impl<T> NativeGenerationHandle<T> {
268    /// Retain this lease for the complete operation, including spawned work.
269    pub fn acquire(&self) -> Result<NativeGenerationLease<T>, RuntimeFailure> {
270        if self.state.closed.get() {
271            return Err(invalid("generation authority admission is closed"));
272        }
273        self.state.leases.set(
274            self.state
275                .leases
276                .get()
277                .checked_add(1)
278                .ok_or_else(|| invalid("generation authority lease limit"))?,
279        );
280        Ok(NativeGenerationLease {
281            value: self.value.clone(),
282            state: self.state.clone(),
283        })
284    }
285}
286
287pub struct NativeGenerationLease<T> {
288    value: Rc<T>,
289    state: Rc<State>,
290}
291impl<T> fmt::Debug for NativeGenerationLease<T> {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        f.debug_struct("NativeGenerationLease")
294            .finish_non_exhaustive()
295    }
296}
297impl<T> std::ops::Deref for NativeGenerationLease<T> {
298    type Target = T;
299    fn deref(&self) -> &T {
300        &self.value
301    }
302}
303impl<T> Drop for NativeGenerationLease<T> {
304    fn drop(&mut self) {
305        self.state.leases.set(self.state.leases.get() - 1);
306        self.state.wake.wake();
307    }
308}
309
310fn invalid(detail: &str) -> RuntimeFailure {
311    RuntimeFailure::InvalidResolvedPlan {
312        detail: detail.into(),
313    }
314}
315
316fn valid_reference(reference: &str) -> bool {
317    !reference.is_empty()
318        && reference.len() <= 128
319        && reference
320            .bytes()
321            .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-'))
322}