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_types::{CompilationProgressCallback, ModuleHash};
10
11use std::{
12    borrow::Cow,
13    fmt,
14    ops::Deref,
15    sync::{Arc, Mutex},
16};
17
18use anyhow::Context as _;
19use futures::future::BoxFuture;
20use virtual_mio::block_on;
21use virtual_net::{DynVirtualNetworking, VirtualNetworking};
22use wasmer::{Engine, Module, RuntimeError};
23use wasmer_wasix_types::wasi::ExitCode;
24
25#[cfg(feature = "journal")]
26use crate::journal::{DynJournal, DynReadableJournal};
27use crate::{
28    SpawnError, WasiTtyState,
29    bin_factory::BinaryPackageCommand,
30    http::{DynHttpClient, HttpClient},
31    os::TtyBridge,
32    runtime::{
33        module_cache::{
34            ModuleCache, ThreadLocalCache,
35            progress::{ModuleLoadProgress, ModuleLoadProgressReporter},
36        },
37        package_loader::{PackageLoader, UnsupportedPackageLoader},
38        resolver::{BackendSource, MultiSource, Source},
39    },
40};
41
42/// Opaque per-instantiation state, created by
43/// [`InstantiationHook::additional_imports`] and handed back to
44/// [`InstantiationHook::configure_new_instance`] for the instance built with
45/// those imports.
46///
47/// Hooks put the data they need to carry between the two phases in with
48/// [`InstantiationState::new`] and get it back out with
49/// [`InstantiationState::take`]. Callers only pass the value along, unmodified.
50// Carrying the state through the instantiation, instead of parking it in the
51// hook, is what makes concurrent instantiations safe: an implementation never
52// has to guess which pending instantiation a configure_new_instance call
53// belongs to, so two threads cold-starting the same module in different
54// stores cannot receive each other's state.
55#[derive(Default)]
56pub struct InstantiationState {
57    state: Option<Box<dyn std::any::Any + Send>>,
58}
59
60impl InstantiationState {
61    /// State that carries no data, for hooks that need nothing from the import
62    /// phase.
63    pub fn empty() -> Self {
64        Self { state: None }
65    }
66
67    /// Carries `state` from the import phase to the instance setup phase.
68    pub fn new<T: std::any::Any + Send>(state: T) -> Self {
69        Self {
70            state: Some(Box::new(state)),
71        }
72    }
73
74    /// Whether this state carries no data.
75    pub fn is_empty(&self) -> bool {
76        self.state.is_none()
77    }
78
79    /// Takes back the data stored by [`InstantiationState::new`].
80    ///
81    /// Fails if the state is empty or holds a different type, both of which
82    /// mean it did not come from the matching import phase.
83    pub fn take<T: std::any::Any + Send>(self) -> anyhow::Result<T> {
84        let state = self
85            .state
86            .context("missing instantiation state from the import phase")?;
87        state
88            .downcast::<T>()
89            .map(|state| *state)
90            .map_err(|_| anyhow::anyhow!("instantiation state does not belong to this hook"))
91    }
92}
93
94impl fmt::Debug for InstantiationState {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        if self.is_empty() {
97            f.write_str("InstantiationState::empty()")
98        } else {
99            f.write_str("InstantiationState(..)")
100        }
101    }
102}
103
104/// A hook into the instantiation of WASIX module instances.
105///
106/// Registered on [`PluggableRuntime::with_instantiation_hook`] or
107/// [`OverriddenRuntime::with_instantiation_hook`], and invoked once per
108/// instance the runtime creates (process bootstrap, thread spawn, dynamically
109/// linked side module).
110///
111/// Both methods have a no-op default, so an implementation only needs to
112/// provide the phases it cares about.
113// Keeping both phases on one trait is what lets each hook route its own
114// InstantiationState from its import phase to its own setup phase when
115// several hooks are registered on the same runtime.
116pub trait InstantiationHook: fmt::Debug + Send + Sync + 'static {
117    /// Creates additional imports for an instance about to be created in
118    /// `store`.
119    ///
120    /// The returned [`InstantiationState`] is handed back to
121    /// [`InstantiationHook::configure_new_instance`] for the instance built
122    /// with these imports. If instantiation fails, the state is dropped.
123    fn additional_imports(
124        &self,
125        module: &wasmer::Module,
126        store: &mut wasmer::StoreMut,
127    ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
128        let _ = (module, store);
129        Ok((wasmer::Imports::new(), InstantiationState::empty()))
130    }
131
132    /// Configures an instantiated instance before initialization/startup.
133    ///
134    /// `state` is the [`InstantiationState`] this hook returned from the
135    /// [`InstantiationHook::additional_imports`] call whose imports the
136    /// instance was created with.
137    fn configure_new_instance(
138        &self,
139        module: &wasmer::Module,
140        store: &mut wasmer::StoreMut,
141        instance: &wasmer::Instance,
142        imported_memory: Option<&wasmer::Memory>,
143        state: InstantiationState,
144    ) -> anyhow::Result<()> {
145        let _ = (module, store, instance, imported_memory, state);
146        Ok(())
147    }
148}
149
150impl<H: InstantiationHook + ?Sized> InstantiationHook for Arc<H> {
151    fn additional_imports(
152        &self,
153        module: &wasmer::Module,
154        store: &mut wasmer::StoreMut,
155    ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
156        (**self).additional_imports(module, store)
157    }
158
159    fn configure_new_instance(
160        &self,
161        module: &wasmer::Module,
162        store: &mut wasmer::StoreMut,
163        instance: &wasmer::Instance,
164        imported_memory: Option<&wasmer::Memory>,
165        state: InstantiationState,
166    ) -> anyhow::Result<()> {
167        (**self).configure_new_instance(module, store, instance, imported_memory, state)
168    }
169}
170
171/// Adapts an import-creation closure to [`InstantiationHook`].
172struct ImportsOnlyHook<F>(F);
173
174impl<F> fmt::Debug for ImportsOnlyHook<F> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        f.write_str("ImportsOnlyHook(..)")
177    }
178}
179
180impl<F> InstantiationHook for ImportsOnlyHook<F>
181where
182    F: Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
183        + Send
184        + Sync
185        + 'static,
186{
187    fn additional_imports(
188        &self,
189        module: &wasmer::Module,
190        store: &mut wasmer::StoreMut,
191    ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
192        Ok(((self.0)(module, store)?, InstantiationState::empty()))
193    }
194}
195
196/// Adapts an instance-setup closure to [`InstantiationHook`].
197struct InstanceSetupHook<F>(F);
198
199impl<F> fmt::Debug for InstanceSetupHook<F> {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        f.write_str("InstanceSetupHook(..)")
202    }
203}
204
205impl<F> InstantiationHook for InstanceSetupHook<F>
206where
207    F: Fn(
208            &wasmer::Module,
209            &mut wasmer::StoreMut,
210            &wasmer::Instance,
211            Option<&wasmer::Memory>,
212        ) -> anyhow::Result<()>
213        + Send
214        + Sync
215        + 'static,
216{
217    fn configure_new_instance(
218        &self,
219        module: &wasmer::Module,
220        store: &mut wasmer::StoreMut,
221        instance: &wasmer::Instance,
222        imported_memory: Option<&wasmer::Memory>,
223        _state: InstantiationState,
224    ) -> anyhow::Result<()> {
225        (self.0)(module, store, instance, imported_memory)
226    }
227}
228
229#[derive(Clone)]
230pub enum TaintReason {
231    UnknownWasiVersion,
232    NonZeroExitCode(ExitCode),
233    RuntimeError(RuntimeError),
234    DlSymbolResolutionFailed(String),
235}
236
237/// The input to load a module.
238///
239/// Exists because the semantics for resolving modules can vary between
240/// different sources.
241///
242/// All variants are wrapped in `Cow` to allow for zero-copy usage when possible.
243#[allow(clippy::large_enum_variant)]
244pub enum ModuleInput<'a> {
245    /// Raw bytes.
246    Bytes(Cow<'a, [u8]>),
247    /// Pre-hashed module data.
248    Hashed(Cow<'a, HashedModuleData>),
249    /// A binary package command.
250    Command(Cow<'a, BinaryPackageCommand>),
251}
252
253impl<'a> ModuleInput<'a> {
254    /// Convert to an owned version of the module input.
255    pub fn to_owned(&'a self) -> ModuleInput<'static> {
256        // The manual code below is needed due to compiler issues with the lifetime.
257        match self {
258            Self::Bytes(Cow::Borrowed(b)) => {
259                let v: Vec<u8> = (*b).to_owned();
260                let c: Cow<'static, [u8]> = Cow::from(v);
261                ModuleInput::Bytes(c)
262            }
263            Self::Bytes(Cow::Owned(b)) => ModuleInput::Bytes(Cow::Owned((*b).clone())),
264            Self::Hashed(Cow::Borrowed(h)) => ModuleInput::Hashed(Cow::Owned((*h).clone())),
265            Self::Hashed(Cow::Owned(h)) => ModuleInput::Hashed(Cow::Owned(h.clone())),
266            Self::Command(Cow::Borrowed(c)) => ModuleInput::Command(Cow::Owned((*c).clone())),
267            Self::Command(Cow::Owned(c)) => ModuleInput::Command(Cow::Owned(c.clone())),
268        }
269    }
270
271    /// Get the module hash.
272    ///
273    /// NOTE: may be expensive, depending on the variant.
274    pub fn hash(&self) -> ModuleHash {
275        match self {
276            Self::Bytes(b) => {
277                // Hash on the fly
278                ModuleHash::new(b)
279            }
280            Self::Hashed(hashed) => *hashed.hash(),
281            Self::Command(cmd) => *cmd.hash(),
282        }
283    }
284
285    /// Get the raw WebAssembly bytes.
286    pub fn wasm(&self) -> &[u8] {
287        match self {
288            Self::Bytes(b) => b,
289            Self::Hashed(hashed) => hashed.wasm().as_ref(),
290            Self::Command(cmd) => cmd.atom_ref().as_ref(),
291        }
292    }
293
294    /// Convert to a `HashedModuleData`.
295    ///
296    /// May involve cloning and hashing.
297    pub fn to_hashed(&self) -> HashedModuleData {
298        match self {
299            Self::Bytes(b) => HashedModuleData::new(b.as_ref()),
300            Self::Hashed(hashed) => hashed.as_ref().clone(),
301            Self::Command(cmd) => HashedModuleData::from_command(cmd),
302        }
303    }
304}
305
306/// Runtime components used when running WebAssembly programs.
307///
308/// Think of this as the "System" in "WebAssembly Systems Interface".
309#[allow(unused_variables)]
310pub trait Runtime
311where
312    Self: fmt::Debug,
313{
314    /// Provides access to all the networking related functions such as sockets.
315    fn networking(&self) -> &DynVirtualNetworking;
316
317    /// Retrieve the active [`VirtualTaskManager`].
318    fn task_manager(&self) -> &Arc<dyn VirtualTaskManager>;
319
320    /// A package loader.
321    fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
322        Arc::new(UnsupportedPackageLoader)
323    }
324
325    /// A cache for compiled modules.
326    fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
327        // Return a cache that uses a thread-local variable. This isn't ideal
328        // because it allows silently sharing state, possibly between runtimes.
329        //
330        // That said, it means people will still get *some* level of caching
331        // because each cache returned by this default implementation will go
332        // through the same thread-local variable.
333        Arc::new(ThreadLocalCache::default())
334    }
335
336    /// The package registry.
337    fn source(&self) -> Arc<dyn Source + Send + Sync>;
338
339    /// Get a [`wasmer::Engine`] for module compilation.
340    fn engine(&self) -> Engine {
341        Engine::default()
342    }
343
344    /// Create a new [`wasmer::Store`].
345    fn new_store(&self) -> wasmer::Store {
346        cfg_if::cfg_if! {
347            if #[cfg(feature = "sys")] {
348                wasmer::Store::new(self.engine())
349            } else {
350                wasmer::Store::default()
351            }
352        }
353    }
354
355    /// Create additional imports for a new WASIX instance in the provided store.
356    ///
357    /// This callback may be invoked multiple times (e.g. process bootstrap,
358    /// thread spawn), so implementations should create imports that are valid
359    /// for the given store each time.
360    ///
361    /// The returned [`InstantiationState`] is per-instantiation state that the
362    /// caller must pass to [`Runtime::configure_new_instance`] once the
363    /// instance built from these imports exists. If instantiation fails, the
364    /// state is simply dropped.
365    fn additional_imports(
366        &self,
367        _module: &wasmer::Module,
368        _store: &mut wasmer::StoreMut,
369    ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
370        Ok((wasmer::Imports::new(), InstantiationState::empty()))
371    }
372
373    /// Configure an instantiated instance before initialization/startup.
374    ///
375    /// `state` must be the [`InstantiationState`] returned by the
376    /// [`Runtime::additional_imports`] call whose imports this instance was
377    /// created with.
378    fn configure_new_instance(
379        &self,
380        _module: &wasmer::Module,
381        _store: &mut wasmer::StoreMut,
382        _instance: &wasmer::Instance,
383        _imported_memory: Option<&wasmer::Memory>,
384        _state: InstantiationState,
385    ) -> anyhow::Result<()> {
386        Ok(())
387    }
388
389    /// Get a custom HTTP client
390    fn http_client(&self) -> Option<&DynHttpClient> {
391        None
392    }
393
394    /// Get access to the TTY used by the environment.
395    fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
396        None
397    }
398
399    /// The primary way to load a module given a module input.
400    ///
401    /// The engine to use can be optionally provided, otherwise the most appropriate engine
402    /// should be selected.
403    ///
404    /// An optional progress reporter callback can be provided to report progress during module loading.
405    fn resolve_module<'a>(
406        &'a self,
407        input: ModuleInput<'a>,
408        engine: Option<&Engine>,
409        on_progress: Option<ModuleLoadProgressReporter>,
410    ) -> BoxFuture<'a, Result<Module, SpawnError>> {
411        let data = input.to_hashed();
412
413        let engine = if let Some(e) = engine {
414            e.clone()
415        } else {
416            match &input {
417                ModuleInput::Bytes(_) => self.engine(),
418                ModuleInput::Hashed(_) => self.engine(),
419                ModuleInput::Command(cmd) => self.engine(),
420            }
421        };
422
423        let module_cache = self.module_cache();
424
425        let task = async move { load_module(&engine, &module_cache, input, on_progress).await };
426        Box::pin(task)
427    }
428
429    /// Sync variant of [`Self::resolve_module`].
430    fn resolve_module_sync(
431        &self,
432        input: ModuleInput<'_>,
433        engine: Option<&Engine>,
434        on_progress: Option<ModuleLoadProgressReporter>,
435    ) -> Result<Module, SpawnError> {
436        block_on(self.resolve_module(input, engine, on_progress))
437    }
438
439    /// Load the module for a command.
440    ///
441    /// Will load the module from the cache if possible, otherwise will compile.
442    ///
443    /// NOTE: This always be preferred over [`Self::load_module`] to avoid
444    /// re-hashing the module!
445    #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
446    fn load_command_module(
447        &self,
448        cmd: &BinaryPackageCommand,
449    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
450        self.resolve_module(ModuleInput::Command(Cow::Owned(cmd.clone())), None, None)
451    }
452
453    /// Sync version of [`Self::load_command_module`].
454    #[deprecated(since = "0.601.0", note = "Use `resolve_module_sync` instead")]
455    fn load_command_module_sync(&self, cmd: &BinaryPackageCommand) -> Result<Module, SpawnError> {
456        block_on(self.resolve_module(ModuleInput::Command(Cow::Borrowed(cmd)), None, None))
457    }
458
459    /// Load a WebAssembly module from raw bytes.
460    ///
461    /// Will load the module from the cache if possible, otherwise will compile.
462    #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
463    fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result<Module, SpawnError>> {
464        self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None)
465    }
466
467    /// Synchronous version of [`Self::load_module`].
468    #[deprecated(
469        since = "0.601.0",
470        note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead"
471    )]
472    fn load_module_sync(&self, wasm: &[u8]) -> Result<Module, SpawnError> {
473        block_on(self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None))
474    }
475
476    /// Load a WebAssembly module from pre-hashed data.
477    ///
478    /// Will load the module from the cache if possible, otherwise will compile.
479    fn load_hashed_module(
480        &self,
481        module: HashedModuleData,
482        engine: Option<&Engine>,
483    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
484        self.resolve_module(ModuleInput::Hashed(Cow::Owned(module)), engine, None)
485    }
486
487    /// Synchronous version of [`Self::load_hashed_module`].
488    fn load_hashed_module_sync(
489        &self,
490        wasm: HashedModuleData,
491        engine: Option<&Engine>,
492    ) -> Result<Module, SpawnError> {
493        block_on(self.resolve_module(ModuleInput::Hashed(Cow::Owned(wasm)), engine, None))
494    }
495
496    /// Callback thats invokes whenever the instance is tainted, tainting can occur
497    /// for multiple reasons however the most common is a panic within the process
498    fn on_taint(&self, _reason: TaintReason) {}
499
500    /// The list of all read-only journals which will be used to restore the state of the
501    /// runtime at a particular point in time
502    #[cfg(feature = "journal")]
503    fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
504        Box::new(std::iter::empty())
505    }
506
507    /// The list of writable journals which will be appended to
508    #[cfg(feature = "journal")]
509    fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
510        Box::new(std::iter::empty())
511    }
512
513    /// The snapshot capturer takes and restores snapshots of the WASM process at specific
514    /// points in time by reading and writing log entries
515    #[cfg(feature = "journal")]
516    fn active_journal(&self) -> Option<&'_ DynJournal> {
517        None
518    }
519}
520
521pub type DynRuntime = dyn Runtime + Send + Sync;
522
523/// Load a Webassembly module, trying to use a pre-compiled version if possible.
524///
525// This function exists to provide a reusable baseline implementation for
526// implementing [`Runtime::load_module`], so custom logic can be added on top.
527#[tracing::instrument(level = "debug", skip_all)]
528pub async fn load_module(
529    engine: &Engine,
530    module_cache: &(dyn ModuleCache + Send + Sync),
531    input: ModuleInput<'_>,
532    on_progress: Option<ModuleLoadProgressReporter>,
533) -> Result<Module, crate::SpawnError> {
534    let wasm_hash = input.hash();
535
536    let result = if let Some(on_progress) = &on_progress {
537        module_cache
538            .load_with_progress(wasm_hash, engine, on_progress.clone())
539            .await
540    } else {
541        module_cache.load(wasm_hash, engine).await
542    };
543
544    match result {
545        Ok(module) => return Ok(module),
546        Err(CacheError::NotFound) => {}
547        Err(other) => {
548            tracing::warn!(
549                %wasm_hash,
550                error=&other as &dyn std::error::Error,
551                "Unable to load the cached module",
552            );
553        }
554    }
555
556    let res = if let Some(progress) = on_progress {
557        #[allow(unused_variables)]
558        let p = CompilationProgressCallback::new(move |p| {
559            progress.notify(ModuleLoadProgress::CompilingModule(p))
560        });
561        #[cfg(feature = "sys")]
562        {
563            if engine.is_sys() {
564                use wasmer::sys::NativeEngineExt;
565                engine.new_module_with_progress(input.wasm(), p)
566            } else {
567                Module::new(&engine, input.wasm())
568            }
569        }
570        #[cfg(not(feature = "sys"))]
571        {
572            Module::new(&engine, input.wasm())
573        }
574    } else {
575        Module::new(&engine, input.wasm())
576    };
577
578    let module = res.map_err(|err| crate::SpawnError::CompileError {
579        module_hash: wasm_hash,
580        error: err,
581    })?;
582
583    // TODO: pass a [`HashedModule`] struct that is safe by construction.
584    if let Err(e) = module_cache.save(wasm_hash, engine, &module).await {
585        tracing::warn!(
586            %wasm_hash,
587            error=&e as &dyn std::error::Error,
588            "Unable to cache the compiled module",
589        );
590    }
591
592    Ok(module)
593}
594
595#[derive(Debug, Default)]
596pub struct DefaultTty {
597    state: Mutex<WasiTtyState>,
598}
599
600impl TtyBridge for DefaultTty {
601    fn reset(&self) {
602        let mut state = self.state.lock().unwrap();
603        state.echo = false;
604        state.line_buffered = false;
605        state.line_feeds = false
606    }
607
608    fn tty_get(&self) -> WasiTtyState {
609        let state = self.state.lock().unwrap();
610        state.clone()
611    }
612
613    fn tty_set(&self, tty_state: WasiTtyState) {
614        let mut state = self.state.lock().unwrap();
615        *state = tty_state;
616    }
617}
618
619#[derive(Debug, Clone)]
620pub struct PluggableRuntime {
621    pub rt: Arc<dyn VirtualTaskManager>,
622    pub networking: DynVirtualNetworking,
623    pub http_client: Option<DynHttpClient>,
624    pub package_loader: Arc<dyn PackageLoader + Send + Sync>,
625    pub source: Arc<dyn Source + Send + Sync>,
626    pub engine: Engine,
627    pub module_cache: Arc<dyn ModuleCache + Send + Sync>,
628    pub tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
629    #[cfg(feature = "journal")]
630    pub read_only_journals: Vec<Arc<DynReadableJournal>>,
631    #[cfg(feature = "journal")]
632    pub writable_journals: Vec<Arc<DynJournal>>,
633    pub instantiation_hooks: Vec<Arc<dyn InstantiationHook>>,
634}
635
636impl PluggableRuntime {
637    pub fn new(rt: Arc<dyn VirtualTaskManager>) -> Self {
638        // TODO: the cfg flags below should instead be handled by separate implementations.
639        cfg_if::cfg_if! {
640            if #[cfg(feature = "host-vnet")] {
641                let networking = Arc::new(virtual_net::host::LocalNetworking::default());
642            } else {
643                let networking = Arc::new(virtual_net::UnsupportedVirtualNetworking::default());
644            }
645        }
646        let http_client =
647            crate::http::default_http_client().map(|client| Arc::new(client) as DynHttpClient);
648
649        let loader = UnsupportedPackageLoader;
650
651        let mut source = MultiSource::default();
652        if let Some(client) = &http_client {
653            source.add_source(BackendSource::new(
654                BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap(),
655                client.clone(),
656            ));
657        }
658
659        Self {
660            rt,
661            networking,
662            http_client,
663            engine: Default::default(),
664            tty: None,
665            source: Arc::new(source),
666            package_loader: Arc::new(loader),
667            module_cache: Arc::new(module_cache::in_memory()),
668            #[cfg(feature = "journal")]
669            read_only_journals: Vec::new(),
670            #[cfg(feature = "journal")]
671            writable_journals: Vec::new(),
672            instantiation_hooks: Vec::new(),
673        }
674    }
675
676    pub fn set_networking_implementation<I>(&mut self, net: I) -> &mut Self
677    where
678        I: VirtualNetworking + Sync,
679    {
680        self.networking = Arc::new(net);
681        self
682    }
683
684    pub fn set_engine(&mut self, engine: Engine) -> &mut Self {
685        self.engine = engine;
686        self
687    }
688
689    pub fn set_tty(&mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> &mut Self {
690        self.tty = Some(tty);
691        self
692    }
693
694    pub fn set_module_cache(
695        &mut self,
696        module_cache: impl ModuleCache + Send + Sync + 'static,
697    ) -> &mut Self {
698        self.module_cache = Arc::new(module_cache);
699        self
700    }
701
702    pub fn set_source(&mut self, source: impl Source + Send + 'static) -> &mut Self {
703        self.source = Arc::new(source);
704        self
705    }
706
707    pub fn set_package_loader(
708        &mut self,
709        package_loader: impl PackageLoader + 'static,
710    ) -> &mut Self {
711        self.package_loader = Arc::new(package_loader);
712        self
713    }
714
715    pub fn set_http_client(
716        &mut self,
717        client: impl HttpClient + Send + Sync + 'static,
718    ) -> &mut Self {
719        self.http_client = Some(Arc::new(client));
720        self
721    }
722
723    #[cfg(feature = "journal")]
724    pub fn add_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) -> &mut Self {
725        self.read_only_journals.push(journal);
726        self
727    }
728
729    #[cfg(feature = "journal")]
730    pub fn add_writable_journal(&mut self, journal: Arc<DynJournal>) -> &mut Self {
731        self.writable_journals.push(journal);
732        self
733    }
734
735    /// Registers a hook that only creates additional imports.
736    pub fn with_additional_imports(
737        &mut self,
738        imports: impl Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
739        + Send
740        + Sync
741        + 'static,
742    ) -> &mut Self {
743        self.with_instantiation_hook(ImportsOnlyHook(imports))
744    }
745
746    /// Registers a hook that only configures newly created instances.
747    pub fn with_instance_setup(
748        &mut self,
749        callback: impl Fn(
750            &wasmer::Module,
751            &mut wasmer::StoreMut,
752            &wasmer::Instance,
753            Option<&wasmer::Memory>,
754        ) -> anyhow::Result<()>
755        + Send
756        + Sync
757        + 'static,
758    ) -> &mut Self {
759        self.with_instantiation_hook(InstanceSetupHook(callback))
760    }
761
762    /// Registers a hook that takes part in both phases of instantiation, so it
763    /// can carry [`InstantiationState`] from its imports to its instance setup.
764    pub fn with_instantiation_hook(&mut self, hook: impl InstantiationHook) -> &mut Self {
765        self.instantiation_hooks.push(Arc::new(hook));
766        self
767    }
768}
769
770/// Runs the import phase of `hooks`, returning the merged imports and the
771/// per-hook states, aligned by index with `hooks`.
772fn run_import_hooks(
773    hooks: &[Arc<dyn InstantiationHook>],
774    module: &wasmer::Module,
775    store: &mut wasmer::StoreMut,
776) -> anyhow::Result<(wasmer::Imports, Vec<InstantiationState>)> {
777    let mut imports = wasmer::Imports::new();
778    let mut states = Vec::with_capacity(hooks.len());
779    for hook in hooks {
780        let (hook_imports, state) = hook.additional_imports(module, store)?;
781        imports.extend(&hook_imports);
782        states.push(state);
783    }
784    Ok((imports, states))
785}
786
787/// Composite state used by [`OverriddenRuntime`] to carry the inner
788/// runtime's state alongside its own hooks' states.
789struct OverriddenInstantiationState {
790    inner: InstantiationState,
791    own: Vec<InstantiationState>,
792}
793
794/// Runs the setup phase of `hooks`, handing each hook the state it produced
795/// during the import phase.
796fn run_setup_hooks(
797    hooks: &[Arc<dyn InstantiationHook>],
798    states: Vec<InstantiationState>,
799    module: &wasmer::Module,
800    store: &mut wasmer::StoreMut,
801    instance: &wasmer::Instance,
802    imported_memory: Option<&wasmer::Memory>,
803) -> anyhow::Result<()> {
804    anyhow::ensure!(
805        states.len() == hooks.len(),
806        "instance setup state does not match the registered instantiation hooks \
807         (got {} states for {} hooks)",
808        states.len(),
809        hooks.len(),
810    );
811    for (hook, state) in hooks.iter().zip(states) {
812        hook.configure_new_instance(module, store, instance, imported_memory, state)?;
813    }
814    Ok(())
815}
816
817impl Runtime for PluggableRuntime {
818    fn networking(&self) -> &DynVirtualNetworking {
819        &self.networking
820    }
821
822    fn http_client(&self) -> Option<&DynHttpClient> {
823        self.http_client.as_ref()
824    }
825
826    fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
827        Arc::clone(&self.package_loader)
828    }
829
830    fn source(&self) -> Arc<dyn Source + Send + Sync> {
831        Arc::clone(&self.source)
832    }
833
834    fn engine(&self) -> Engine {
835        self.engine.clone()
836    }
837
838    fn new_store(&self) -> wasmer::Store {
839        wasmer::Store::new(self.engine.clone())
840    }
841
842    fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
843        &self.rt
844    }
845
846    fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
847        self.tty.as_deref()
848    }
849
850    fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
851        self.module_cache.clone()
852    }
853
854    fn additional_imports(
855        &self,
856        module: &wasmer::Module,
857        store: &mut wasmer::StoreMut,
858    ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
859        if self.instantiation_hooks.is_empty() {
860            return Ok((wasmer::Imports::new(), InstantiationState::empty()));
861        }
862        let (imports, states) = run_import_hooks(&self.instantiation_hooks, module, store)?;
863        Ok((imports, InstantiationState::new(states)))
864    }
865
866    fn configure_new_instance(
867        &self,
868        module: &wasmer::Module,
869        store: &mut wasmer::StoreMut,
870        instance: &wasmer::Instance,
871        imported_memory: Option<&wasmer::Memory>,
872        state: InstantiationState,
873    ) -> anyhow::Result<()> {
874        if self.instantiation_hooks.is_empty() {
875            return Ok(());
876        }
877        let states = state
878            .take::<Vec<InstantiationState>>()
879            .context("invalid instance setup state from additional_imports")?;
880        run_setup_hooks(
881            &self.instantiation_hooks,
882            states,
883            module,
884            store,
885            instance,
886            imported_memory,
887        )
888    }
889
890    #[cfg(feature = "journal")]
891    fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
892        Box::new(self.read_only_journals.iter().cloned())
893    }
894
895    #[cfg(feature = "journal")]
896    fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
897        Box::new(self.writable_journals.iter().cloned())
898    }
899
900    #[cfg(feature = "journal")]
901    fn active_journal(&self) -> Option<&DynJournal> {
902        self.writable_journals.iter().last().map(|a| a.as_ref())
903    }
904}
905
906/// Runtime that allows for certain things to be overridden
907/// such as the active journals
908#[derive(Clone, Debug)]
909pub struct OverriddenRuntime {
910    inner: Arc<DynRuntime>,
911    task_manager: Option<Arc<dyn VirtualTaskManager>>,
912    networking: Option<DynVirtualNetworking>,
913    http_client: Option<DynHttpClient>,
914    package_loader: Option<Arc<dyn PackageLoader + Send + Sync>>,
915    source: Option<Arc<dyn Source + Send + Sync>>,
916    engine: Option<Engine>,
917    module_cache: Option<Arc<dyn ModuleCache + Send + Sync>>,
918    tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
919    instantiation_hooks: Vec<Arc<dyn InstantiationHook>>,
920    #[cfg(feature = "journal")]
921    pub read_only_journals: Option<Vec<Arc<DynReadableJournal>>>,
922    #[cfg(feature = "journal")]
923    pub writable_journals: Option<Vec<Arc<DynJournal>>>,
924}
925
926impl OverriddenRuntime {
927    pub fn new(inner: Arc<DynRuntime>) -> Self {
928        Self {
929            inner,
930            task_manager: None,
931            networking: None,
932            http_client: None,
933            package_loader: None,
934            source: None,
935            engine: None,
936            module_cache: None,
937            tty: None,
938            instantiation_hooks: Vec::new(),
939            #[cfg(feature = "journal")]
940            read_only_journals: None,
941            #[cfg(feature = "journal")]
942            writable_journals: None,
943        }
944    }
945
946    pub fn with_task_manager(mut self, task_manager: Arc<dyn VirtualTaskManager>) -> Self {
947        self.task_manager.replace(task_manager);
948        self
949    }
950
951    pub fn with_networking(mut self, networking: DynVirtualNetworking) -> Self {
952        self.networking.replace(networking);
953        self
954    }
955
956    pub fn with_http_client(mut self, http_client: DynHttpClient) -> Self {
957        self.http_client.replace(http_client);
958        self
959    }
960
961    pub fn with_package_loader(
962        mut self,
963        package_loader: Arc<dyn PackageLoader + Send + Sync>,
964    ) -> Self {
965        self.package_loader.replace(package_loader);
966        self
967    }
968
969    pub fn with_source(mut self, source: Arc<dyn Source + Send + Sync>) -> Self {
970        self.source.replace(source);
971        self
972    }
973
974    pub fn with_engine(mut self, engine: Engine) -> Self {
975        self.engine.replace(engine);
976        self
977    }
978
979    pub fn with_module_cache(mut self, module_cache: Arc<dyn ModuleCache + Send + Sync>) -> Self {
980        self.module_cache.replace(module_cache);
981        self
982    }
983
984    pub fn with_tty(mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> Self {
985        self.tty.replace(tty);
986        self
987    }
988
989    /// Registers a hook that only creates additional imports.
990    pub fn with_additional_imports(
991        self,
992        imports: impl Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
993        + Send
994        + Sync
995        + 'static,
996    ) -> Self {
997        self.with_instantiation_hook(ImportsOnlyHook(imports))
998    }
999
1000    /// Registers a hook that only configures newly created instances.
1001    pub fn with_instance_setup(
1002        self,
1003        callback: impl Fn(
1004            &wasmer::Module,
1005            &mut wasmer::StoreMut,
1006            &wasmer::Instance,
1007            Option<&wasmer::Memory>,
1008        ) -> anyhow::Result<()>
1009        + Send
1010        + Sync
1011        + 'static,
1012    ) -> Self {
1013        self.with_instantiation_hook(InstanceSetupHook(callback))
1014    }
1015
1016    /// Registers a hook that takes part in both phases of instantiation, so it
1017    /// can carry [`InstantiationState`] from its imports to its instance setup.
1018    pub fn with_instantiation_hook(mut self, hook: impl InstantiationHook) -> Self {
1019        self.instantiation_hooks.push(Arc::new(hook));
1020        self
1021    }
1022
1023    #[cfg(feature = "journal")]
1024    pub fn with_read_only_journals(mut self, journals: Vec<Arc<DynReadableJournal>>) -> Self {
1025        self.read_only_journals.replace(journals);
1026        self
1027    }
1028
1029    #[cfg(feature = "journal")]
1030    pub fn with_writable_journals(mut self, journals: Vec<Arc<DynJournal>>) -> Self {
1031        self.writable_journals.replace(journals);
1032        self
1033    }
1034}
1035
1036impl Runtime for OverriddenRuntime {
1037    fn networking(&self) -> &DynVirtualNetworking {
1038        if let Some(net) = self.networking.as_ref() {
1039            net
1040        } else {
1041            self.inner.networking()
1042        }
1043    }
1044
1045    fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
1046        if let Some(rt) = self.task_manager.as_ref() {
1047            rt
1048        } else {
1049            self.inner.task_manager()
1050        }
1051    }
1052
1053    fn source(&self) -> Arc<dyn Source + Send + Sync> {
1054        if let Some(source) = self.source.clone() {
1055            source
1056        } else {
1057            self.inner.source()
1058        }
1059    }
1060
1061    fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
1062        if let Some(loader) = self.package_loader.clone() {
1063            loader
1064        } else {
1065            self.inner.package_loader()
1066        }
1067    }
1068
1069    fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
1070        if let Some(cache) = self.module_cache.clone() {
1071            cache
1072        } else {
1073            self.inner.module_cache()
1074        }
1075    }
1076
1077    fn engine(&self) -> Engine {
1078        if let Some(engine) = self.engine.clone() {
1079            engine
1080        } else {
1081            self.inner.engine()
1082        }
1083    }
1084
1085    fn new_store(&self) -> wasmer::Store {
1086        if let Some(engine) = self.engine.clone() {
1087            wasmer::Store::new(engine)
1088        } else {
1089            self.inner.new_store()
1090        }
1091    }
1092
1093    fn additional_imports(
1094        &self,
1095        module: &wasmer::Module,
1096        store: &mut wasmer::StoreMut,
1097    ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
1098        let (mut imports, inner_state) = self.inner.additional_imports(module, store)?;
1099        if self.instantiation_hooks.is_empty() && inner_state.is_empty() {
1100            return Ok((imports, InstantiationState::empty()));
1101        }
1102        let (own_imports, own_states) = run_import_hooks(&self.instantiation_hooks, module, store)?;
1103        imports.extend(&own_imports);
1104        Ok((
1105            imports,
1106            InstantiationState::new(OverriddenInstantiationState {
1107                inner: inner_state,
1108                own: own_states,
1109            }),
1110        ))
1111    }
1112
1113    fn configure_new_instance(
1114        &self,
1115        module: &wasmer::Module,
1116        store: &mut wasmer::StoreMut,
1117        instance: &wasmer::Instance,
1118        imported_memory: Option<&wasmer::Memory>,
1119        state: InstantiationState,
1120    ) -> anyhow::Result<()> {
1121        let state = if state.is_empty() {
1122            anyhow::ensure!(
1123                self.instantiation_hooks.is_empty(),
1124                "missing instance setup state from additional_imports"
1125            );
1126            OverriddenInstantiationState {
1127                inner: InstantiationState::empty(),
1128                own: Vec::new(),
1129            }
1130        } else {
1131            state
1132                .take::<OverriddenInstantiationState>()
1133                .context("invalid instance setup state from additional_imports")?
1134        };
1135        self.inner
1136            .configure_new_instance(module, store, instance, imported_memory, state.inner)?;
1137        run_setup_hooks(
1138            &self.instantiation_hooks,
1139            state.own,
1140            module,
1141            store,
1142            instance,
1143            imported_memory,
1144        )
1145    }
1146
1147    fn http_client(&self) -> Option<&DynHttpClient> {
1148        if let Some(client) = self.http_client.as_ref() {
1149            Some(client)
1150        } else {
1151            self.inner.http_client()
1152        }
1153    }
1154
1155    fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
1156        if let Some(tty) = self.tty.as_ref() {
1157            Some(tty.deref())
1158        } else {
1159            self.inner.tty()
1160        }
1161    }
1162
1163    #[cfg(feature = "journal")]
1164    fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
1165        if let Some(journals) = self.read_only_journals.as_ref() {
1166            Box::new(journals.iter().cloned())
1167        } else {
1168            self.inner.read_only_journals()
1169        }
1170    }
1171
1172    #[cfg(feature = "journal")]
1173    fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
1174        if let Some(journals) = self.writable_journals.as_ref() {
1175            Box::new(journals.iter().cloned())
1176        } else {
1177            self.inner.writable_journals()
1178        }
1179    }
1180
1181    #[cfg(feature = "journal")]
1182    fn active_journal(&self) -> Option<&'_ DynJournal> {
1183        if let Some(journals) = self.writable_journals.as_ref() {
1184            journals.iter().last().map(|a| a.as_ref())
1185        } else {
1186            self.inner.active_journal()
1187        }
1188    }
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193    use super::InstantiationState;
1194
1195    #[test]
1196    fn instantiation_state_round_trips_the_hook_data() {
1197        let state = InstantiationState::new(42u32);
1198        assert!(!state.is_empty());
1199        assert_eq!(state.take::<u32>().unwrap(), 42);
1200    }
1201
1202    #[test]
1203    fn empty_instantiation_state_carries_nothing() {
1204        let state = InstantiationState::empty();
1205        assert!(state.is_empty());
1206        let err = state.take::<u32>().unwrap_err();
1207        assert!(err.to_string().contains("missing instantiation state"));
1208    }
1209
1210    #[test]
1211    fn instantiation_state_from_another_hook_is_rejected() {
1212        // What a hook receiving state that isn't its own must see, rather than
1213        // silently operating on another instantiation's data.
1214        let state = InstantiationState::new("some other hook's state");
1215        let err = state.take::<u32>().unwrap_err();
1216        assert!(err.to_string().contains("does not belong to this hook"));
1217    }
1218}