wasmer_wasix/runtime/
mod.rs

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