wasmer_wasix/runtime/
mod.rs

1pub mod module_cache;
2pub mod package_loader;
3pub mod resolver;
4pub mod task_manager;
5
6pub use self::task_manager::{SpawnType, VirtualTaskManager};
7use self::{module_cache::CacheError, task_manager::InlineWaker};
8use module_cache::HashedModuleData;
9use wasmer_config::package::SuggestedCompilerOptimizations;
10use wasmer_types::target::UserCompilerOptimizations as WasmerSuggestedCompilerOptimizations;
11
12use std::{
13    fmt,
14    ops::Deref,
15    sync::{Arc, Mutex},
16};
17
18use futures::future::BoxFuture;
19use virtual_net::{DynVirtualNetworking, VirtualNetworking};
20use wasmer::{CompileError, Engine, Module, RuntimeError};
21use wasmer_wasix_types::wasi::ExitCode;
22
23#[cfg(feature = "journal")]
24use crate::journal::{DynJournal, DynReadableJournal};
25use crate::{
26    SpawnError, WasiTtyState,
27    bin_factory::BinaryPackageCommand,
28    http::{DynHttpClient, HttpClient},
29    os::TtyBridge,
30    runtime::{
31        module_cache::{ModuleCache, ThreadLocalCache},
32        package_loader::{PackageLoader, UnsupportedPackageLoader},
33        resolver::{BackendSource, MultiSource, Source},
34    },
35};
36
37#[derive(Clone)]
38pub enum TaintReason {
39    UnknownWasiVersion,
40    NonZeroExitCode(ExitCode),
41    RuntimeError(RuntimeError),
42    DlSymbolResolutionFailed(String),
43}
44
45/// Runtime components used when running WebAssembly programs.
46///
47/// Think of this as the "System" in "WebAssembly Systems Interface".
48#[allow(unused_variables)]
49pub trait Runtime
50where
51    Self: fmt::Debug,
52{
53    /// Provides access to all the networking related functions such as sockets.
54    fn networking(&self) -> &DynVirtualNetworking;
55
56    /// Retrieve the active [`VirtualTaskManager`].
57    fn task_manager(&self) -> &Arc<dyn VirtualTaskManager>;
58
59    /// A package loader.
60    fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
61        Arc::new(UnsupportedPackageLoader)
62    }
63
64    /// A cache for compiled modules.
65    fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
66        // Return a cache that uses a thread-local variable. This isn't ideal
67        // because it allows silently sharing state, possibly between runtimes.
68        //
69        // That said, it means people will still get *some* level of caching
70        // because each cache returned by this default implementation will go
71        // through the same thread-local variable.
72        Arc::new(ThreadLocalCache::default())
73    }
74
75    /// The package registry.
76    fn source(&self) -> Arc<dyn Source + Send + Sync>;
77
78    /// Get a [`wasmer::Engine`] for module compilation.
79    fn engine(&self) -> Engine {
80        Engine::default()
81    }
82
83    fn engine_with_suggested_opts(
84        &self,
85        suggested_opts: &SuggestedCompilerOptimizations,
86    ) -> Result<Engine, CompileError> {
87        let mut engine = self.engine();
88        engine.with_opts(&WasmerSuggestedCompilerOptimizations {
89            pass_params: suggested_opts.pass_params,
90        })?;
91        Ok(engine)
92    }
93
94    /// Create a new [`wasmer::Store`].
95    fn new_store(&self) -> wasmer::Store {
96        cfg_if::cfg_if! {
97            if #[cfg(feature = "sys")] {
98                wasmer::Store::new(self.engine())
99            } else {
100                wasmer::Store::default()
101            }
102        }
103    }
104
105    /// Get a custom HTTP client
106    fn http_client(&self) -> Option<&DynHttpClient> {
107        None
108    }
109
110    /// Get access to the TTY used by the environment.
111    fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
112        None
113    }
114
115    /// Load the module for a command.
116    ///
117    /// Will load the module from the cache if possible, otherwise will compile.
118    ///
119    /// NOTE: This always be preferred over [`Self::load_module`] to avoid
120    /// re-hashing the module!
121    fn load_command_module(
122        &self,
123        cmd: &BinaryPackageCommand,
124    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
125        let module_cache = self.module_cache();
126        let data = HashedModuleData::from_command(cmd);
127
128        let engine = match self.engine_with_suggested_opts(&cmd.suggested_compiler_optimizations) {
129            Ok(engine) => engine,
130            Err(error) => {
131                return Box::pin(async move {
132                    Err(SpawnError::CompileError {
133                        module_hash: *data.hash(),
134                        error,
135                    })
136                });
137            }
138        };
139
140        let task = async move { load_module(&engine, &module_cache, &data).await };
141
142        Box::pin(task)
143    }
144
145    /// Sync version of [`Self::load_command_module`].
146    fn load_command_module_sync(&self, cmd: &BinaryPackageCommand) -> Result<Module, SpawnError> {
147        InlineWaker::block_on(self.load_command_module(cmd))
148    }
149
150    /// Load a WebAssembly module from raw bytes.
151    ///
152    /// Will load the module from the cache if possible, otherwise will compile.
153    #[deprecated(
154        since = "0.601.0",
155        note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead"
156    )]
157    fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result<Module, SpawnError>> {
158        let engine = self.engine();
159        let module_cache = self.module_cache();
160        let data = HashedModuleData::new(wasm.to_vec());
161        let task = async move { load_module(&engine, &module_cache, &data).await };
162        Box::pin(task)
163    }
164
165    /// Synchronous version of [`Self::load_module`].
166    #[deprecated(
167        since = "0.601.0",
168        note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead"
169    )]
170    fn load_module_sync(&self, wasm: &[u8]) -> Result<Module, SpawnError> {
171        #[allow(deprecated)]
172        InlineWaker::block_on(self.load_module(wasm))
173    }
174
175    /// Load a WebAssembly module from pre-hashed data.
176    ///
177    /// Will load the module from the cache if possible, otherwise will compile.
178    fn load_hashed_module(
179        &self,
180        module: HashedModuleData,
181        engine: Option<&Engine>,
182    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
183        let engine = engine.cloned().unwrap_or_else(|| self.engine());
184        let module_cache = self.module_cache();
185        let task = async move { load_module(&engine, &module_cache, &module).await };
186        Box::pin(task)
187    }
188
189    /// Synchronous version of [`Self::load_hashed_module`].
190    fn load_hashed_module_sync(
191        &self,
192        wasm: HashedModuleData,
193        engine: Option<&Engine>,
194    ) -> Result<Module, SpawnError> {
195        InlineWaker::block_on(self.load_hashed_module(wasm, engine))
196    }
197
198    /// Callback thats invokes whenever the instance is tainted, tainting can occur
199    /// for multiple reasons however the most common is a panic within the process
200    fn on_taint(&self, _reason: TaintReason) {}
201
202    /// The list of all read-only journals which will be used to restore the state of the
203    /// runtime at a particular point in time
204    #[cfg(feature = "journal")]
205    fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
206        Box::new(std::iter::empty())
207    }
208
209    /// The list of writable journals which will be appended to
210    #[cfg(feature = "journal")]
211    fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
212        Box::new(std::iter::empty())
213    }
214
215    /// The snapshot capturer takes and restores snapshots of the WASM process at specific
216    /// points in time by reading and writing log entries
217    #[cfg(feature = "journal")]
218    fn active_journal(&self) -> Option<&'_ DynJournal> {
219        None
220    }
221}
222
223pub type DynRuntime = dyn Runtime + Send + Sync;
224
225/// Load a a Webassembly module, trying to use a pre-compiled version if possible.
226///
227// This function exists to provide a reusable baseline implementation for
228// implementing [`Runtime::load_module`], so custom logic can be added on top.
229#[tracing::instrument(level = "debug", skip_all)]
230pub async fn load_module(
231    engine: &Engine,
232    module_cache: &(dyn ModuleCache + Send + Sync),
233    module: &HashedModuleData,
234) -> Result<Module, crate::SpawnError> {
235    let wasm_hash = *module.hash();
236    let result = module_cache.load(wasm_hash, engine).await;
237
238    match result {
239        Ok(module) => return Ok(module),
240        Err(CacheError::NotFound) => {}
241        Err(other) => {
242            tracing::warn!(
243                %wasm_hash,
244                error=&other as &dyn std::error::Error,
245                "Unable to load the cached module",
246            );
247        }
248    }
249
250    let module =
251        Module::new(&engine, module.wasm()).map_err(|err| crate::SpawnError::CompileError {
252            module_hash: wasm_hash,
253            error: err,
254        })?;
255
256    // TODO: pass a [`HashedModule`] struct that is safe by construction.
257    if let Err(e) = module_cache.save(wasm_hash, engine, &module).await {
258        tracing::warn!(
259            %wasm_hash,
260            error=&e as &dyn std::error::Error,
261            "Unable to cache the compiled module",
262        );
263    }
264
265    Ok(module)
266}
267
268#[derive(Debug, Default)]
269pub struct DefaultTty {
270    state: Mutex<WasiTtyState>,
271}
272
273impl TtyBridge for DefaultTty {
274    fn reset(&self) {
275        let mut state = self.state.lock().unwrap();
276        state.echo = false;
277        state.line_buffered = false;
278        state.line_feeds = false
279    }
280
281    fn tty_get(&self) -> WasiTtyState {
282        let state = self.state.lock().unwrap();
283        state.clone()
284    }
285
286    fn tty_set(&self, tty_state: WasiTtyState) {
287        let mut state = self.state.lock().unwrap();
288        *state = tty_state;
289    }
290}
291
292#[derive(Debug, Clone)]
293pub struct PluggableRuntime {
294    pub rt: Arc<dyn VirtualTaskManager>,
295    pub networking: DynVirtualNetworking,
296    pub http_client: Option<DynHttpClient>,
297    pub package_loader: Arc<dyn PackageLoader + Send + Sync>,
298    pub source: Arc<dyn Source + Send + Sync>,
299    pub engine: Engine,
300    pub module_cache: Arc<dyn ModuleCache + Send + Sync>,
301    pub tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
302    #[cfg(feature = "journal")]
303    pub read_only_journals: Vec<Arc<DynReadableJournal>>,
304    #[cfg(feature = "journal")]
305    pub writable_journals: Vec<Arc<DynJournal>>,
306}
307
308impl PluggableRuntime {
309    pub fn new(rt: Arc<dyn VirtualTaskManager>) -> Self {
310        // TODO: the cfg flags below should instead be handled by separate implementations.
311        cfg_if::cfg_if! {
312            if #[cfg(feature = "host-vnet")] {
313                let networking = Arc::new(virtual_net::host::LocalNetworking::default());
314            } else {
315                let networking = Arc::new(virtual_net::UnsupportedVirtualNetworking::default());
316            }
317        }
318        let http_client =
319            crate::http::default_http_client().map(|client| Arc::new(client) as DynHttpClient);
320
321        let loader = UnsupportedPackageLoader;
322
323        let mut source = MultiSource::default();
324        if let Some(client) = &http_client {
325            source.add_source(BackendSource::new(
326                BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap(),
327                client.clone(),
328            ));
329        }
330
331        Self {
332            rt,
333            networking,
334            http_client,
335            engine: Default::default(),
336            tty: None,
337            source: Arc::new(source),
338            package_loader: Arc::new(loader),
339            module_cache: Arc::new(module_cache::in_memory()),
340            #[cfg(feature = "journal")]
341            read_only_journals: Vec::new(),
342            #[cfg(feature = "journal")]
343            writable_journals: Vec::new(),
344        }
345    }
346
347    pub fn set_networking_implementation<I>(&mut self, net: I) -> &mut Self
348    where
349        I: VirtualNetworking + Sync,
350    {
351        self.networking = Arc::new(net);
352        self
353    }
354
355    pub fn set_engine(&mut self, engine: Engine) -> &mut Self {
356        self.engine = engine;
357        self
358    }
359
360    pub fn set_tty(&mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> &mut Self {
361        self.tty = Some(tty);
362        self
363    }
364
365    pub fn set_module_cache(
366        &mut self,
367        module_cache: impl ModuleCache + Send + Sync + 'static,
368    ) -> &mut Self {
369        self.module_cache = Arc::new(module_cache);
370        self
371    }
372
373    pub fn set_source(&mut self, source: impl Source + Send + 'static) -> &mut Self {
374        self.source = Arc::new(source);
375        self
376    }
377
378    pub fn set_package_loader(
379        &mut self,
380        package_loader: impl PackageLoader + 'static,
381    ) -> &mut Self {
382        self.package_loader = Arc::new(package_loader);
383        self
384    }
385
386    pub fn set_http_client(
387        &mut self,
388        client: impl HttpClient + Send + Sync + 'static,
389    ) -> &mut Self {
390        self.http_client = Some(Arc::new(client));
391        self
392    }
393
394    #[cfg(feature = "journal")]
395    pub fn add_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) -> &mut Self {
396        self.read_only_journals.push(journal);
397        self
398    }
399
400    #[cfg(feature = "journal")]
401    pub fn add_writable_journal(&mut self, journal: Arc<DynJournal>) -> &mut Self {
402        self.writable_journals.push(journal);
403        self
404    }
405}
406
407impl Runtime for PluggableRuntime {
408    fn networking(&self) -> &DynVirtualNetworking {
409        &self.networking
410    }
411
412    fn http_client(&self) -> Option<&DynHttpClient> {
413        self.http_client.as_ref()
414    }
415
416    fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
417        Arc::clone(&self.package_loader)
418    }
419
420    fn source(&self) -> Arc<dyn Source + Send + Sync> {
421        Arc::clone(&self.source)
422    }
423
424    fn engine(&self) -> Engine {
425        self.engine.clone()
426    }
427
428    fn new_store(&self) -> wasmer::Store {
429        wasmer::Store::new(self.engine.clone())
430    }
431
432    fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
433        &self.rt
434    }
435
436    fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
437        self.tty.as_deref()
438    }
439
440    fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
441        self.module_cache.clone()
442    }
443
444    #[cfg(feature = "journal")]
445    fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
446        Box::new(self.read_only_journals.iter().cloned())
447    }
448
449    #[cfg(feature = "journal")]
450    fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
451        Box::new(self.writable_journals.iter().cloned())
452    }
453
454    #[cfg(feature = "journal")]
455    fn active_journal(&self) -> Option<&DynJournal> {
456        self.writable_journals.iter().last().map(|a| a.as_ref())
457    }
458}
459
460/// Runtime that allows for certain things to be overridden
461/// such as the active journals
462#[derive(Clone, Debug)]
463pub struct OverriddenRuntime {
464    inner: Arc<DynRuntime>,
465    task_manager: Option<Arc<dyn VirtualTaskManager>>,
466    networking: Option<DynVirtualNetworking>,
467    http_client: Option<DynHttpClient>,
468    package_loader: Option<Arc<dyn PackageLoader + Send + Sync>>,
469    source: Option<Arc<dyn Source + Send + Sync>>,
470    engine: Option<Engine>,
471    module_cache: Option<Arc<dyn ModuleCache + Send + Sync>>,
472    tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
473    #[cfg(feature = "journal")]
474    pub read_only_journals: Option<Vec<Arc<DynReadableJournal>>>,
475    #[cfg(feature = "journal")]
476    pub writable_journals: Option<Vec<Arc<DynJournal>>>,
477}
478
479impl OverriddenRuntime {
480    pub fn new(inner: Arc<DynRuntime>) -> Self {
481        Self {
482            inner,
483            task_manager: None,
484            networking: None,
485            http_client: None,
486            package_loader: None,
487            source: None,
488            engine: None,
489            module_cache: None,
490            tty: None,
491            #[cfg(feature = "journal")]
492            read_only_journals: None,
493            #[cfg(feature = "journal")]
494            writable_journals: None,
495        }
496    }
497
498    pub fn with_task_manager(mut self, task_manager: Arc<dyn VirtualTaskManager>) -> Self {
499        self.task_manager.replace(task_manager);
500        self
501    }
502
503    pub fn with_networking(mut self, networking: DynVirtualNetworking) -> Self {
504        self.networking.replace(networking);
505        self
506    }
507
508    pub fn with_http_client(mut self, http_client: DynHttpClient) -> Self {
509        self.http_client.replace(http_client);
510        self
511    }
512
513    pub fn with_package_loader(
514        mut self,
515        package_loader: Arc<dyn PackageLoader + Send + Sync>,
516    ) -> Self {
517        self.package_loader.replace(package_loader);
518        self
519    }
520
521    pub fn with_source(mut self, source: Arc<dyn Source + Send + Sync>) -> Self {
522        self.source.replace(source);
523        self
524    }
525
526    pub fn with_engine(mut self, engine: Engine) -> Self {
527        self.engine.replace(engine);
528        self
529    }
530
531    pub fn with_module_cache(mut self, module_cache: Arc<dyn ModuleCache + Send + Sync>) -> Self {
532        self.module_cache.replace(module_cache);
533        self
534    }
535
536    pub fn with_tty(mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> Self {
537        self.tty.replace(tty);
538        self
539    }
540
541    #[cfg(feature = "journal")]
542    pub fn with_read_only_journals(mut self, journals: Vec<Arc<DynReadableJournal>>) -> Self {
543        self.read_only_journals.replace(journals);
544        self
545    }
546
547    #[cfg(feature = "journal")]
548    pub fn with_writable_journals(mut self, journals: Vec<Arc<DynJournal>>) -> Self {
549        self.writable_journals.replace(journals);
550        self
551    }
552}
553
554impl Runtime for OverriddenRuntime {
555    fn networking(&self) -> &DynVirtualNetworking {
556        if let Some(net) = self.networking.as_ref() {
557            net
558        } else {
559            self.inner.networking()
560        }
561    }
562
563    fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
564        if let Some(rt) = self.task_manager.as_ref() {
565            rt
566        } else {
567            self.inner.task_manager()
568        }
569    }
570
571    fn source(&self) -> Arc<dyn Source + Send + Sync> {
572        if let Some(source) = self.source.clone() {
573            source
574        } else {
575            self.inner.source()
576        }
577    }
578
579    fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
580        if let Some(loader) = self.package_loader.clone() {
581            loader
582        } else {
583            self.inner.package_loader()
584        }
585    }
586
587    fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
588        if let Some(cache) = self.module_cache.clone() {
589            cache
590        } else {
591            self.inner.module_cache()
592        }
593    }
594
595    fn engine(&self) -> Engine {
596        if let Some(engine) = self.engine.clone() {
597            engine
598        } else {
599            self.inner.engine()
600        }
601    }
602
603    fn new_store(&self) -> wasmer::Store {
604        if let Some(engine) = self.engine.clone() {
605            wasmer::Store::new(engine)
606        } else {
607            self.inner.new_store()
608        }
609    }
610
611    fn http_client(&self) -> Option<&DynHttpClient> {
612        if let Some(client) = self.http_client.as_ref() {
613            Some(client)
614        } else {
615            self.inner.http_client()
616        }
617    }
618
619    fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
620        if let Some(tty) = self.tty.as_ref() {
621            Some(tty.deref())
622        } else {
623            self.inner.tty()
624        }
625    }
626
627    #[cfg(feature = "journal")]
628    fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
629        if let Some(journals) = self.read_only_journals.as_ref() {
630            Box::new(journals.iter().cloned())
631        } else {
632            self.inner.read_only_journals()
633        }
634    }
635
636    #[cfg(feature = "journal")]
637    fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
638        if let Some(journals) = self.writable_journals.as_ref() {
639            Box::new(journals.iter().cloned())
640        } else {
641            self.inner.writable_journals()
642        }
643    }
644
645    #[cfg(feature = "journal")]
646    fn active_journal(&self) -> Option<&'_ DynJournal> {
647        if let Some(journals) = self.writable_journals.as_ref() {
648            journals.iter().last().map(|a| a.as_ref())
649        } else {
650            self.inner.active_journal()
651        }
652    }
653
654    fn load_module<'a>(&'a self, data: &[u8]) -> BoxFuture<'a, Result<Module, SpawnError>> {
655        #[allow(deprecated)]
656        if let (Some(engine), Some(module_cache)) = (&self.engine, self.module_cache.clone()) {
657            let engine = engine.clone();
658            let hashed_data = HashedModuleData::new(data.to_vec());
659            let task = async move { load_module(&engine, &module_cache, &hashed_data).await };
660            Box::pin(task)
661        } else {
662            let data = data.to_vec();
663            Box::pin(async move { self.inner.load_module(&data).await })
664        }
665    }
666
667    fn load_module_sync(&self, wasm: &[u8]) -> Result<Module, SpawnError> {
668        #[allow(deprecated)]
669        if self.engine.is_some() || self.module_cache.is_some() {
670            InlineWaker::block_on(self.load_module(wasm))
671        } else {
672            self.inner.load_module_sync(wasm)
673        }
674    }
675
676    fn load_hashed_module(
677        &self,
678        data: HashedModuleData,
679        engine: Option<&Engine>,
680    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
681        if self.engine.is_some() || self.module_cache.is_some() {
682            let engine = self.engine();
683            let module_cache = self.module_cache();
684            let task = async move { load_module(&engine, &module_cache, &data).await };
685            Box::pin(task)
686        } else {
687            self.inner.load_hashed_module(data, engine)
688        }
689    }
690
691    fn load_hashed_module_sync(
692        &self,
693        wasm: HashedModuleData,
694        engine: Option<&Engine>,
695    ) -> Result<Module, SpawnError> {
696        if self.engine.is_some() || self.module_cache.is_some() {
697            InlineWaker::block_on(self.load_hashed_module(wasm, engine))
698        } else {
699            self.inner.load_hashed_module_sync(wasm, engine)
700        }
701    }
702}