Skip to main content

wasmtime_cli/commands/
serve.rs

1use crate::common::{HttpHooks, Profile, RunCommon, RunTarget};
2use clap::Parser;
3use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode};
4use http_body_util::{BodyExt as _, Full};
5use hyper::server::conn::http1;
6use pin_project_lite::pin_project;
7use std::convert::Infallible;
8use std::ffi::OsString;
9use std::net::SocketAddr;
10use std::pin::Pin;
11use std::task::{Context, Poll};
12use std::{
13    path::PathBuf,
14    sync::{
15        Arc, Mutex,
16        atomic::{AtomicBool, AtomicU64, Ordering},
17    },
18    time::{Duration, Instant},
19};
20use tokio::io::{self, AsyncWrite};
21use tokio::sync::{Notify, Semaphore};
22use wasmtime::component::{Component, GuestTaskId, Linker};
23use wasmtime::error::Context as _;
24use wasmtime::{
25    AsContextMut as _, Engine, Result, Store, StoreContextMut, StoreLimits, UpdateDeadline, bail,
26};
27use wasmtime_cli_flags::opt::WasmtimeOptionValue;
28use wasmtime_wasi::p2::{StreamError, StreamResult};
29use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
30use wasmtime_wasi_http::WasiHttpCtx;
31use wasmtime_wasi_http::handler::{
32    HandlerState, Instance, Prepared, Proxy, ProxyHandler, ProxyPre, ShouldAccept,
33    WorkerExpiration, WorkerState, WorkerStatus,
34};
35use wasmtime_wasi_http::io::TokioIo;
36
37#[cfg(feature = "debug")]
38use crate::commands::run::RunCommand;
39
40#[cfg(feature = "wasi-config")]
41use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
42#[cfg(feature = "wasi-keyvalue")]
43use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
44#[cfg(feature = "wasi-nn")]
45use wasmtime_wasi_nn::wit::WasiNnCtx;
46
47const DEFAULT_WASIP3_MAX_INSTANCE_REUSE_COUNT: usize = 128;
48const DEFAULT_WASIP2_MAX_INSTANCE_REUSE_COUNT: usize = 1;
49const DEFAULT_WASIP3_MAX_INSTANCE_CONCURRENT_REUSE_COUNT: usize = 16;
50
51struct Host {
52    table: wasmtime::component::ResourceTable,
53    ctx: WasiCtx,
54    http: WasiHttpCtx,
55    hooks: HttpHooks,
56
57    limits: StoreLimits,
58
59    #[cfg(feature = "wasi-nn")]
60    nn: Option<WasiNnCtx>,
61
62    #[cfg(feature = "wasi-config")]
63    wasi_config: Option<WasiConfigVariables>,
64
65    #[cfg(feature = "wasi-keyvalue")]
66    wasi_keyvalue: Option<WasiKeyValueCtx>,
67
68    #[cfg(feature = "profiling")]
69    guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
70
71    write_profile: Option<WriteProfile>,
72}
73
74impl WasiView for Host {
75    fn ctx(&mut self) -> WasiCtxView<'_> {
76        WasiCtxView {
77            ctx: &mut self.ctx,
78            table: &mut self.table,
79        }
80    }
81}
82
83impl wasmtime_wasi_http::WasiHttpView for Host {
84    fn http(&mut self) -> wasmtime_wasi_http::WasiHttpCtxView<'_> {
85        wasmtime_wasi_http::WasiHttpCtxView {
86            ctx: &mut self.http,
87            table: &mut self.table,
88            hooks: &mut self.hooks,
89        }
90    }
91}
92
93const DEFAULT_ADDR: std::net::SocketAddr = std::net::SocketAddr::new(
94    std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
95    8080,
96);
97
98fn parse_duration(s: &str) -> Result<Duration, String> {
99    Duration::parse(Some(s)).map_err(|e| e.to_string())
100}
101
102/// Runs a WebAssembly module
103#[derive(Parser)]
104pub struct ServeCommand {
105    #[command(flatten)]
106    run: RunCommon,
107
108    /// Socket address for the web server to bind to.
109    #[arg(long , value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR)]
110    addr: SocketAddr,
111
112    /// Socket address where, when connected to, will initiate a graceful
113    /// shutdown.
114    ///
115    /// Note that graceful shutdown is also supported on ctrl-c.
116    #[arg(long, value_name = "SOCKADDR")]
117    shutdown_addr: Option<SocketAddr>,
118
119    /// Disable log prefixes of wasi-http handlers.
120    /// if unspecified, logs will be prefixed with 'stdout|stderr [{req_id}] :: '
121    #[arg(long)]
122    no_logging_prefix: bool,
123
124    /// The WebAssembly component to run.
125    #[arg(value_name = "WASM", required = true)]
126    component: PathBuf,
127
128    /// Maximum number of requests to send to a single component instance before
129    /// dropping it.
130    ///
131    /// This defaults to 1 for WASIp2 components and 128 for WASIp3 components.
132    #[arg(long)]
133    max_instance_reuse_count: Option<usize>,
134
135    /// Maximum number of concurrent requests to send to a single component
136    /// instance.
137    ///
138    /// This defaults to 1 for WASIp2 components and 16 for WASIp3 components.
139    /// Note that setting it to more than 1 will have no effect for WASIp2
140    /// components since they cannot be called concurrently.
141    #[arg(long)]
142    max_instance_concurrent_reuse_count: Option<usize>,
143
144    /// Time to hold an idle component instance for possible reuse before
145    /// dropping it.
146    ///
147    /// A number with no suffix or with an `s` suffix is interpreted as seconds;
148    /// other accepted suffixes include `ms` (milliseconds), `us` or `μs`
149    /// (microseconds), and `ns` (nanoseconds).
150    #[arg(long, default_value = "1s", value_parser = parse_duration)]
151    idle_instance_timeout: Duration,
152
153    /// Replace or add a request header before forwarding it to the component.
154    ///
155    /// The argument must have the form `name: value`. May be specified more
156    /// than once. An argument beginning with `@` is treated as a file containing
157    /// one header per line.
158    #[arg(short = 'H', long = "header", value_name = "HEADER")]
159    headers: Vec<String>,
160
161    /// Maximum number of concurrent requests that can be processed at any one
162    /// point in time.
163    #[arg(long)]
164    max_concurrent_requests: Option<usize>,
165    /// Maximum number of concurrent connections that can be held at any one
166    /// point in time.
167    #[arg(long)]
168    max_concurrent_connections: Option<usize>,
169}
170
171impl ServeCommand {
172    /// Start a server to run the given wasi-http proxy component
173    pub fn execute(mut self) -> Result<()> {
174        self.run.common.init_logging()?;
175
176        // We force cli errors before starting to listen for connections so then
177        // we don't accidentally delay them to the first request.
178
179        if self.run.common.wasi.nn == Some(true) {
180            #[cfg(not(feature = "wasi-nn"))]
181            {
182                bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
183            }
184        }
185
186        if self.run.common.wasi.threads == Some(true) {
187            bail!("wasi-threads does not support components yet")
188        }
189
190        // The serve command requires both wasi-http and the component model, so
191        // we enable those by default here.
192        if self.run.common.wasi.http.replace(true) == Some(false) {
193            bail!("wasi-http is required for the serve command, and must not be disabled");
194        }
195        if self.run.common.wasm.component_model.replace(true) == Some(false) {
196            bail!("components are required for the serve command, and must not be disabled");
197        }
198
199        let runtime = tokio::runtime::Builder::new_multi_thread()
200            .enable_time()
201            .enable_io()
202            .build()?;
203
204        runtime.block_on(self.serve())?;
205
206        Ok(())
207    }
208
209    /// Set up the debugger component side-car, mirroring
210    /// [`RunCommand::debugger_run`].
211    #[cfg(feature = "debug")]
212    fn debugger_setup(&mut self) -> Result<Option<RunCommand>> {
213        fn set_implicit_option(
214            place: &str,
215            name: &str,
216            setting: &mut Option<bool>,
217            value: bool,
218        ) -> Result<()> {
219            if *setting == Some(!value) {
220                bail!(
221                    "Explicitly-set option on {place} {name}={} is not compatible \
222                     with debugging-implied setting {value}",
223                    setting.unwrap()
224                );
225            }
226            *setting = Some(value);
227            Ok(())
228        }
229
230        #[cfg(feature = "gdbstub")]
231        let override_bytes = if let Some(addr) = self.run.gdbstub.as_deref() {
232            if self.run.common.debug.debugger.is_some() {
233                bail!("-g/--gdb cannot be combined with -Ddebugger=");
234            }
235            let addr = if addr.parse::<u16>().is_ok() {
236                format!("127.0.0.1:{addr}")
237            } else {
238                use std::net::SocketAddr as SA;
239                addr.parse::<SA>()
240                    .with_context(|| format!("invalid gdbstub address: `{addr}`"))?;
241                addr.to_string()
242            };
243            self.run.common.debug.debugger = Some("<built-in gdbstub>".into());
244            self.run.common.debug.arg.push(addr);
245            Some(gdbstub_component_artifact::GDBSTUB_COMPONENT)
246        } else {
247            None
248        };
249        #[cfg(not(feature = "gdbstub"))]
250        let override_bytes = None;
251
252        if let Some(debugger_component_path) = self.run.common.debug.debugger.as_ref() {
253            set_implicit_option(
254                "debuggee",
255                "guest_debug",
256                &mut self.run.common.debug.guest_debug,
257                true,
258            )?;
259            set_implicit_option(
260                "debuggee",
261                "epoch_interruption",
262                &mut self.run.common.wasm.epoch_interruption,
263                true,
264            )?;
265
266            let mut debugger_run = RunCommand::try_parse_from(
267                ["run".into(), debugger_component_path.into()]
268                    .into_iter()
269                    .chain(self.run.common.debug.arg.iter().map(OsString::from)),
270            )?;
271            debugger_run.module_bytes = override_bytes;
272
273            debugger_run.run.common.wasi.tcp.get_or_insert(true);
274            debugger_run
275                .run
276                .common
277                .wasi
278                .inherit_network
279                .get_or_insert(true);
280
281            set_implicit_option(
282                "debugger",
283                "inherit_stdin",
284                &mut debugger_run.run.common.wasi.inherit_stdin,
285                self.run.common.debug.inherit_stdin.unwrap_or(false),
286            )?;
287            set_implicit_option(
288                "debugger",
289                "inherit_stdout",
290                &mut debugger_run.run.common.wasi.inherit_stdout,
291                self.run.common.debug.inherit_stdout.unwrap_or(false),
292            )?;
293            set_implicit_option(
294                "debugger",
295                "inherit_stderr",
296                &mut debugger_run.run.common.wasi.inherit_stderr,
297                self.run.common.debug.inherit_stderr.unwrap_or(false),
298            )?;
299            Ok(Some(debugger_run))
300        } else {
301            Ok(None)
302        }
303    }
304
305    /// Run the HTTP server under a debugger component.
306    ///
307    /// Uses a single store and instance to handle all requests
308    /// sequentially, so the debugger can pause and inspect state.
309    #[cfg(feature = "debug")]
310    async fn serve_under_debugger(
311        self,
312        mut debug_run: RunCommand,
313        linker: Linker<Host>,
314        component: Component,
315    ) -> Result<()> {
316        let mut debuggee_store = self.new_store(linker.engine(), None)?;
317
318        // Pre-register component modules so the debugger can see
319        // them and set breakpoints at the initial stop.
320        debuggee_store.debug_register_component(&component)?;
321
322        let debug_engine = debug_run.new_engine()?;
323        let debug_main = debug_run.run.load_module(
324            &debug_engine,
325            debug_run.module_and_args[0].as_ref(),
326            debug_run.module_bytes.as_ref().map(|v| &v[..]),
327        )?;
328        let (mut debug_store, debug_linker) =
329            debug_run.new_store_and_linker(&debug_engine, &debug_main)?;
330        let debug_component = match debug_main {
331            RunTarget::Core(_) => {
332                bail!("Debugger component is a core module; only components are supported")
333            }
334            RunTarget::Component(c) => c,
335        };
336        let mut debug_linker = match debug_linker {
337            crate::commands::run::CliLinker::Core(_) => unreachable!(),
338            crate::commands::run::CliLinker::Component(l) => l,
339        };
340        debug_run.add_debugger_api(&mut debug_linker)?;
341
342        debug_run
343            .invoke_debugger(
344                &mut debug_store,
345                &debug_component,
346                &mut debug_linker,
347                debuggee_store,
348                move |store| Box::pin(self.serve_maybe_debug(linker, component, Some(store))),
349            )
350            .await
351    }
352
353    fn new_store(&self, engine: &Engine, instance_id: Option<u64>) -> Result<Store<Host>> {
354        let mut builder = WasiCtxBuilder::new();
355        self.run.configure_wasip2(&mut builder)?;
356
357        if let Some(instance_id) = instance_id {
358            builder.env("INSTANCE_ID", instance_id.to_string());
359        }
360
361        let stdout_prefix: String;
362        let stderr_prefix: String;
363        match instance_id {
364            Some(instance_id) if !self.no_logging_prefix => {
365                stdout_prefix = format!("stdout [{instance_id}] :: ");
366                stderr_prefix = format!("stderr [{instance_id}] :: ");
367            }
368            _ => {
369                stdout_prefix = "".to_string();
370                stderr_prefix = "".to_string();
371            }
372        }
373        builder.stdout(LogStream::new(stdout_prefix, Output::Stdout));
374        builder.stderr(LogStream::new(stderr_prefix, Output::Stderr));
375
376        let mut table = wasmtime::component::ResourceTable::new();
377        if let Some(max) = self.run.common.wasi.max_resources {
378            table.set_max_capacity(max);
379        }
380        let mut host = Host {
381            table,
382            ctx: builder.build(),
383            http: self.run.wasi_http_ctx()?,
384            hooks: self.run.wasi_http_hooks(),
385
386            limits: StoreLimits::default(),
387
388            #[cfg(feature = "wasi-nn")]
389            nn: None,
390            #[cfg(feature = "wasi-config")]
391            wasi_config: None,
392            #[cfg(feature = "wasi-keyvalue")]
393            wasi_keyvalue: None,
394            #[cfg(feature = "profiling")]
395            guest_profiler: None,
396            write_profile: None,
397        };
398
399        if self.run.common.wasi.nn == Some(true) {
400            #[cfg(feature = "wasi-nn")]
401            {
402                let graphs = self
403                    .run
404                    .common
405                    .wasi
406                    .nn_graph
407                    .iter()
408                    .map(|g| (g.format.clone(), g.dir.clone()))
409                    .collect::<Vec<_>>();
410                let (backends, registry) = wasmtime_wasi_nn::preload(&graphs)?;
411                host.nn.replace(WasiNnCtx::new(backends, registry));
412            }
413        }
414
415        if self.run.common.wasi.config == Some(true) {
416            #[cfg(feature = "wasi-config")]
417            {
418                let vars = WasiConfigVariables::from_iter(
419                    self.run
420                        .common
421                        .wasi
422                        .config_var
423                        .iter()
424                        .map(|v| (v.key.clone(), v.value.clone())),
425                );
426                host.wasi_config.replace(vars);
427            }
428        }
429
430        if self.run.common.wasi.keyvalue == Some(true) {
431            #[cfg(feature = "wasi-keyvalue")]
432            {
433                let ctx = WasiKeyValueCtxBuilder::new()
434                    .in_memory_data(
435                        self.run
436                            .common
437                            .wasi
438                            .keyvalue_in_memory_data
439                            .iter()
440                            .map(|v| (v.key.clone(), v.value.clone())),
441                    )
442                    .build();
443                host.wasi_keyvalue.replace(ctx);
444            }
445        }
446
447        let mut store = Store::new(engine, host);
448        self.run.configure_store(&mut store, |t| &mut t.limits)?;
449        Ok(store)
450    }
451
452    fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
453        self.run.validate_p3_option()?;
454        let cli = self.run.validate_cli_enabled()?;
455
456        // Repurpose the `-Scli` flag of `wasmtime run` for `wasmtime serve`
457        // to serve as a signal to enable all WASI interfaces instead of just
458        // those in the `proxy` world. If `-Scli` is present then add all
459        // `command` APIs and then additionally add in the required HTTP APIs.
460        //
461        // If `-Scli` isn't passed then use the `add_to_linker_async`
462        // bindings which adds just those interfaces that the proxy interface
463        // uses.
464        if cli == Some(true) {
465            self.run.add_wasmtime_wasi_to_linker(linker)?;
466            wasmtime_wasi_http::p2::add_only_http_to_linker_async(linker)?;
467            #[cfg(feature = "component-model-async")]
468            if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
469                wasmtime_wasi_http::p3::add_to_linker(linker)?;
470            }
471        } else {
472            wasmtime_wasi_http::p2::add_to_linker_async(linker)?;
473            #[cfg(feature = "component-model-async")]
474            if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
475                wasmtime_wasi_http::p3::add_to_linker(linker)?;
476                wasmtime_wasi::p3::clocks::add_to_linker(linker)?;
477                wasmtime_wasi::p3::random::add_to_linker(linker)?;
478                wasmtime_wasi::p3::cli::add_to_linker(linker)?;
479            }
480        }
481
482        if self.run.common.wasi.nn == Some(true) {
483            #[cfg(not(feature = "wasi-nn"))]
484            {
485                bail!("support for wasi-nn was disabled at compile time");
486            }
487            #[cfg(feature = "wasi-nn")]
488            {
489                wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
490                    let ctx = h.nn.as_mut().unwrap();
491                    wasmtime_wasi_nn::wit::WasiNnView::new(&mut h.table, ctx)
492                })?;
493            }
494        }
495
496        if self.run.common.wasi.config == Some(true) {
497            #[cfg(not(feature = "wasi-config"))]
498            {
499                bail!("support for wasi-config was disabled at compile time");
500            }
501            #[cfg(feature = "wasi-config")]
502            {
503                wasmtime_wasi_config::add_to_linker(linker, |h| {
504                    WasiConfig::from(h.wasi_config.as_ref().unwrap())
505                })?;
506            }
507        }
508
509        if self.run.common.wasi.keyvalue == Some(true) {
510            #[cfg(not(feature = "wasi-keyvalue"))]
511            {
512                bail!("support for wasi-keyvalue was disabled at compile time");
513            }
514            #[cfg(feature = "wasi-keyvalue")]
515            {
516                wasmtime_wasi_keyvalue::add_to_linker(linker, |h: &mut Host| {
517                    WasiKeyValue::new(h.wasi_keyvalue.as_ref().unwrap(), &mut h.table)
518                })?;
519            }
520        }
521
522        if self.run.common.wasi.threads == Some(true) {
523            bail!("support for wasi-threads is not available with components");
524        }
525
526        if self.run.common.wasi.http == Some(false) {
527            bail!("support for wasi-http must be enabled for `serve` subcommand");
528        }
529
530        Ok(())
531    }
532
533    async fn serve(mut self) -> Result<()> {
534        #[cfg(feature = "debug")]
535        let debug_run = self.debugger_setup()?;
536
537        let mut config = self
538            .run
539            .common
540            .config(use_pooling_allocator_by_default().unwrap_or(None))?;
541        config.wasm_component_model(true);
542
543        if self.run.common.wasm.timeout.is_some() {
544            config.epoch_interruption(true);
545        }
546
547        match self.run.profile {
548            Some(Profile::Native(s)) => {
549                config.profiler(s);
550            }
551            Some(Profile::Guest { .. }) => {
552                config.epoch_interruption(true);
553            }
554            None => {}
555        }
556
557        let engine = Engine::new(&config)?;
558        let mut linker = Linker::new(&engine);
559
560        self.add_to_linker(&mut linker)?;
561
562        let component = match self.run.load_module(&engine, &self.component, None)? {
563            RunTarget::Core(_) => bail!("The serve command currently requires a component"),
564            RunTarget::Component(c) => c,
565        };
566
567        #[cfg(feature = "debug")]
568        if let Some(debug_run) = debug_run {
569            return self
570                .serve_under_debugger(debug_run, linker, component)
571                .await;
572        }
573
574        self.serve_maybe_debug(linker, component, None).await
575    }
576
577    async fn serve_maybe_debug(
578        self,
579        linker: Linker<Host>,
580        component: Component,
581        mut debuggee_store: Option<&mut Store<Host>>,
582    ) -> Result<()> {
583        let engine = linker.engine();
584        let request_headers = RequestHeaders::parse(&self.headers)?;
585        let instance = linker.instantiate_pre(&component)?;
586        #[cfg(feature = "component-model-async")]
587        let instance = match wasmtime_wasi_http::p3::bindings::ServicePre::new(instance.clone()) {
588            Ok(pre) => ProxyPre::P3(pre),
589            Err(_) => ProxyPre::P2(wasmtime_wasi_http::p2::bindings::ProxyPre::new(instance)?),
590        };
591        #[cfg(not(feature = "component-model-async"))]
592        let instance = ProxyPre::P2(wasmtime_wasi_http::p2::bindings::ProxyPre::new(instance)?);
593
594        // Spawn background task(s) waiting for graceful shutdown signals. This
595        // always listens for ctrl-c but additionally can listen for a TCP
596        // connection to the specified address.
597        let shutdown = Arc::new(GracefulShutdown::default());
598        tokio::task::spawn({
599            let shutdown = shutdown.clone();
600            async move {
601                tokio::signal::ctrl_c().await.unwrap();
602                shutdown.requested.notify_one();
603            }
604        });
605        if let Some(addr) = self.shutdown_addr {
606            let listener = tokio::net::TcpListener::bind(addr).await?;
607            eprintln!(
608                "Listening for shutdown on tcp://{}/",
609                listener.local_addr()?
610            );
611            let shutdown = shutdown.clone();
612            tokio::task::spawn(async move {
613                let _ = listener.accept().await;
614                shutdown.requested.notify_one();
615            });
616        }
617
618        let socket = match &self.addr {
619            SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
620            SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
621        };
622        // Conditionally enable `SO_REUSEADDR` depending on the current
623        // platform. On Unix we want this to be able to rebind an address in
624        // the `TIME_WAIT` state which can happen then a server is killed with
625        // active TCP connections and then restarted. On Windows though if
626        // `SO_REUSEADDR` is specified then it enables multiple applications to
627        // bind the port at the same time which is not something we want. Hence
628        // this is conditionally set based on the platform (and deviates from
629        // Tokio's default from always-on).
630        socket.set_reuseaddr(!cfg!(windows))?;
631        socket.bind(self.addr)?;
632        let listener = socket.listen(100)?;
633
634        eprintln!("Serving HTTP on http://{}/", listener.local_addr()?);
635
636        log::info!("Listening on {}", self.addr);
637
638        let epoch_interval = if let Some(Profile::Guest { interval, .. }) = self.run.profile {
639            Some(interval)
640        } else if let Some(t) = self.run.common.wasm.timeout {
641            Some(EPOCH_INTERRUPT_PERIOD.min(t))
642        } else if debuggee_store.is_some() {
643            Some(Duration::from_millis(1))
644        } else {
645            None
646        };
647        let _epoch_thread = epoch_interval.map(|t| EpochThread::spawn(t, engine.clone()));
648
649        let max_instance_reuse_count = self.max_instance_reuse_count.unwrap_or_else(|| {
650            if let ProxyPre::P3(_) = &instance {
651                DEFAULT_WASIP3_MAX_INSTANCE_REUSE_COUNT
652            } else {
653                DEFAULT_WASIP2_MAX_INSTANCE_REUSE_COUNT
654            }
655        });
656
657        let max_instance_concurrent_reuse_count = if let ProxyPre::P3(_) = &instance {
658            self.max_instance_concurrent_reuse_count
659                .unwrap_or(DEFAULT_WASIP3_MAX_INSTANCE_CONCURRENT_REUSE_COUNT)
660        } else {
661            1
662        };
663
664        let max_concurrent_connections = self
665            .max_concurrent_connections
666            .unwrap_or(if debuggee_store.is_some() { 1 } else { 1000 });
667        let max_concurrent_requests = self
668            .max_concurrent_requests
669            .unwrap_or(if debuggee_store.is_some() { 1 } else { 1000 });
670        if debuggee_store.is_some() && max_concurrent_connections != 1 {
671            bail!("cannot have more than 1 max concurrent connections with a debugger");
672        }
673        if debuggee_store.is_some() && max_concurrent_requests != 1 {
674            bail!("cannot have more than 1 max concurrent requests with a debugger");
675        }
676
677        let sem_connections = Arc::new(Semaphore::new(max_concurrent_connections));
678
679        let handler = ProxyHandler::new(HostHandlerState {
680            sem_requests: Semaphore::new(max_concurrent_requests),
681            cmd: self,
682            component,
683            request_headers,
684            max_instance_reuse_count,
685            max_instance_concurrent_reuse_count,
686            instance,
687            next_instance_id: AtomicU64::default(),
688            next_request_id: AtomicU64::default(),
689            // Give one shutdown guard to this handler which will track the
690            // full lifetime of any instances spawned.
691            _shutdown_guard: Box::new(shutdown.clone().increment()),
692        });
693
694        loop {
695            // Wait for a socket, but also "race" against shutdown to break out
696            // of this loop. Once the graceful shutdown signal is received then
697            // this loop exits immediately.
698            let (connection_permit, stream) = tokio::select! {
699                _ = shutdown.requested.notified() => break,
700                v = async {
701                    let permit = sem_connections.clone().acquire_owned().await?;
702                    let (stream, _) = listener.accept().await?;
703                    wasmtime::error::Ok((permit, stream))
704                } => v?,
705            };
706
707            // The Nagle algorithm can impose a significant latency penalty
708            // (e.g. 40ms on Linux) on guests which write small, intermittent
709            // response body chunks (e.g. SSE streams).  Here we disable that
710            // algorithm and rely on the guest to buffer if appropriate to avoid
711            // TCP fragmentation.
712            stream.set_nodelay(true)?;
713
714            // In addition to the shutdown guard given to the handler above,
715            // also give one to the tokio tasks doing HTTP I/O as well to ensure
716            // it keeps them alive too.
717            let shutdown_guard = shutdown.clone().increment();
718
719            // When debugging, handle the client synchronously since
720            // concurrent requests can't be served. Otherwise though spawn a
721            // task to handle this client.
722            match &mut debuggee_store {
723                Some(store) => handle_client(stream, &handler, Some(store)).await,
724                None => {
725                    let handler = handler.clone();
726                    tokio::task::spawn(async move {
727                        handle_client(stream, &handler, None).await;
728                        drop(shutdown_guard);
729                        drop(connection_permit);
730                    });
731                }
732            }
733        }
734
735        // Don't allow any further requests to get picked up.
736        handler.state().sem_requests.close();
737
738        drop(handler);
739
740        // Upon exiting the loop we'll no longer process any more incoming
741        // connections but there may still be outstanding connections
742        // processing in child tasks. If there are wait for those to complete
743        // before shutting down completely. Also enable short-circuiting this
744        // wait with a second ctrl-c signal.
745        if shutdown.close() {
746            return Ok(());
747        }
748        eprintln!("Waiting for child tasks to exit, ctrl-c again to quit sooner...");
749        tokio::select! {
750            _ = tokio::signal::ctrl_c() => {}
751            _ = shutdown.complete.notified() => {}
752        }
753
754        Ok(())
755    }
756}
757
758pin_project! {
759    struct HostWorkerExpiration {
760        idle_timeout: Duration,
761        request_timeout: Duration,
762        #[pin]
763        sleep: tokio::time::Sleep,
764    }
765}
766
767impl WorkerExpiration for HostWorkerExpiration {
768    fn poll(
769        self: Pin<&mut Self>,
770        cx: &mut Context<'_>,
771        status: WorkerStatus,
772        start: Instant,
773    ) -> Poll<()> {
774        let mut me = self.project();
775
776        let timeout = match status {
777            WorkerStatus::Idle => *me.idle_timeout,
778            // TODO: add a dedicated `post_return_timeout` config setting
779            // instead of reusing `request_timeout` for
780            // `WorkerStatus::PostReturn` here
781            WorkerStatus::Requests | WorkerStatus::PostReturn => *me.request_timeout,
782        };
783
784        if let Some(deadline) = start.checked_add(timeout) {
785            let deadline = deadline.into();
786            if deadline != me.sleep.deadline() {
787                me.sleep.as_mut().reset(deadline);
788            }
789            me.sleep.poll(cx)
790        } else {
791            Poll::Pending
792        }
793    }
794}
795
796struct HostWorkerState {
797    instance_id: u64,
798    max_instance_reuse_count: usize,
799    max_instance_concurrent_reuse_count: usize,
800    request_timeout: Duration,
801}
802
803impl WorkerState for HostWorkerState {
804    type StoreData = Host;
805    type RequestData = u64;
806
807    fn should_accept_request(&self, concurrent_count: usize, total_count: usize) -> ShouldAccept {
808        if total_count >= self.max_instance_reuse_count {
809            ShouldAccept::Never
810        } else if concurrent_count >= self.max_instance_concurrent_reuse_count {
811            ShouldAccept::No
812        } else {
813            ShouldAccept::Yes
814        }
815    }
816
817    fn on_request_start(
818        &self,
819        _store: StoreContextMut<Host>,
820        request_id: u64,
821        _task_id: GuestTaskId,
822    ) -> Pin<Box<dyn Future<Output = ()> + 'static + Send + Sync>> {
823        log::info!(
824            "Instance {} handling request {request_id}",
825            self.instance_id,
826        );
827
828        Box::pin(tokio::time::sleep(self.request_timeout))
829    }
830
831    fn drop(&self, mut store: Store<Self::StoreData>, result: Result<(), wasmtime::Error>) {
832        if let Err(error) = result {
833            eprintln!("worker failed: {error:?}");
834        }
835
836        if let Some(write_profile) = store.data_mut().write_profile.take() {
837            write_profile(store.as_context_mut());
838        }
839
840        drop(store);
841    }
842}
843
844struct HostHandlerState {
845    cmd: ServeCommand,
846    component: Component,
847    request_headers: RequestHeaders,
848    max_instance_reuse_count: usize,
849    max_instance_concurrent_reuse_count: usize,
850    instance: ProxyPre<Host>,
851    next_instance_id: AtomicU64,
852    next_request_id: AtomicU64,
853    sem_requests: Semaphore,
854    _shutdown_guard: Box<dyn std::any::Any + Send + Sync>,
855}
856
857impl HostHandlerState {
858    async fn instantiate_into(&self, store: &mut Store<Host>) -> Result<Proxy> {
859        let write_profile = setup_epoch_handler(&self.cmd, &mut *store, self.component.clone())?;
860        store.data_mut().write_profile = Some(write_profile);
861        self.instance.instantiate_async(&mut *store).await
862    }
863}
864
865impl HandlerState for HostHandlerState {
866    type StoreData = Host;
867    type WorkerExpiration = HostWorkerExpiration;
868    type WorkerState = HostWorkerState;
869
870    async fn instantiate(
871        &self,
872    ) -> Result<Instance<Self::StoreData, Self::WorkerExpiration, Self::WorkerState>> {
873        let instance_id = self.next_instance_id.fetch_add(1, Ordering::Relaxed);
874        let mut store = self
875            .cmd
876            .new_store(self.component.engine(), Some(instance_id))?;
877        let proxy = self.instantiate_into(&mut store).await?;
878
879        Ok(Instance {
880            store,
881            proxy,
882            view: wasmtime_wasi_http::WasiHttpView::http,
883            expiration: HostWorkerExpiration {
884                idle_timeout: self.cmd.idle_instance_timeout,
885                request_timeout: self.cmd.run.common.wasm.timeout.unwrap_or(Duration::MAX),
886                sleep: tokio::time::sleep(Duration::MAX),
887            },
888            state: HostWorkerState {
889                max_instance_reuse_count: self.max_instance_reuse_count,
890                max_instance_concurrent_reuse_count: self.max_instance_concurrent_reuse_count,
891                instance_id,
892                request_timeout: self.cmd.run.common.wasm.timeout.unwrap_or(Duration::MAX),
893            },
894        })
895    }
896}
897
898/// Helper structure to manage graceful shutdown int he accept loop above.
899#[derive(Default)]
900struct GracefulShutdown {
901    /// Async notification that shutdown has been requested.
902    requested: Notify,
903    /// Async notification that shutdown has completed, signaled when
904    /// `notify_when_done` is `true` and `active_tasks` reaches 0.
905    complete: Notify,
906    /// Internal state related to what's in progress when shutdown is requested.
907    state: Mutex<GracefulShutdownState>,
908}
909
910#[derive(Default)]
911struct GracefulShutdownState {
912    active_tasks: u32,
913    notify_when_done: bool,
914}
915
916impl GracefulShutdown {
917    /// Increments the number of active tasks and returns a guard indicating
918    fn increment(self: Arc<Self>) -> impl Drop + Send + Sync {
919        struct Guard(Arc<GracefulShutdown>);
920
921        let mut state = self.state.lock().unwrap();
922        assert!(!state.notify_when_done);
923        state.active_tasks += 1;
924        drop(state);
925
926        return Guard(self);
927
928        impl Drop for Guard {
929            fn drop(&mut self) {
930                let mut state = self.0.state.lock().unwrap();
931                state.active_tasks -= 1;
932                if state.notify_when_done && state.active_tasks == 0 {
933                    self.0.complete.notify_one();
934                }
935            }
936        }
937    }
938
939    /// Flags this state as done spawning tasks and returns whether there are no
940    /// more child tasks remaining.
941    fn close(&self) -> bool {
942        let mut state = self.state.lock().unwrap();
943        state.notify_when_done = true;
944        state.active_tasks == 0
945    }
946}
947
948/// When executing with a timeout enabled, this is how frequently epoch
949/// interrupts will be executed to check for timeouts. If guest profiling
950/// is enabled, the guest epoch period will be used.
951const EPOCH_INTERRUPT_PERIOD: Duration = Duration::from_millis(50);
952
953struct EpochThread {
954    shutdown: Arc<AtomicBool>,
955    handle: Option<std::thread::JoinHandle<()>>,
956}
957
958impl EpochThread {
959    fn spawn(interval: std::time::Duration, engine: Engine) -> Self {
960        let shutdown = Arc::new(AtomicBool::new(false));
961        let handle = {
962            let shutdown = Arc::clone(&shutdown);
963            let handle = std::thread::spawn(move || {
964                while !shutdown.load(Ordering::Relaxed) {
965                    std::thread::sleep(interval);
966                    engine.increment_epoch();
967                }
968            });
969            Some(handle)
970        };
971
972        EpochThread { shutdown, handle }
973    }
974}
975
976impl Drop for EpochThread {
977    fn drop(&mut self) {
978        if let Some(handle) = self.handle.take() {
979            self.shutdown.store(true, Ordering::Relaxed);
980            handle.join().unwrap();
981        }
982    }
983}
984
985type WriteProfile = Box<dyn FnOnce(StoreContextMut<Host>) + Send>;
986
987fn setup_epoch_handler(
988    cmd: &ServeCommand,
989    store: &mut Store<Host>,
990    component: Component,
991) -> Result<WriteProfile> {
992    // Profiling Enabled
993    if let Some(Profile::Guest { interval, path }) = &cmd.run.profile {
994        #[cfg(feature = "profiling")]
995        return setup_guest_profiler(store, path.clone(), *interval, component.clone());
996        #[cfg(not(feature = "profiling"))]
997        {
998            let _ = (path, interval);
999            bail!("support for profiling disabled at compile time!");
1000        }
1001    }
1002
1003    // Profiling disabled but there's a global request timeout
1004    if cmd.run.common.wasm.timeout.is_some() || cmd.run.common.debug.debugger.is_some() {
1005        store.epoch_deadline_async_yield_and_update(1);
1006        store.set_epoch_deadline(1);
1007    }
1008
1009    Ok(Box::new(|_store| {}))
1010}
1011
1012#[cfg(feature = "profiling")]
1013fn setup_guest_profiler(
1014    store: &mut Store<Host>,
1015    path: String,
1016    interval: Duration,
1017    component: Component,
1018) -> Result<WriteProfile> {
1019    use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut};
1020
1021    let module_name = "<main>";
1022
1023    store.data_mut().guest_profiler = Some(Arc::new(GuestProfiler::new_component(
1024        store.engine(),
1025        module_name,
1026        interval,
1027        component,
1028        std::iter::empty(),
1029    )?));
1030
1031    fn sample(
1032        mut store: StoreContextMut<Host>,
1033        f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
1034    ) {
1035        let mut profiler = store.data_mut().guest_profiler.take().unwrap();
1036        f(
1037            Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
1038            store.as_context(),
1039        );
1040        store.data_mut().guest_profiler = Some(profiler);
1041    }
1042
1043    // Hostcall entry/exit, etc.
1044    store.call_hook(|store, kind| {
1045        sample(store, |profiler, store| profiler.call_hook(store, kind));
1046        Ok(())
1047    });
1048
1049    store.epoch_deadline_callback(move |store| {
1050        sample(store, |profiler, store| {
1051            profiler.sample(store, std::time::Duration::ZERO)
1052        });
1053
1054        Ok(UpdateDeadline::Continue(1))
1055    });
1056
1057    store.set_epoch_deadline(1);
1058
1059    let write_profile = Box::new(move |mut store: StoreContextMut<Host>| {
1060        let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
1061            .expect("profiling doesn't support threads yet");
1062        if let Err(e) = std::fs::File::create(&path)
1063            .map_err(wasmtime::Error::new)
1064            .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
1065        {
1066            eprintln!("failed writing profile at {path}: {e:#}");
1067        } else {
1068            eprintln!();
1069            eprintln!("Profile written to: {path}");
1070            eprintln!("View this profile at https://profiler.firefox.com/.");
1071        }
1072    });
1073
1074    Ok(write_profile)
1075}
1076
1077type Request = hyper::Request<hyper::body::Incoming>;
1078
1079async fn handle_client(
1080    client: tokio::net::TcpStream,
1081    handler: &ProxyHandler<HostHandlerState>,
1082    debuggee_store: Option<&mut Store<Host>>,
1083) {
1084    // Hyper's `service_fn` takes an `Fn` closure, so to bridge the need to
1085    // transfer a mutable store to each request for debugging a tokio mutex is
1086    // used. The tokio mutex is required as the returned future must also be
1087    // `Send`.
1088    let lock = &debuggee_store.map(tokio::sync::Mutex::new);
1089
1090    if let Err(e) = http1::Builder::new()
1091        .keep_alive(true)
1092        .serve_connection(
1093            TokioIo::new(client),
1094            hyper::service::service_fn(move |req| async move {
1095                let mut debuggee_store = match &lock {
1096                    Some(store) => Some(store.lock().await),
1097                    None => None,
1098                };
1099                let debuggee_store = debuggee_store.as_mut().map(|s| &mut ***s);
1100                match handle_request(handler, debuggee_store, req).await {
1101                    Ok(r) => Ok::<_, Infallible>(r),
1102                    Err(e) => {
1103                        eprintln!("error: {e:?}");
1104                        let error_html = "\
1105<!doctype html>
1106<html>
1107<head>
1108    <title>500 Internal Server Error</title>
1109</head>
1110<body>
1111    <center>
1112        <h1>500 Internal Server Error</h1>
1113        <hr>
1114        wasmtime
1115    </center>
1116</body>
1117</html>";
1118                        Ok(Response::builder()
1119                            .status(StatusCode::INTERNAL_SERVER_ERROR)
1120                            .header("Content-Type", "text/html; charset=UTF-8")
1121                            .body(
1122                                Full::new(bytes::Bytes::from(error_html))
1123                                    .map_err(|_| unreachable!())
1124                                    .boxed_unsync(),
1125                            )
1126                            .unwrap())
1127                    }
1128                }
1129            }),
1130        )
1131        .await
1132    {
1133        eprintln!("error: {e:?}");
1134    }
1135}
1136
1137async fn handle_request(
1138    handler: &ProxyHandler<HostHandlerState>,
1139    debuggee_store: Option<&mut Store<Host>>,
1140    mut req: Request,
1141) -> Result<hyper::Response<wasmtime_wasi_http::WasiBody>> {
1142    // This is used to throttle the maximum number of concurrent requests that
1143    // can be processed at any one point in time before delegating to
1144    // `handler.handle(...)` below.
1145    let _request_permit = handler.state().sem_requests.acquire().await?;
1146
1147    handler.state().request_headers.apply(req.headers_mut());
1148
1149    let request_id = handler
1150        .state()
1151        .next_request_id
1152        .fetch_add(1, Ordering::Relaxed);
1153    log::info!(
1154        "Received request {request_id}: {} {}",
1155        req.method(),
1156        req.uri()
1157    );
1158
1159    let req = req.map(|body| body.map_err(|e| e.into()).boxed_unsync());
1160
1161    match debuggee_store {
1162        // For debugging go ahead and synchronously execute the instance here
1163        // in a single instance. This is debugging-specific to use the store
1164        // passed in.
1165        Some(store) => {
1166            let instance = handler.state().instantiate_into(store).await?;
1167            let (tx, rx) = futures::channel::oneshot::channel();
1168            let prepared = Prepared::new(
1169                store.as_context_mut(),
1170                &instance,
1171                req,
1172                wasmtime_wasi_http::WasiHttpView::http,
1173                tx,
1174            )?;
1175            store
1176                .run_concurrent(async |store| prepared.run(store, std::future::pending()).await)
1177                .await??;
1178            rx.await?
1179        }
1180
1181        // For when debugging is disabled delegate to the default handling path.
1182        None => handler.handle(request_id, req).await,
1183    }
1184}
1185
1186#[derive(Clone, Default)]
1187struct RequestHeaders {
1188    entries: Vec<(HeaderName, HeaderValue)>,
1189}
1190
1191impl RequestHeaders {
1192    fn parse(headers: &[String]) -> Result<Self> {
1193        let mut entries = Vec::new();
1194        for header in headers {
1195            if let Some(path) = header.strip_prefix('@') {
1196                let contents = std::fs::read_to_string(path)
1197                    .with_context(|| format!("failed to read header file `{path}`"))?;
1198                for line in contents.lines().filter(|line| !line.trim().is_empty()) {
1199                    entries.push(parse_header(line)?);
1200                }
1201            } else {
1202                entries.push(parse_header(header)?);
1203            }
1204        }
1205        Ok(Self { entries })
1206    }
1207
1208    fn apply(&self, headers: &mut HeaderMap) {
1209        // Remove all request-provided values before appending CLI-provided
1210        // values so repeated CLI headers with the same name are preserved.
1211        for name in self.entries.iter().map(|(name, _)| name) {
1212            headers.remove(name);
1213        }
1214        for (name, value) in &self.entries {
1215            headers.append(name, value.clone());
1216        }
1217    }
1218}
1219
1220fn parse_header(header: &str) -> Result<(HeaderName, HeaderValue)> {
1221    let (name, value) = header
1222        .split_once(':')
1223        .with_context(|| format!("header `{header}` is missing `:`"))?;
1224    let name = HeaderName::from_bytes(name.trim().as_bytes())
1225        .with_context(|| format!("invalid header name in header `{header}`"))?;
1226    let value = HeaderValue::from_str(value.trim_start())
1227        .with_context(|| format!("invalid header value in header `{header}`"))?;
1228    Ok((name, value))
1229}
1230
1231#[derive(Clone)]
1232enum Output {
1233    Stdout,
1234    Stderr,
1235}
1236
1237impl Output {
1238    fn write_all(&self, buf: &[u8]) -> io::Result<()> {
1239        use std::io::Write;
1240
1241        match self {
1242            Output::Stdout => std::io::stdout().write_all(buf),
1243            Output::Stderr => std::io::stderr().write_all(buf),
1244        }
1245    }
1246}
1247
1248#[derive(Clone)]
1249struct LogStream {
1250    output: Output,
1251    state: Arc<LogStreamState>,
1252}
1253
1254struct LogStreamState {
1255    prefix: String,
1256    needs_prefix_on_next_write: AtomicBool,
1257}
1258
1259impl LogStream {
1260    fn new(prefix: String, output: Output) -> LogStream {
1261        LogStream {
1262            output,
1263            state: Arc::new(LogStreamState {
1264                prefix,
1265                needs_prefix_on_next_write: AtomicBool::new(true),
1266            }),
1267        }
1268    }
1269
1270    fn write_all(&mut self, mut bytes: &[u8]) -> io::Result<()> {
1271        while !bytes.is_empty() {
1272            if self
1273                .state
1274                .needs_prefix_on_next_write
1275                .load(Ordering::Relaxed)
1276            {
1277                self.output.write_all(self.state.prefix.as_bytes())?;
1278                self.state
1279                    .needs_prefix_on_next_write
1280                    .store(false, Ordering::Relaxed);
1281            }
1282            match bytes.iter().position(|b| *b == b'\n') {
1283                Some(i) => {
1284                    let (a, b) = bytes.split_at(i + 1);
1285                    bytes = b;
1286                    self.output.write_all(a)?;
1287                    self.state
1288                        .needs_prefix_on_next_write
1289                        .store(true, Ordering::Relaxed);
1290                }
1291                None => {
1292                    self.output.write_all(bytes)?;
1293                    break;
1294                }
1295            }
1296        }
1297
1298        Ok(())
1299    }
1300}
1301
1302impl wasmtime_wasi::cli::StdoutStream for LogStream {
1303    fn p2_stream(&self) -> Box<dyn wasmtime_wasi::p2::OutputStream> {
1304        Box::new(self.clone())
1305    }
1306    fn async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync> {
1307        Box::new(self.clone())
1308    }
1309}
1310
1311impl wasmtime_wasi::cli::IsTerminal for LogStream {
1312    fn is_terminal(&self) -> bool {
1313        match &self.output {
1314            Output::Stdout => std::io::stdout().is_terminal(),
1315            Output::Stderr => std::io::stderr().is_terminal(),
1316        }
1317    }
1318}
1319
1320impl wasmtime_wasi::p2::OutputStream for LogStream {
1321    fn write(&mut self, bytes: bytes::Bytes) -> StreamResult<()> {
1322        self.write_all(&bytes)
1323            .map_err(|e| StreamError::LastOperationFailed(e.into()))?;
1324        Ok(())
1325    }
1326
1327    fn flush(&mut self) -> StreamResult<()> {
1328        Ok(())
1329    }
1330
1331    fn check_write(&mut self) -> StreamResult<usize> {
1332        Ok(1024 * 1024)
1333    }
1334}
1335
1336#[async_trait::async_trait]
1337impl wasmtime_wasi::p2::Pollable for LogStream {
1338    async fn ready(&mut self) {}
1339}
1340
1341impl AsyncWrite for LogStream {
1342    fn poll_write(
1343        mut self: Pin<&mut Self>,
1344        _cx: &mut Context<'_>,
1345        buf: &[u8],
1346    ) -> Poll<io::Result<usize>> {
1347        Poll::Ready(self.write_all(buf).map(|_| buf.len()))
1348    }
1349    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1350        Poll::Ready(Ok(()))
1351    }
1352    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1353        Poll::Ready(Ok(()))
1354    }
1355}
1356
1357/// The pooling allocator is tailor made for the `wasmtime serve` use case, so
1358/// try to use it when we can. The main cost of the pooling allocator, however,
1359/// is the virtual memory required to run it. Not all systems support the same
1360/// amount of virtual memory, for example some aarch64 and riscv64 configuration
1361/// only support 39 bits of virtual address space.
1362///
1363/// The pooling allocator, by default, will request 1000 linear memories each
1364/// sized at 6G per linear memory. This is 6T of virtual memory which ends up
1365/// being about 42 bits of the address space. This exceeds the 39 bit limit of
1366/// some systems, so there the pooling allocator will fail by default.
1367///
1368/// This function attempts to dynamically determine the hint for the pooling
1369/// allocator. This returns `Some(true)` if the pooling allocator should be used
1370/// by default, or `None` or an error otherwise.
1371///
1372/// The method for testing this is to allocate a 0-sized 64-bit linear memory
1373/// with a maximum size that's N bits large where we force all memories to be
1374/// static. This should attempt to acquire N bits of the virtual address space.
1375/// If successful that should mean that the pooling allocator is OK to use, but
1376/// if it fails then the pooling allocator is not used and the normal mmap-based
1377/// implementation is used instead.
1378fn use_pooling_allocator_by_default() -> Result<Option<bool>> {
1379    use wasmtime::{Config, Memory, MemoryType};
1380    const BITS_TO_TEST: u32 = 42;
1381    let mut config = Config::new();
1382    config.wasm_memory64(true);
1383    config.memory_reservation(1 << BITS_TO_TEST);
1384    let engine = Engine::new(&config)?;
1385    let mut store = Store::new(&engine, ());
1386    // NB: the maximum size is in wasm pages to take out the 16-bits of wasm
1387    // page size here from the maximum size.
1388    let ty = MemoryType::new64(0, Some(1 << (BITS_TO_TEST - 16)));
1389    if Memory::new(&mut store, ty).is_ok() {
1390        Ok(Some(true))
1391    } else {
1392        Ok(None)
1393    }
1394}