Skip to main content

dope_runtime/executor/
session.rs

1use std::{io, marker, pin, process, time};
2
3use dope_core::driver::{self, lifecycle};
4use o3::cell::brand;
5
6use crate::{client, executor, random, run, shutdown};
7
8const APP_SHUTDOWN_DRAIN: time::Duration = time::Duration::from_secs(2);
9
10pub struct Session<'scope, 'd: 'scope, S = (), Q = ()> {
11    storage: pin::Pin<&'d S>,
12    core: &'scope mut Core<'d, Q>,
13    lifecycle: &'scope Q,
14}
15
16pub(super) struct Core<'d, Q> {
17    scope: lifecycle::Scope<'d>,
18    seed: random::Seed,
19    retained_events: crate::Events<'d>,
20    _shutdown: marker::PhantomData<fn(Q) -> Q>,
21}
22
23pub struct Application<'app, 'd: 'app, D, Q = ()> {
24    core: &'app mut Core<'d, Q>,
25    installed: crate::Installed<'app, 'd, D>,
26}
27
28struct AppScope<'app, 'd: 'app, D, Q>
29where
30    D: executor::Application<'d>,
31{
32    core: &'app mut Core<'d, Q>,
33    installed: crate::Installed<'app, 'd, D>,
34}
35
36impl<'app, 'd: 'app, D, Q> AppScope<'app, 'd, D, Q>
37where
38    D: executor::Application<'d>,
39{
40    fn shutdown(&mut self) -> executor::raw::Pending<'app, 'd, D> {
41        let installed = self.installed;
42        let core = &mut *self.core;
43        let completions = &mut core.retained_events;
44        core.scope.with_turn(|token, mut driver, turn| {
45            run::Run::new(&mut driver, installed, token, turn, completions).shutdown()
46        })
47    }
48
49    fn drain(&mut self, pending: &executor::raw::Pending<'app, 'd, D>) -> io::Result<()> {
50        let installed = self.installed;
51        let core = &mut *self.core;
52        let completions = &mut core.retained_events;
53        core.scope.with_turn(|token, mut driver, turn| {
54            run::Run::new(&mut driver, installed, token, turn, completions)
55                .drain_for(APP_SHUTDOWN_DRAIN, pending)
56        })
57    }
58
59    fn finalize(&mut self, pending: executor::raw::Pending<'app, 'd, D>) {
60        let core = &mut *self.core;
61        drop(core.retained_events.take());
62        let (token, finalization) = match core.scope.final_quiescence() {
63            Ok(finalization) => finalization,
64            Err(_) => process::abort(),
65        };
66        let finish = pending.finish(finalization);
67        self.installed.finish(token, finish);
68    }
69
70    fn finish(&mut self) -> io::Result<()> {
71        let pending = self.shutdown();
72        if self.drain(&pending).is_err() {
73            process::abort();
74        }
75        self.finalize(pending);
76        Ok(())
77    }
78}
79
80impl<Q> Drop for Core<'_, Q> {
81    fn drop(&mut self) {
82        drop(self.retained_events.take());
83        if self.scope.final_quiescence().is_err() {
84            process::abort();
85        }
86    }
87}
88
89impl<'scope, 'd: 'scope, S, Q> Session<'scope, 'd, S, Q> {
90    pub(super) fn new(
91        storage: pin::Pin<&'d S>,
92        core: &'scope mut Core<'d, Q>,
93        lifecycle: &'scope Q,
94    ) -> Self {
95        Self {
96            storage,
97            core,
98            lifecycle,
99        }
100    }
101
102    /// Storage for the complete driver domain.
103    /// Its domain lifetime is independent of the temporary session borrow.
104    pub fn storage(&self) -> &'d S
105    where
106        S: 'd,
107    {
108        self.storage.get_ref()
109    }
110
111    /// Returns the session's exclusive driver context for staging resources.
112    ///
113    /// The access lifetime is tied to this mutable session borrow. Consequently
114    /// the context cannot escape the generative executor scope, overlap another
115    /// driver context, or coexist with an installed application.
116    ///
117    /// ```compile_fail
118    /// use dope_core::driver;
119    /// use dope_runtime::executor::Executor;
120    ///
121    /// fn escape(executor: Executor) -> driver::Context<'static, 'static> {
122    ///     executor.enter(|mut session| session.driver_access())
123    /// }
124    /// ```
125    ///
126    /// ```compile_fail
127    /// use dope_runtime::executor::{self, Application};
128    ///
129    /// fn overlap<'scope, 'd, D>(
130    ///     session: &mut executor::session::Session<'scope, 'd>,
131    ///     app: D,
132    /// ) where
133    ///     'd: 'scope,
134    ///     D: Application<'d>,
135    /// {
136    ///     let driver = session.driver_access();
137    ///     let _ = session.with_app(app, |_| {});
138    ///     drop(driver);
139    /// }
140    /// ```
141    ///
142    /// ```compile_fail
143    /// use dope_runtime::executor::session::Session;
144    ///
145    /// fn duplicate<'scope, 'd>(session: &mut Session<'scope, 'd>)
146    /// where
147    ///     'd: 'scope,
148    /// {
149    ///     let first = session.driver_access();
150    ///     let second = session.driver_access();
151    ///     drop((first, second));
152    /// }
153    /// ```
154    pub fn driver_access(&mut self) -> driver::Context<'_, 'd> {
155        self.core.scope.context()
156    }
157
158    /// Issues a keyed hash builder bound to this exact driver domain.
159    ///
160    /// ```compile_fail
161    /// use dope_runtime::{
162    ///     executor::Executor,
163    ///     random::{Domain, HashState},
164    /// };
165    ///
166    /// fn escape(executor: Executor) -> HashState<'static> {
167    ///     executor.enter(|mut session| session.hash_state(Domain::new(0)))
168    /// }
169    /// ```
170    pub fn hash_state(&mut self, domain: random::Domain) -> random::HashState<'d> {
171        let seed = self.core.seed;
172        seed.bind(self.core.scope.token(), domain)
173    }
174
175    fn token(&mut self) -> &mut brand::Token<'d> {
176        self.core.scope.token()
177    }
178
179    /// Installs one application for `f` and finalizes it on normal return.
180    /// # Panics
181    /// Application or callback panics are terminal; session reuse is not guaranteed.
182    pub fn with_app<D, R>(
183        &mut self,
184        app: D,
185        f: impl for<'app> FnOnce(Application<'app, 'd, D, Q>) -> R,
186    ) -> io::Result<R>
187    where
188        D: executor::Application<'d>,
189        Q: executor::Startup,
190    {
191        let (output, finalized) = {
192            let cell = pin::pin!(brand::Value::new(app));
193            debug_assert!(self.core.retained_events.is_none());
194            let installed = crate::Installed::install(cell.as_ref(), self.token());
195            let mut scope = AppScope {
196                core: &mut *self.core,
197                installed,
198            };
199            self.lifecycle.installed();
200            let output = f(Application {
201                core: &mut *scope.core,
202                installed: scope.installed.reborrow(),
203            });
204            let finalized = scope.finish();
205            (output, finalized)
206        };
207        let reaped = self.core.scope.reap_finalized();
208        if reaped.is_err() {
209            process::abort();
210        }
211        finalized?;
212        Ok(output)
213    }
214
215    /// Runs a composition with a pinned provider owner and its scoped client.
216    /// The higher-ranked application lifetime prevents either from escaping.
217    pub fn with_provider<O, P, C>(
218        &mut self,
219        owner: O,
220        select: impl for<'borrow> FnOnce(pin::Pin<&'borrow O>) -> pin::Pin<&'borrow P>,
221        composition: C,
222    ) -> C::Output
223    where
224        P: client::Provider<'d>,
225        C: client::Composition<'scope, 'd, S, Q, O, P>,
226    {
227        let mut owner = pin::pin!(owner);
228        let client = P::provide(select(owner.as_ref()), client::Scope::new());
229        let root = client::Anchor::new(owner.as_mut());
230        C::compose(composition, client, root, self)
231    }
232}
233
234impl<'app, 'd: 'app, D, Q> Application<'app, 'd, D, Q>
235where
236    D: executor::Application<'d>,
237{
238    pub fn drive<R>(&mut self, root: R) -> io::Result<R::Output>
239    where
240        R: executor::Root<'d>,
241    {
242        let installed = self.installed;
243        let core = &mut *self.core;
244        let completions = &mut core.retained_events;
245        core.scope.with_turn(|token, mut driver, turn| {
246            run::Run::new(&mut driver, installed, token, turn, completions).block_on(root)
247        })
248    }
249
250    /// Issues a provider client tied to the surrounding application scope.
251    pub fn client<P>(
252        &mut self,
253        select: impl for<'borrow> FnOnce(pin::Pin<&'borrow D>) -> pin::Pin<&'borrow P>,
254    ) -> P::Client<'app>
255    where
256        'd: 'app,
257        P: client::Provider<'d>,
258    {
259        let provider = select(self.installed.borrow_pin(self.core.scope.token()));
260        P::provide(provider, client::Scope::new())
261    }
262
263    pub fn run(&mut self) -> io::Result<shutdown::Requested<Q>> {
264        let installed = self.installed;
265        let core = &mut *self.core;
266        let completions = &mut core.retained_events;
267        core.scope.with_turn(|token, mut driver, turn| {
268            run::Run::new(&mut driver, installed, token, turn, completions).run()
269        })?;
270        Ok(shutdown::Requested::new())
271    }
272}
273
274impl<'d, Q> Core<'d, Q> {
275    pub(super) fn new(scope: lifecycle::Scope<'d>, seed: random::Seed) -> Self {
276        Self {
277            scope,
278            seed,
279            retained_events: None,
280            _shutdown: marker::PhantomData,
281        }
282    }
283}