xref: /wasmtime-44.0.1/src/commands/serve.rs (revision bda02c19)
1 use crate::common::{Profile, RunCommon, RunTarget};
2 use bytes::Bytes;
3 use clap::Parser;
4 use futures::future::FutureExt;
5 use http::{Response, StatusCode};
6 use http_body_util::BodyExt as _;
7 use http_body_util::combinators::UnsyncBoxBody;
8 use std::convert::Infallible;
9 use std::net::SocketAddr;
10 use std::pin::Pin;
11 use std::task::{Context, Poll};
12 use std::{
13     path::PathBuf,
14     sync::{
15         Arc, Mutex,
16         atomic::{AtomicBool, Ordering},
17     },
18     time::Duration,
19 };
20 use tokio::io::{self, AsyncWrite};
21 use tokio::sync::Notify;
22 use wasmtime::component::{Component, Linker, ResourceTable};
23 use wasmtime::{
24     Engine, Result, Store, StoreContextMut, StoreLimits, UpdateDeadline, bail, error::Context as _,
25 };
26 use wasmtime_cli_flags::opt::WasmtimeOptionValue;
27 use wasmtime_wasi::p2::{StreamError, StreamResult};
28 use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
29 #[cfg(feature = "component-model-async")]
30 use wasmtime_wasi_http::handler::p2::bindings as p2;
31 use wasmtime_wasi_http::handler::{HandlerState, Proxy, ProxyHandler, ProxyPre, StoreBundle};
32 use wasmtime_wasi_http::io::TokioIo;
33 use wasmtime_wasi_http::{
34     DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
35     WasiHttpView,
36 };
37 
38 #[cfg(feature = "wasi-config")]
39 use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
40 #[cfg(feature = "wasi-keyvalue")]
41 use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
42 #[cfg(feature = "wasi-nn")]
43 use wasmtime_wasi_nn::wit::WasiNnCtx;
44 
45 const DEFAULT_WASIP3_MAX_INSTANCE_REUSE_COUNT: usize = 128;
46 const DEFAULT_WASIP2_MAX_INSTANCE_REUSE_COUNT: usize = 1;
47 const DEFAULT_WASIP3_MAX_INSTANCE_CONCURRENT_REUSE_COUNT: usize = 16;
48 
49 struct Host {
50     table: wasmtime::component::ResourceTable,
51     ctx: WasiCtx,
52     http: WasiHttpCtx,
53     http_outgoing_body_buffer_chunks: Option<usize>,
54     http_outgoing_body_chunk_size: Option<usize>,
55 
56     #[cfg(feature = "component-model-async")]
57     p3_http: crate::common::DefaultP3Ctx,
58 
59     limits: StoreLimits,
60 
61     #[cfg(feature = "wasi-nn")]
62     nn: Option<WasiNnCtx>,
63 
64     #[cfg(feature = "wasi-config")]
65     wasi_config: Option<WasiConfigVariables>,
66 
67     #[cfg(feature = "wasi-keyvalue")]
68     wasi_keyvalue: Option<WasiKeyValueCtx>,
69 
70     #[cfg(feature = "profiling")]
71     guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
72 }
73 
74 impl 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 
83 impl WasiHttpView for Host {
84     fn ctx(&mut self) -> &mut WasiHttpCtx {
85         &mut self.http
86     }
87     fn table(&mut self) -> &mut ResourceTable {
88         &mut self.table
89     }
90 
91     fn outgoing_body_buffer_chunks(&mut self) -> usize {
92         self.http_outgoing_body_buffer_chunks
93             .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
94     }
95 
96     fn outgoing_body_chunk_size(&mut self) -> usize {
97         self.http_outgoing_body_chunk_size
98             .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
99     }
100 }
101 
102 #[cfg(feature = "component-model-async")]
103 impl wasmtime_wasi_http::p3::WasiHttpView for Host {
104     fn http(&mut self) -> wasmtime_wasi_http::p3::WasiHttpCtxView<'_> {
105         wasmtime_wasi_http::p3::WasiHttpCtxView {
106             table: &mut self.table,
107             ctx: &mut self.p3_http,
108         }
109     }
110 }
111 
112 const DEFAULT_ADDR: std::net::SocketAddr = std::net::SocketAddr::new(
113     std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
114     8080,
115 );
116 
117 fn parse_duration(s: &str) -> Result<Duration, String> {
118     Duration::parse(Some(s)).map_err(|e| e.to_string())
119 }
120 
121 /// Runs a WebAssembly module
122 #[derive(Parser)]
123 pub struct ServeCommand {
124     #[command(flatten)]
125     run: RunCommon,
126 
127     /// Socket address for the web server to bind to.
128     #[arg(long , value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR)]
129     addr: SocketAddr,
130 
131     /// Socket address where, when connected to, will initiate a graceful
132     /// shutdown.
133     ///
134     /// Note that graceful shutdown is also supported on ctrl-c.
135     #[arg(long, value_name = "SOCKADDR")]
136     shutdown_addr: Option<SocketAddr>,
137 
138     /// Disable log prefixes of wasi-http handlers.
139     /// if unspecified, logs will be prefixed with 'stdout|stderr [{req_id}] :: '
140     #[arg(long)]
141     no_logging_prefix: bool,
142 
143     /// The WebAssembly component to run.
144     #[arg(value_name = "WASM", required = true)]
145     component: PathBuf,
146 
147     /// Maximum number of requests to send to a single component instance before
148     /// dropping it.
149     ///
150     /// This defaults to 1 for WASIp2 components and 128 for WASIp3 components.
151     #[arg(long)]
152     max_instance_reuse_count: Option<usize>,
153 
154     /// Maximum number of concurrent requests to send to a single component
155     /// instance.
156     ///
157     /// This defaults to 1 for WASIp2 components and 16 for WASIp3 components.
158     /// Note that setting it to more than 1 will have no effect for WASIp2
159     /// components since they cannot be called concurrently.
160     #[arg(long)]
161     max_instance_concurrent_reuse_count: Option<usize>,
162 
163     /// Time to hold an idle component instance for possible reuse before
164     /// dropping it.
165     ///
166     /// A number with no suffix or with an `s` suffix is interpreted as seconds;
167     /// other accepted suffixes include `ms` (milliseconds), `us` or `μs`
168     /// (microseconds), and `ns` (nanoseconds).
169     #[arg(long, default_value = "1s", value_parser = parse_duration)]
170     idle_instance_timeout: Duration,
171 }
172 
173 impl ServeCommand {
174     /// Start a server to run the given wasi-http proxy component
175     pub fn execute(mut self) -> Result<()> {
176         self.run.common.init_logging()?;
177 
178         // We force cli errors before starting to listen for connections so then
179         // we don't accidentally delay them to the first request.
180 
181         if self.run.common.wasi.nn == Some(true) {
182             #[cfg(not(feature = "wasi-nn"))]
183             {
184                 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
185             }
186         }
187 
188         if self.run.common.wasi.threads == Some(true) {
189             bail!("wasi-threads does not support components yet")
190         }
191 
192         // The serve command requires both wasi-http and the component model, so
193         // we enable those by default here.
194         if self.run.common.wasi.http.replace(true) == Some(false) {
195             bail!("wasi-http is required for the serve command, and must not be disabled");
196         }
197         if self.run.common.wasm.component_model.replace(true) == Some(false) {
198             bail!("components are required for the serve command, and must not be disabled");
199         }
200 
201         let runtime = tokio::runtime::Builder::new_multi_thread()
202             .enable_time()
203             .enable_io()
204             .build()?;
205 
206         runtime.block_on(self.serve())?;
207 
208         Ok(())
209     }
210 
211     fn new_store(&self, engine: &Engine, req_id: Option<u64>) -> Result<Store<Host>> {
212         let mut builder = WasiCtxBuilder::new();
213         self.run.configure_wasip2(&mut builder)?;
214 
215         if let Some(req_id) = req_id {
216             builder.env("REQUEST_ID", req_id.to_string());
217         }
218 
219         let stdout_prefix: String;
220         let stderr_prefix: String;
221         match req_id {
222             Some(req_id) if !self.no_logging_prefix => {
223                 stdout_prefix = format!("stdout [{req_id}] :: ");
224                 stderr_prefix = format!("stderr [{req_id}] :: ");
225             }
226             _ => {
227                 stdout_prefix = "".to_string();
228                 stderr_prefix = "".to_string();
229             }
230         }
231         builder.stdout(LogStream::new(stdout_prefix, Output::Stdout));
232         builder.stderr(LogStream::new(stderr_prefix, Output::Stderr));
233 
234         let mut host = Host {
235             table: wasmtime::component::ResourceTable::new(),
236             ctx: builder.build(),
237             http: WasiHttpCtx::new(),
238             http_outgoing_body_buffer_chunks: self.run.common.wasi.http_outgoing_body_buffer_chunks,
239             http_outgoing_body_chunk_size: self.run.common.wasi.http_outgoing_body_chunk_size,
240 
241             limits: StoreLimits::default(),
242 
243             #[cfg(feature = "wasi-nn")]
244             nn: None,
245             #[cfg(feature = "wasi-config")]
246             wasi_config: None,
247             #[cfg(feature = "wasi-keyvalue")]
248             wasi_keyvalue: None,
249             #[cfg(feature = "profiling")]
250             guest_profiler: None,
251             #[cfg(feature = "component-model-async")]
252             p3_http: crate::common::DefaultP3Ctx,
253         };
254 
255         if self.run.common.wasi.nn == Some(true) {
256             #[cfg(feature = "wasi-nn")]
257             {
258                 let graphs = self
259                     .run
260                     .common
261                     .wasi
262                     .nn_graph
263                     .iter()
264                     .map(|g| (g.format.clone(), g.dir.clone()))
265                     .collect::<Vec<_>>();
266                 let (backends, registry) = wasmtime_wasi_nn::preload(&graphs)?;
267                 host.nn.replace(WasiNnCtx::new(backends, registry));
268             }
269         }
270 
271         if self.run.common.wasi.config == Some(true) {
272             #[cfg(feature = "wasi-config")]
273             {
274                 let vars = WasiConfigVariables::from_iter(
275                     self.run
276                         .common
277                         .wasi
278                         .config_var
279                         .iter()
280                         .map(|v| (v.key.clone(), v.value.clone())),
281                 );
282                 host.wasi_config.replace(vars);
283             }
284         }
285 
286         if self.run.common.wasi.keyvalue == Some(true) {
287             #[cfg(feature = "wasi-keyvalue")]
288             {
289                 let ctx = WasiKeyValueCtxBuilder::new()
290                     .in_memory_data(
291                         self.run
292                             .common
293                             .wasi
294                             .keyvalue_in_memory_data
295                             .iter()
296                             .map(|v| (v.key.clone(), v.value.clone())),
297                     )
298                     .build();
299                 host.wasi_keyvalue.replace(ctx);
300             }
301         }
302 
303         let mut store = Store::new(engine, host);
304 
305         store.data_mut().limits = self.run.store_limits();
306         store.limiter(|t| &mut t.limits);
307 
308         // If fuel has been configured, we want to add the configured
309         // fuel amount to this store.
310         if let Some(fuel) = self.run.common.wasm.fuel {
311             store.set_fuel(fuel)?;
312         }
313 
314         Ok(store)
315     }
316 
317     fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
318         self.run.validate_p3_option()?;
319         let cli = self.run.validate_cli_enabled()?;
320 
321         // Repurpose the `-Scli` flag of `wasmtime run` for `wasmtime serve`
322         // to serve as a signal to enable all WASI interfaces instead of just
323         // those in the `proxy` world. If `-Scli` is present then add all
324         // `command` APIs and then additionally add in the required HTTP APIs.
325         //
326         // If `-Scli` isn't passed then use the `add_to_linker_async`
327         // bindings which adds just those interfaces that the proxy interface
328         // uses.
329         if cli == Some(true) {
330             self.run.add_wasmtime_wasi_to_linker(linker)?;
331             wasmtime_wasi_http::add_only_http_to_linker_async(linker)?;
332             #[cfg(feature = "component-model-async")]
333             if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
334                 wasmtime_wasi_http::p3::add_to_linker(linker)?;
335             }
336         } else {
337             wasmtime_wasi_http::add_to_linker_async(linker)?;
338             #[cfg(feature = "component-model-async")]
339             if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
340                 wasmtime_wasi_http::p3::add_to_linker(linker)?;
341                 wasmtime_wasi::p3::clocks::add_to_linker(linker)?;
342                 wasmtime_wasi::p3::random::add_to_linker(linker)?;
343                 wasmtime_wasi::p3::cli::add_to_linker(linker)?;
344             }
345         }
346 
347         if self.run.common.wasi.nn == Some(true) {
348             #[cfg(not(feature = "wasi-nn"))]
349             {
350                 bail!("support for wasi-nn was disabled at compile time");
351             }
352             #[cfg(feature = "wasi-nn")]
353             {
354                 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
355                     let ctx = h.nn.as_mut().unwrap();
356                     wasmtime_wasi_nn::wit::WasiNnView::new(&mut h.table, ctx)
357                 })?;
358             }
359         }
360 
361         if self.run.common.wasi.config == Some(true) {
362             #[cfg(not(feature = "wasi-config"))]
363             {
364                 bail!("support for wasi-config was disabled at compile time");
365             }
366             #[cfg(feature = "wasi-config")]
367             {
368                 wasmtime_wasi_config::add_to_linker(linker, |h| {
369                     WasiConfig::from(h.wasi_config.as_ref().unwrap())
370                 })?;
371             }
372         }
373 
374         if self.run.common.wasi.keyvalue == Some(true) {
375             #[cfg(not(feature = "wasi-keyvalue"))]
376             {
377                 bail!("support for wasi-keyvalue was disabled at compile time");
378             }
379             #[cfg(feature = "wasi-keyvalue")]
380             {
381                 wasmtime_wasi_keyvalue::add_to_linker(linker, |h: &mut Host| {
382                     WasiKeyValue::new(h.wasi_keyvalue.as_ref().unwrap(), &mut h.table)
383                 })?;
384             }
385         }
386 
387         if self.run.common.wasi.threads == Some(true) {
388             bail!("support for wasi-threads is not available with components");
389         }
390 
391         if self.run.common.wasi.http == Some(false) {
392             bail!("support for wasi-http must be enabled for `serve` subcommand");
393         }
394 
395         Ok(())
396     }
397 
398     async fn serve(mut self) -> Result<()> {
399         use hyper::server::conn::http1;
400 
401         let mut config = self
402             .run
403             .common
404             .config(use_pooling_allocator_by_default().unwrap_or(None))?;
405         config.wasm_component_model(true);
406 
407         if self.run.common.wasm.timeout.is_some() {
408             config.epoch_interruption(true);
409         }
410 
411         match self.run.profile {
412             Some(Profile::Native(s)) => {
413                 config.profiler(s);
414             }
415             Some(Profile::Guest { .. }) => {
416                 config.epoch_interruption(true);
417             }
418             None => {}
419         }
420 
421         let engine = Engine::new(&config)?;
422         let mut linker = Linker::new(&engine);
423 
424         self.add_to_linker(&mut linker)?;
425 
426         let component = match self.run.load_module(&engine, &self.component)? {
427             RunTarget::Core(_) => bail!("The serve command currently requires a component"),
428             RunTarget::Component(c) => c,
429         };
430 
431         let instance = linker.instantiate_pre(&component)?;
432         #[cfg(feature = "component-model-async")]
433         let instance = match wasmtime_wasi_http::p3::bindings::ServicePre::new(instance.clone()) {
434             Ok(pre) => ProxyPre::P3(pre),
435             Err(_) => ProxyPre::P2(p2::ProxyPre::new(instance)?),
436         };
437         #[cfg(not(feature = "component-model-async"))]
438         let instance = ProxyPre::P2(p2::ProxyPre::new(instance)?);
439 
440         // Spawn background task(s) waiting for graceful shutdown signals. This
441         // always listens for ctrl-c but additionally can listen for a TCP
442         // connection to the specified address.
443         let shutdown = Arc::new(GracefulShutdown::default());
444         tokio::task::spawn({
445             let shutdown = shutdown.clone();
446             async move {
447                 tokio::signal::ctrl_c().await.unwrap();
448                 shutdown.requested.notify_one();
449             }
450         });
451         if let Some(addr) = self.shutdown_addr {
452             let listener = tokio::net::TcpListener::bind(addr).await?;
453             eprintln!(
454                 "Listening for shutdown on tcp://{}/",
455                 listener.local_addr()?
456             );
457             let shutdown = shutdown.clone();
458             tokio::task::spawn(async move {
459                 let _ = listener.accept().await;
460                 shutdown.requested.notify_one();
461             });
462         }
463 
464         let socket = match &self.addr {
465             SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
466             SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
467         };
468         // Conditionally enable `SO_REUSEADDR` depending on the current
469         // platform. On Unix we want this to be able to rebind an address in
470         // the `TIME_WAIT` state which can happen then a server is killed with
471         // active TCP connections and then restarted. On Windows though if
472         // `SO_REUSEADDR` is specified then it enables multiple applications to
473         // bind the port at the same time which is not something we want. Hence
474         // this is conditionally set based on the platform (and deviates from
475         // Tokio's default from always-on).
476         socket.set_reuseaddr(!cfg!(windows))?;
477         socket.bind(self.addr)?;
478         let listener = socket.listen(100)?;
479 
480         eprintln!("Serving HTTP on http://{}/", listener.local_addr()?);
481 
482         log::info!("Listening on {}", self.addr);
483 
484         let epoch_interval = if let Some(Profile::Guest { interval, .. }) = self.run.profile {
485             Some(interval)
486         } else if let Some(t) = self.run.common.wasm.timeout {
487             Some(EPOCH_INTERRUPT_PERIOD.min(t))
488         } else {
489             None
490         };
491         let _epoch_thread = epoch_interval.map(|t| EpochThread::spawn(t, engine.clone()));
492 
493         let max_instance_reuse_count = self.max_instance_reuse_count.unwrap_or_else(|| {
494             if let ProxyPre::P3(_) = &instance {
495                 DEFAULT_WASIP3_MAX_INSTANCE_REUSE_COUNT
496             } else {
497                 DEFAULT_WASIP2_MAX_INSTANCE_REUSE_COUNT
498             }
499         });
500 
501         let max_instance_concurrent_reuse_count = if let ProxyPre::P3(_) = &instance {
502             self.max_instance_concurrent_reuse_count
503                 .unwrap_or(DEFAULT_WASIP3_MAX_INSTANCE_CONCURRENT_REUSE_COUNT)
504         } else {
505             1
506         };
507 
508         let handler = ProxyHandler::new(
509             HostHandlerState {
510                 cmd: self,
511                 engine,
512                 component,
513                 max_instance_reuse_count,
514                 max_instance_concurrent_reuse_count,
515             },
516             instance,
517         );
518 
519         loop {
520             // Wait for a socket, but also "race" against shutdown to break out
521             // of this loop. Once the graceful shutdown signal is received then
522             // this loop exits immediately.
523             let (stream, _) = tokio::select! {
524                 _ = shutdown.requested.notified() => break,
525                 v = listener.accept() => v?,
526             };
527 
528             // The Nagle algorithm can impose a significant latency penalty
529             // (e.g. 40ms on Linux) on guests which write small, intermittent
530             // response body chunks (e.g. SSE streams).  Here we disable that
531             // algorithm and rely on the guest to buffer if appropriate to avoid
532             // TCP fragmentation.
533             stream.set_nodelay(true)?;
534 
535             let stream = TokioIo::new(stream);
536             let h = handler.clone();
537             let shutdown_guard = shutdown.clone().increment();
538             tokio::task::spawn(async move {
539                 if let Err(e) = http1::Builder::new()
540                     .keep_alive(true)
541                     .serve_connection(
542                         stream,
543                         hyper::service::service_fn(move |req| {
544                             let h = h.clone();
545                             async move {
546                                 use http_body_util::{BodyExt, Full};
547                                 match handle_request(h, req).await {
548                                     Ok(r) => Ok::<_, Infallible>(r),
549                                     Err(e) => {
550                                         eprintln!("error: {e:?}");
551                                         let error_html = "\
552 <!doctype html>
553 <html>
554 <head>
555     <title>500 Internal Server Error</title>
556 </head>
557 <body>
558     <center>
559         <h1>500 Internal Server Error</h1>
560         <hr>
561         wasmtime
562     </center>
563 </body>
564 </html>";
565                                         Ok(Response::builder()
566                                             .status(StatusCode::INTERNAL_SERVER_ERROR)
567                                             .header("Content-Type", "text/html; charset=UTF-8")
568                                             .body(
569                                                 Full::new(bytes::Bytes::from(error_html))
570                                                     .map_err(|_| unreachable!())
571                                                     .boxed_unsync(),
572                                             )
573                                             .unwrap())
574                                     }
575                                 }
576                             }
577                         }),
578                     )
579                     .await
580                 {
581                     eprintln!("error: {e:?}");
582                 }
583                 drop(shutdown_guard);
584             });
585         }
586 
587         // Upon exiting the loop we'll no longer process any more incoming
588         // connections but there may still be outstanding connections
589         // processing in child tasks. If there are wait for those to complete
590         // before shutting down completely. Also enable short-circuiting this
591         // wait with a second ctrl-c signal.
592         if shutdown.close() {
593             return Ok(());
594         }
595         eprintln!("Waiting for child tasks to exit, ctrl-c again to quit sooner...");
596         tokio::select! {
597             _ = tokio::signal::ctrl_c() => {}
598             _ = shutdown.complete.notified() => {}
599         }
600 
601         Ok(())
602     }
603 }
604 
605 struct HostHandlerState {
606     cmd: ServeCommand,
607     engine: Engine,
608     component: Component,
609     max_instance_reuse_count: usize,
610     max_instance_concurrent_reuse_count: usize,
611 }
612 
613 impl HandlerState for HostHandlerState {
614     type StoreData = Host;
615 
616     fn new_store(&self, req_id: Option<u64>) -> Result<StoreBundle<Host>> {
617         let mut store = self.cmd.new_store(&self.engine, req_id)?;
618         let write_profile = setup_epoch_handler(&self.cmd, &mut store, self.component.clone())?;
619 
620         Ok(StoreBundle {
621             store,
622             write_profile,
623         })
624     }
625 
626     fn request_timeout(&self) -> Duration {
627         self.cmd.run.common.wasm.timeout.unwrap_or(Duration::MAX)
628     }
629 
630     fn idle_instance_timeout(&self) -> Duration {
631         self.cmd.idle_instance_timeout
632     }
633 
634     fn max_instance_reuse_count(&self) -> usize {
635         self.max_instance_reuse_count
636     }
637 
638     fn max_instance_concurrent_reuse_count(&self) -> usize {
639         self.max_instance_concurrent_reuse_count
640     }
641 
642     fn handle_worker_error(&self, error: wasmtime::Error) {
643         eprintln!("worker error: {error}");
644     }
645 }
646 
647 /// Helper structure to manage graceful shutdown int he accept loop above.
648 #[derive(Default)]
649 struct GracefulShutdown {
650     /// Async notification that shutdown has been requested.
651     requested: Notify,
652     /// Async notification that shutdown has completed, signaled when
653     /// `notify_when_done` is `true` and `active_tasks` reaches 0.
654     complete: Notify,
655     /// Internal state related to what's in progress when shutdown is requested.
656     state: Mutex<GracefulShutdownState>,
657 }
658 
659 #[derive(Default)]
660 struct GracefulShutdownState {
661     active_tasks: u32,
662     notify_when_done: bool,
663 }
664 
665 impl GracefulShutdown {
666     /// Increments the number of active tasks and returns a guard indicating
667     fn increment(self: Arc<Self>) -> impl Drop {
668         struct Guard(Arc<GracefulShutdown>);
669 
670         let mut state = self.state.lock().unwrap();
671         assert!(!state.notify_when_done);
672         state.active_tasks += 1;
673         drop(state);
674 
675         return Guard(self);
676 
677         impl Drop for Guard {
678             fn drop(&mut self) {
679                 let mut state = self.0.state.lock().unwrap();
680                 state.active_tasks -= 1;
681                 if state.notify_when_done && state.active_tasks == 0 {
682                     self.0.complete.notify_one();
683                 }
684             }
685         }
686     }
687 
688     /// Flags this state as done spawning tasks and returns whether there are no
689     /// more child tasks remaining.
690     fn close(&self) -> bool {
691         let mut state = self.state.lock().unwrap();
692         state.notify_when_done = true;
693         state.active_tasks == 0
694     }
695 }
696 
697 /// When executing with a timeout enabled, this is how frequently epoch
698 /// interrupts will be executed to check for timeouts. If guest profiling
699 /// is enabled, the guest epoch period will be used.
700 const EPOCH_INTERRUPT_PERIOD: Duration = Duration::from_millis(50);
701 
702 struct EpochThread {
703     shutdown: Arc<AtomicBool>,
704     handle: Option<std::thread::JoinHandle<()>>,
705 }
706 
707 impl EpochThread {
708     fn spawn(interval: std::time::Duration, engine: Engine) -> Self {
709         let shutdown = Arc::new(AtomicBool::new(false));
710         let handle = {
711             let shutdown = Arc::clone(&shutdown);
712             let handle = std::thread::spawn(move || {
713                 while !shutdown.load(Ordering::Relaxed) {
714                     std::thread::sleep(interval);
715                     engine.increment_epoch();
716                 }
717             });
718             Some(handle)
719         };
720 
721         EpochThread { shutdown, handle }
722     }
723 }
724 
725 impl Drop for EpochThread {
726     fn drop(&mut self) {
727         if let Some(handle) = self.handle.take() {
728             self.shutdown.store(true, Ordering::Relaxed);
729             handle.join().unwrap();
730         }
731     }
732 }
733 
734 type WriteProfile = Box<dyn FnOnce(StoreContextMut<Host>) + Send>;
735 
736 fn setup_epoch_handler(
737     cmd: &ServeCommand,
738     store: &mut Store<Host>,
739     component: Component,
740 ) -> Result<WriteProfile> {
741     // Profiling Enabled
742     if let Some(Profile::Guest { interval, path }) = &cmd.run.profile {
743         #[cfg(feature = "profiling")]
744         return setup_guest_profiler(store, path.clone(), *interval, component.clone());
745         #[cfg(not(feature = "profiling"))]
746         {
747             let _ = (path, interval);
748             bail!("support for profiling disabled at compile time!");
749         }
750     }
751 
752     // Profiling disabled but there's a global request timeout
753     if cmd.run.common.wasm.timeout.is_some() {
754         store.epoch_deadline_async_yield_and_update(1);
755     }
756 
757     Ok(Box::new(|_store| {}))
758 }
759 
760 #[cfg(feature = "profiling")]
761 fn setup_guest_profiler(
762     store: &mut Store<Host>,
763     path: String,
764     interval: Duration,
765     component: Component,
766 ) -> Result<WriteProfile> {
767     use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut};
768 
769     let module_name = "<main>";
770 
771     store.data_mut().guest_profiler = Some(Arc::new(GuestProfiler::new_component(
772         store.engine(),
773         module_name,
774         interval,
775         component,
776         std::iter::empty(),
777     )?));
778 
779     fn sample(
780         mut store: StoreContextMut<Host>,
781         f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
782     ) {
783         let mut profiler = store.data_mut().guest_profiler.take().unwrap();
784         f(
785             Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
786             store.as_context(),
787         );
788         store.data_mut().guest_profiler = Some(profiler);
789     }
790 
791     // Hostcall entry/exit, etc.
792     store.call_hook(|store, kind| {
793         sample(store, |profiler, store| profiler.call_hook(store, kind));
794         Ok(())
795     });
796 
797     store.epoch_deadline_callback(move |store| {
798         sample(store, |profiler, store| {
799             profiler.sample(store, std::time::Duration::ZERO)
800         });
801 
802         Ok(UpdateDeadline::Continue(1))
803     });
804 
805     store.set_epoch_deadline(1);
806 
807     let write_profile = Box::new(move |mut store: StoreContextMut<Host>| {
808         let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
809             .expect("profiling doesn't support threads yet");
810         if let Err(e) = std::fs::File::create(&path)
811             .map_err(wasmtime::Error::new)
812             .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
813         {
814             eprintln!("failed writing profile at {path}: {e:#}");
815         } else {
816             eprintln!();
817             eprintln!("Profile written to: {path}");
818             eprintln!("View this profile at https://profiler.firefox.com/.");
819         }
820     });
821 
822     Ok(write_profile)
823 }
824 
825 type Request = hyper::Request<hyper::body::Incoming>;
826 
827 async fn handle_request(
828     handler: ProxyHandler<HostHandlerState>,
829     req: Request,
830 ) -> Result<hyper::Response<UnsyncBoxBody<Bytes, wasmtime::Error>>> {
831     use tokio::sync::oneshot;
832 
833     let req_id = handler.next_req_id();
834 
835     log::info!(
836         "Request {req_id} handling {} to {}",
837         req.method(),
838         req.uri()
839     );
840 
841     // Here we must declare different channel types for p2 and p3 since p2's
842     // `WasiHttpView::new_response_outparam` expects a specific kind of sender
843     // that uses `p2::http::types::ErrorCode`, and we don't want to have to
844     // convert from the p3 `ErrorCode` to the p2 one, only to convert again to
845     // `wasmtime::Error`.
846 
847     type P2Response = Result<
848         hyper::Response<wasmtime_wasi_http::body::HyperOutgoingBody>,
849         p2::http::types::ErrorCode,
850     >;
851     type P3Response = hyper::Response<UnsyncBoxBody<Bytes, wasmtime::Error>>;
852 
853     enum Sender {
854         P2(oneshot::Sender<P2Response>),
855         P3(oneshot::Sender<P3Response>),
856     }
857 
858     enum Receiver {
859         P2(oneshot::Receiver<P2Response>),
860         P3(oneshot::Receiver<P3Response>),
861     }
862 
863     let (tx, rx) = match handler.instance_pre() {
864         ProxyPre::P2(_) => {
865             let (tx, rx) = oneshot::channel();
866             (Sender::P2(tx), Receiver::P2(rx))
867         }
868         ProxyPre::P3(_) => {
869             let (tx, rx) = oneshot::channel();
870             (Sender::P3(tx), Receiver::P3(rx))
871         }
872     };
873 
874     handler.spawn(
875         if handler.state().max_instance_reuse_count() == 1 {
876             Some(req_id)
877         } else {
878             None
879         },
880         Box::new(move |store, proxy| {
881             Box::pin(
882                 async move {
883                     match proxy {
884                         Proxy::P2(proxy) => {
885                             let Sender::P2(tx) = tx else { unreachable!() };
886                             let (req, out) = store.with(move |mut store| {
887                                 let req = store
888                                     .data_mut()
889                                     .new_incoming_request(p2::http::types::Scheme::Http, req)?;
890                                 let out = store.data_mut().new_response_outparam(tx)?;
891                                 wasmtime::error::Ok((req, out))
892                             })?;
893 
894                             proxy
895                                 .wasi_http_incoming_handler()
896                                 .call_handle(store, req, out)
897                                 .await
898                         }
899                         Proxy::P3(proxy) => {
900                             use wasmtime_wasi_http::p3::bindings::http::types::{
901                                 ErrorCode, Request,
902                             };
903 
904                             let Sender::P3(tx) = tx else { unreachable!() };
905                             let (req, body) = req.into_parts();
906                             let body = body.map_err(ErrorCode::from_hyper_request_error);
907                             let req = http::Request::from_parts(req, body);
908                             let (request, request_io_result) = Request::from_http(req);
909                             let (res, task) = proxy.handle(store, request).await??;
910                             let res = store
911                                 .with(|mut store| res.into_http(&mut store, request_io_result))?;
912                             _ = tx.send(res.map(|body| body.map_err(|e| e.into()).boxed_unsync()));
913 
914                             // Wait for the task to finish.
915                             task.block(store).await;
916                             Ok(())
917                         }
918                     }
919                 }
920                 .map(move |result| {
921                     if let Err(error) = result {
922                         eprintln!("[{req_id}] :: {error:?}");
923                     }
924                 }),
925             )
926         }),
927     );
928 
929     Ok(match rx {
930         Receiver::P2(rx) => rx
931             .await
932             .context("guest never invoked `response-outparam::set` method")?
933             .map_err(|e| wasmtime::Error::from(e))?
934             .map(|body| body.map_err(|e| e.into()).boxed_unsync()),
935         Receiver::P3(rx) => rx.await?,
936     })
937 }
938 
939 #[derive(Clone)]
940 enum Output {
941     Stdout,
942     Stderr,
943 }
944 
945 impl Output {
946     fn write_all(&self, buf: &[u8]) -> io::Result<()> {
947         use std::io::Write;
948 
949         match self {
950             Output::Stdout => std::io::stdout().write_all(buf),
951             Output::Stderr => std::io::stderr().write_all(buf),
952         }
953     }
954 }
955 
956 #[derive(Clone)]
957 struct LogStream {
958     output: Output,
959     state: Arc<LogStreamState>,
960 }
961 
962 struct LogStreamState {
963     prefix: String,
964     needs_prefix_on_next_write: AtomicBool,
965 }
966 
967 impl LogStream {
968     fn new(prefix: String, output: Output) -> LogStream {
969         LogStream {
970             output,
971             state: Arc::new(LogStreamState {
972                 prefix,
973                 needs_prefix_on_next_write: AtomicBool::new(true),
974             }),
975         }
976     }
977 
978     fn write_all(&mut self, mut bytes: &[u8]) -> io::Result<()> {
979         while !bytes.is_empty() {
980             if self
981                 .state
982                 .needs_prefix_on_next_write
983                 .load(Ordering::Relaxed)
984             {
985                 self.output.write_all(self.state.prefix.as_bytes())?;
986                 self.state
987                     .needs_prefix_on_next_write
988                     .store(false, Ordering::Relaxed);
989             }
990             match bytes.iter().position(|b| *b == b'\n') {
991                 Some(i) => {
992                     let (a, b) = bytes.split_at(i + 1);
993                     bytes = b;
994                     self.output.write_all(a)?;
995                     self.state
996                         .needs_prefix_on_next_write
997                         .store(true, Ordering::Relaxed);
998                 }
999                 None => {
1000                     self.output.write_all(bytes)?;
1001                     break;
1002                 }
1003             }
1004         }
1005 
1006         Ok(())
1007     }
1008 }
1009 
1010 impl wasmtime_wasi::cli::StdoutStream for LogStream {
1011     fn p2_stream(&self) -> Box<dyn wasmtime_wasi::p2::OutputStream> {
1012         Box::new(self.clone())
1013     }
1014     fn async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync> {
1015         Box::new(self.clone())
1016     }
1017 }
1018 
1019 impl wasmtime_wasi::cli::IsTerminal for LogStream {
1020     fn is_terminal(&self) -> bool {
1021         match &self.output {
1022             Output::Stdout => std::io::stdout().is_terminal(),
1023             Output::Stderr => std::io::stderr().is_terminal(),
1024         }
1025     }
1026 }
1027 
1028 impl wasmtime_wasi::p2::OutputStream for LogStream {
1029     fn write(&mut self, bytes: bytes::Bytes) -> StreamResult<()> {
1030         self.write_all(&bytes)
1031             .map_err(|e| StreamError::LastOperationFailed(e.into()))?;
1032         Ok(())
1033     }
1034 
1035     fn flush(&mut self) -> StreamResult<()> {
1036         Ok(())
1037     }
1038 
1039     fn check_write(&mut self) -> StreamResult<usize> {
1040         Ok(1024 * 1024)
1041     }
1042 }
1043 
1044 #[async_trait::async_trait]
1045 impl wasmtime_wasi::p2::Pollable for LogStream {
1046     async fn ready(&mut self) {}
1047 }
1048 
1049 impl AsyncWrite for LogStream {
1050     fn poll_write(
1051         mut self: Pin<&mut Self>,
1052         _cx: &mut Context<'_>,
1053         buf: &[u8],
1054     ) -> Poll<io::Result<usize>> {
1055         Poll::Ready(self.write_all(buf).map(|_| buf.len()))
1056     }
1057     fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1058         Poll::Ready(Ok(()))
1059     }
1060     fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1061         Poll::Ready(Ok(()))
1062     }
1063 }
1064 
1065 /// The pooling allocator is tailor made for the `wasmtime serve` use case, so
1066 /// try to use it when we can. The main cost of the pooling allocator, however,
1067 /// is the virtual memory required to run it. Not all systems support the same
1068 /// amount of virtual memory, for example some aarch64 and riscv64 configuration
1069 /// only support 39 bits of virtual address space.
1070 ///
1071 /// The pooling allocator, by default, will request 1000 linear memories each
1072 /// sized at 6G per linear memory. This is 6T of virtual memory which ends up
1073 /// being about 42 bits of the address space. This exceeds the 39 bit limit of
1074 /// some systems, so there the pooling allocator will fail by default.
1075 ///
1076 /// This function attempts to dynamically determine the hint for the pooling
1077 /// allocator. This returns `Some(true)` if the pooling allocator should be used
1078 /// by default, or `None` or an error otherwise.
1079 ///
1080 /// The method for testing this is to allocate a 0-sized 64-bit linear memory
1081 /// with a maximum size that's N bits large where we force all memories to be
1082 /// static. This should attempt to acquire N bits of the virtual address space.
1083 /// If successful that should mean that the pooling allocator is OK to use, but
1084 /// if it fails then the pooling allocator is not used and the normal mmap-based
1085 /// implementation is used instead.
1086 fn use_pooling_allocator_by_default() -> Result<Option<bool>> {
1087     use wasmtime::{Config, Memory, MemoryType};
1088     const BITS_TO_TEST: u32 = 42;
1089     let mut config = Config::new();
1090     config.wasm_memory64(true);
1091     config.memory_reservation(1 << BITS_TO_TEST);
1092     let engine = Engine::new(&config)?;
1093     let mut store = Store::new(&engine, ());
1094     // NB: the maximum size is in wasm pages to take out the 16-bits of wasm
1095     // page size here from the maximum size.
1096     let ty = MemoryType::new64(0, Some(1 << (BITS_TO_TEST - 16)));
1097     if Memory::new(&mut store, ty).is_ok() {
1098         Ok(Some(true))
1099     } else {
1100         Ok(None)
1101     }
1102 }
1103