char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somunify public module registration and declarations6534c3e

main
8.2 KiB242 linesraw
1use std::cell::RefCell;
2use std::future::Future;
3use std::rc::Rc;
4use std::sync::Arc;
5use std::sync::atomic::Ordering;
6use std::task::Poll;
7use std::time::Duration;
8
9use avril_caps::Capabilities;
10use avril_core::{Extension, ModuleLoader, Runtime, RuntimeOptions, Snapshot};
11use avril_ext_workers::{Control, WorkerScope};
12use avril_host::environment::Environment;
13use avril_host::messaging::{Broker, Endpoint};
14use futures::future::{Either, select};
15use tokio_util::sync::CancellationToken;
16
17use crate::modules::ModuleGraph;
18use crate::registry::PackageStore;
19
20mod snapshots;
21pub use snapshots::create as create_snapshot;
22
23#[cfg(test)]
24mod tests;
25
26#[derive(Clone)]
27pub struct Settings {
28    pub snapshot: Option<Arc<Snapshot>>,
29    pub storage: avril_webstorage::Persistent,
30    pub caps: Arc<Capabilities>,
31    pub environment: Environment,
32    pub args: Vec<String>,
33    pub store: Arc<PackageStore>,
34    pub runtime: tokio::runtime::Handle,
35    pub broker: Arc<Broker>,
36    pub process_signal: avril_ext_process::SignalMode,
37}
38
39pub fn runtime(
40    graph: Rc<ModuleGraph>,
41    settings: Settings,
42    stdio: Rc<avril_ext_stdio::Stdio>,
43    listener: Option<avril_http::Address>,
44    test: Option<avril_ext_test::Shared>,
45) -> Result<Runtime, Box<dyn std::error::Error>> {
46    create_runtime(graph, settings, stdio, None, listener, test)
47}
48
49fn create_runtime(
50    graph: Rc<ModuleGraph>,
51    settings: Settings,
52    stdio: Rc<avril_ext_stdio::Stdio>,
53    worker: Option<WorkerScope>,
54    listener: Option<avril_http::Address>,
55    test: Option<avril_ext_test::Shared>,
56) -> Result<Runtime, Box<dyn std::error::Error>> {
57    let snapshot = settings.snapshot.clone();
58    let extensions = extensions(graph.clone(), settings, stdio, worker, listener, test)?;
59    let options = RuntimeOptions {
60        extensions,
61        module_loader: Some(graph),
62    };
63    let mut runtime = match snapshot.as_deref() {
64        Some(snapshot) => Runtime::from_snapshot(options, snapshot)?,
65        None => Runtime::new(options)?,
66    };
67    crate::stdlib::register(&mut runtime)?;
68    Ok(runtime)
69}
70
71fn extensions(
72    graph: Rc<ModuleGraph>,
73    settings: Settings,
74    stdio: Rc<avril_ext_stdio::Stdio>,
75    worker: Option<WorkerScope>,
76    listener: Option<avril_http::Address>,
77    test: Option<avril_ext_test::Shared>,
78) -> Result<Vec<Extension>, Box<dyn std::error::Error>> {
79    let environment = Rc::new(RefCell::new(settings.environment.clone()));
80    // Outside `avril test`, `test()` registers harmlessly and never runs.
81    let test = test.unwrap_or_else(|| avril_ext_test::idle(&stdio));
82    let (websocket, sockets) =
83        avril_ext_websocket::init(settings.runtime.clone(), settings.caps.clone())?;
84    let (file, files) = avril_ext_file::init(
85        settings.runtime.clone(),
86        settings.caps.clone(),
87        stdio.interrupt_flag(),
88    );
89    let top_level = worker.is_none();
90    let mut extensions = vec![
91        avril_ext_web::with_broker(settings.broker.clone()),
92        avril_ext_timers::init()?,
93        avril_ext_url::init(),
94        avril_ext_urlpattern::init(),
95        avril_ext_webstreams::init(),
96        avril_compression::init(),
97        avril_webcrypto::init(),
98        avril_ext_fjson::init(),
99        avril_ext_kv::init(settings.runtime.clone(), settings.caps.clone()),
100        avril_ext_task::init(),
101        avril_ffi::init(settings.caps.clone()),
102        avril_ext_encoding::init(),
103        avril_ext_blob::init(),
104        avril_ext_form_data::init(),
105        avril_ext_http_types::init(),
106        avril_ext_fetch::init(
107            settings.runtime.clone(),
108            settings.caps.clone(),
109            graph.entry_url(),
110        ),
111        avril_ext_net::init(settings.runtime.clone(), settings.caps.clone()),
112        avril_ext_http::init(
113            settings.runtime.clone(),
114            settings.caps.clone(),
115            sockets,
116            files.clone(),
117            listener,
118        ),
119        websocket,
120        avril_ext_wasmstreaming::init(),
121        avril_ext_structured_clone::init(),
122        file,
123        avril_ext_process::init(
124            settings.runtime.clone(),
125            settings.caps.clone(),
126            environment.clone(),
127            files,
128            worker
129                .as_ref()
130                .map_or_else(CancellationToken::new, |worker| {
131                    worker.control.process_cancellation()
132                }),
133            worker.is_some(),
134            settings.process_signal,
135        ),
136        stdio.extension(),
137        avril_ext_test::init(test),
138        avril_ext_base::init(
139            settings.caps.clone(),
140            environment.clone(),
141            settings.args.clone(),
142        ),
143        avril_ext_console::init(stdio),
144        avril_ext_path::init(),
145        avril_ext_workers::init(
146            move || graph.entry_url(),
147            settings.caps.clone(),
148            settings.broker.clone(),
149            Arc::new(settings.clone()),
150            environment,
151            worker,
152        ),
153    ];
154    // Web Storage is a top-level-only API; worker runtimes omit it entirely.
155    if top_level {
156        extensions.push(avril_webstorage::init(settings.storage.clone()));
157    }
158    Ok(extensions)
159}
160
161impl avril_ext_workers::Launch for Settings {
162    fn run(
163        &self,
164        request: avril_ext_workers::Request,
165        control: Arc<Control>,
166        inbox: Arc<Endpoint>,
167    ) -> Result<(), Box<dyn std::error::Error>> {
168        let mut settings = self.clone();
169        settings.caps = request.caps;
170        settings.environment = request.environment;
171        let specifier = request.specifier;
172        let referrer = request.referrer;
173        let name = request.name;
174        let authority = avril_runtime::authority::SourceAuthority::execute(settings.caps.clone());
175        let graph = Rc::new(ModuleGraph::open_shared(authority, settings.store.clone()));
176        let host = settings.runtime.clone();
177        let entry = host.block_on(async {
178            match select(
179                Box::pin(graph.prepare(specifier, referrer)),
180                Box::pin(control.cancelled()),
181            )
182            .await
183            {
184                Either::Left((entry, _)) => entry.map(Some),
185                Either::Right(_) => Ok(None),
186            }
187        })?;
188        let Some(entry) = entry else {
189            return Ok(());
190        };
191        graph.set_entry(&entry);
192        let url =
193            avril_core::ModuleLoader::module_url(&*graph, &entry).unwrap_or_else(|| entry.clone());
194        let terminated = control.termination_flag();
195        let stdio = avril_ext_stdio::Stdio::new_for_worker(terminated.clone())?;
196        let mut runtime = create_runtime(
197            graph,
198            settings,
199            stdio.clone(),
200            Some(WorkerScope {
201                control: control.clone(),
202                inbox,
203                url,
204                name,
205            }),
206            None,
207            None,
208        )?;
209        control.set_isolate(Some(runtime.isolate_handle()));
210        let result = runtime.start_module(&entry).and_then(|()| {
211            let mut background_delay = None;
212            host.block_on(futures::future::poll_fn(|cx| {
213                control.register_waker(cx.waker());
214                if control.is_stopped() {
215                    return Poll::Ready(Ok(()));
216                }
217                let background_pending = runtime.has_pending_background_tasks();
218                if let Err(error) = runtime.poll_event_loop(cx) {
219                    return Poll::Ready(Err(error));
220                }
221                // The default V8 platform does not notify the host about foreground tasks.
222                if background_pending || runtime.has_pending_background_tasks() {
223                    let delay = background_delay.get_or_insert_with(|| {
224                        Box::pin(tokio::time::sleep(Duration::from_millis(20)))
225                    });
226                    if delay.as_mut().poll(cx).is_ready() {
227                        background_delay = None;
228                        cx.waker().wake_by_ref();
229                    }
230                } else {
231                    background_delay = None;
232                }
233                Poll::Pending
234            }))
235        });
236        let flushed = stdio.flush_interruptible(|| terminated.load(Ordering::SeqCst));
237        control.set_isolate(None);
238        result?;
239        flushed?;
240        Ok(())
241    }
242}