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#[derive(Parser)]
104pub struct ServeCommand {
105 #[command(flatten)]
106 run: RunCommon,
107
108 #[arg(long , value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR)]
110 addr: SocketAddr,
111
112 #[arg(long, value_name = "SOCKADDR")]
117 shutdown_addr: Option<SocketAddr>,
118
119 #[arg(long)]
122 no_logging_prefix: bool,
123
124 #[arg(value_name = "WASM", required = true)]
126 component: PathBuf,
127
128 #[arg(long)]
133 max_instance_reuse_count: Option<usize>,
134
135 #[arg(long)]
142 max_instance_concurrent_reuse_count: Option<usize>,
143
144 #[arg(long, default_value = "1s", value_parser = parse_duration)]
151 idle_instance_timeout: Duration,
152
153 #[arg(short = 'H', long = "header", value_name = "HEADER")]
159 headers: Vec<String>,
160
161 #[arg(long)]
164 max_concurrent_requests: Option<usize>,
165 #[arg(long)]
168 max_concurrent_connections: Option<usize>,
169}
170
171impl ServeCommand {
172 pub fn execute(mut self) -> Result<()> {
174 self.run.common.init_logging()?;
175
176 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 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 #[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 #[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 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 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 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 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 _shutdown_guard: Box::new(shutdown.clone().increment()),
692 });
693
694 loop {
695 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 stream.set_nodelay(true)?;
713
714 let shutdown_guard = shutdown.clone().increment();
718
719 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 handler.state().sem_requests.close();
737
738 drop(handler);
739
740 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 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#[derive(Default)]
900struct GracefulShutdown {
901 requested: Notify,
903 complete: Notify,
906 state: Mutex<GracefulShutdownState>,
908}
909
910#[derive(Default)]
911struct GracefulShutdownState {
912 active_tasks: u32,
913 notify_when_done: bool,
914}
915
916impl GracefulShutdown {
917 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 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
948const 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 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 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 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 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 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 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 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 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
1357fn 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 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}