1use {
4 crate::{
5 Error,
6 acl::Acls,
7 adapt,
8 body::Body,
9 body_tee::tee,
10 cache::Cache,
11 component as compute,
12 config::{
13 Backends, DeviceDetection, Dictionaries, ExperimentalModule, FakeValidFastlyKeys,
14 Geolocation, UnknownImportBehavior,
15 },
16 downstream::{DownstreamMetadata, DownstreamRequest, DownstreamResponse, prepare_request},
17 error::{ExecutionError, NonHttpResponse},
18 handoff::{HandoffConfig, HandoffRequestInfo, HandoffTlsConfig, perform_handoff},
19 http::framing::apply_response_framing,
20 linking::{ComponentCtx, WasmCtx, create_store, link_host_functions},
21 object_store::ObjectStores,
22 sandbox::Sandbox,
23 secret_store::SecretStores,
24 shielding_site::ShieldingSites,
25 upstream::TlsConfig,
26 },
27 futures::{
28 Future,
29 task::{Context, Poll},
30 },
31 http::StatusCode,
32 hyper::{Request, Response},
33 pin_project::pin_project,
34 std::{
35 collections::HashSet,
36 fmt, fs,
37 io::Write,
38 net::{Ipv4Addr, SocketAddr},
39 path::{Path, PathBuf},
40 pin::Pin,
41 sync::{
42 Arc, Mutex,
43 atomic::{AtomicBool, AtomicU64, Ordering},
44 },
45 thread::{self, JoinHandle},
46 time::{Duration, Instant, SystemTime},
47 },
48 tokio::sync::Mutex as AsyncMutex,
49 tokio::sync::mpsc,
50 tokio::sync::oneshot::{self, Sender},
51 tracing::{Instrument, Level, error, event, info, info_span},
52 wasmtime::{
53 Engine, GuestProfiler, InstancePre, Linker, Module, ProfilingStrategy,
54 component::{self, Component},
55 },
56 wasmtime_wasi::I32Exit,
57};
58
59pub use wasmtime::WasmFeatures;
60
61pub const DEFAULT_EPOCH_INTERRUPTION_PERIOD: Duration = Duration::from_micros(50);
62
63const NEXT_REQ_PENDING_MAX: usize = 5;
64const REGION_NONE: &str = "none";
65
66enum Instance {
67 Module(Module, InstancePre<WasmCtx>),
68 Component(
69 Component,
70 compute::bindings::AdapterServicePre<ComponentCtx>,
71 ),
72}
73
74impl Instance {
75 fn unwrap_module(&self) -> (&Module, &InstancePre<WasmCtx>) {
76 match self {
77 Instance::Module(m, i) => (m, i),
78 Instance::Component(_, _) => panic!("unwrap_module called on a component"),
79 }
80 }
81}
82
83#[derive(Clone)]
89pub enum ProfilingConfig {
90 None,
92 Guest {
94 path: PathBuf,
97 sample_period: Duration,
99 },
100 Native(wasmtime::ProfilingStrategy),
102}
103
104impl ProfilingConfig {
105 pub fn native_strategy(&self) -> wasmtime::ProfilingStrategy {
107 match self {
108 ProfilingConfig::Native(strategy) => *strategy,
109 _ => wasmtime::ProfilingStrategy::None,
110 }
111 }
112
113 fn guest_config(&self) -> Option<GuestProfileConfig> {
115 match self {
116 ProfilingConfig::Guest {
117 path,
118 sample_period,
119 } => Some(GuestProfileConfig {
120 path: path.clone(),
121 sample_period: *sample_period,
122 }),
123 _ => None,
124 }
125 }
126}
127
128#[derive(Clone)]
130struct GuestProfileConfig {
131 path: PathBuf,
132 sample_period: Duration,
133}
134
135pub struct NextRequest(Option<(Box<DownstreamRequest>, Arc<ExecuteCtx>)>);
136
137impl NextRequest {
138 pub fn into_request(mut self) -> Option<DownstreamRequest> {
139 self.0.take().map(|(r, _)| *r)
140 }
141}
142
143impl fmt::Debug for NextRequest {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 let debug = self.0.as_ref().map(|(r, _)| r);
146 f.debug_tuple("NextRequest")
147 .field(&debug)
148 .finish_non_exhaustive()
149 }
150}
151
152impl Drop for NextRequest {
153 fn drop(&mut self) {
154 let Some((req, ctx)) = self.0.take() else {
155 return;
156 };
157
158 ctx.retry_request(*req);
159 }
160}
161
162pub struct ExecuteCtx {
168 engine: Engine,
170 instance_pre: Arc<Instance>,
172 tls_config: TlsConfig,
174 next_req_id: Arc<AtomicU64>,
176 cache: Arc<Cache>,
178 pending_reuse: Arc<AsyncMutex<Vec<Sender<NextRequest>>>>,
180 epoch_increment_thread: Option<JoinHandle<()>>,
181 epoch_increment_stop: Arc<AtomicBool>,
183 guest_profile_config: Option<Arc<GuestProfileConfig>>,
185 settings: Settings,
187}
188
189struct Settings {
195 acls: Acls,
197 backends: Backends,
199 device_detection: DeviceDetection,
201 geolocation: Geolocation,
203 dictionaries: Dictionaries,
205 config_path: Option<PathBuf>,
207 capture_logs: Arc<Mutex<dyn Write + Send>>,
209 log_stdout: bool,
211 log_stderr: bool,
213 local_pushpin_proxy_port: Option<u16>,
215 enable_local_websocket_passthrough: bool,
220 object_store: ObjectStores,
222 secret_stores: SecretStores,
224 shielding_sites: ShieldingSites,
226 fake_valid_fastly_keys: FakeValidFastlyKeys,
228}
229
230impl Default for Settings {
231 fn default() -> Self {
232 Self {
233 acls: Acls::new(),
234 backends: Backends::default(),
235 device_detection: DeviceDetection::default(),
236 geolocation: Geolocation::default(),
237 dictionaries: Dictionaries::default(),
238 config_path: None,
239 capture_logs: Arc::new(Mutex::new(std::io::stdout())),
240 log_stdout: false,
241 log_stderr: false,
242 local_pushpin_proxy_port: None,
243 enable_local_websocket_passthrough: true,
244 object_store: ObjectStores::new(),
245 secret_stores: SecretStores::new(),
246 shielding_sites: ShieldingSites::new(),
247 fake_valid_fastly_keys: FakeValidFastlyKeys::new(),
248 }
249 }
250}
251
252impl ExecuteCtx {
253 pub fn build(
259 module_path: impl AsRef<Path>,
260 profiling: ProfilingConfig,
261 wasi_modules: HashSet<ExperimentalModule>,
262 unknown_import_behavior: UnknownImportBehavior,
263 adapt_components: bool,
264 wasm_features: WasmFeatures,
265 ) -> Result<ExecuteCtxBuilder, Error> {
266 Ok(ExecuteCtxBuilder {
267 module_path: module_path.as_ref().to_owned(),
268 profiling,
269 wasi_modules,
270 unknown_import_behavior,
271 adapt_components,
272 wasm_features,
273 debug_info: false,
274 settings: Settings::default(),
275 })
276 }
277
278 pub fn new(
282 module_path: impl AsRef<Path>,
283 profiling: ProfilingConfig,
284 wasi_modules: HashSet<ExperimentalModule>,
285 unknown_import_behavior: UnknownImportBehavior,
286 adapt_components: bool,
287 wasm_features: WasmFeatures,
288 ) -> Result<Arc<Self>, Error> {
289 ExecuteCtx::build(
290 module_path,
291 profiling,
292 wasi_modules,
293 unknown_import_behavior,
294 adapt_components,
295 wasm_features,
296 )?
297 .finish()
298 }
299
300 pub fn engine(&self) -> &Engine {
302 &self.engine
303 }
304
305 pub fn acls(&self) -> &Acls {
307 &self.settings.acls
308 }
309
310 pub fn backends(&self) -> &Backends {
312 &self.settings.backends
313 }
314
315 pub fn device_detection(&self) -> &DeviceDetection {
317 &self.settings.device_detection
318 }
319
320 pub fn geolocation(&self) -> &Geolocation {
322 &self.settings.geolocation
323 }
324
325 pub fn dictionaries(&self) -> &Dictionaries {
327 &self.settings.dictionaries
328 }
329
330 pub fn capture_logs(&self) -> Arc<Mutex<dyn Write + Send>> {
332 self.settings.capture_logs.clone()
333 }
334
335 pub fn log_stdout(&self) -> bool {
337 self.settings.log_stdout
338 }
339
340 pub fn log_stderr(&self) -> bool {
342 self.settings.log_stderr
343 }
344
345 pub fn tls_config(&self) -> &TlsConfig {
347 &self.tls_config
348 }
349
350 async fn maybe_receive_response(
351 mut receiver: mpsc::Receiver<DownstreamResponse>,
352 ) -> Option<(Response<Body>, Option<anyhow::Error>)> {
353 loop {
354 match receiver.recv().await? {
355 DownstreamResponse::Http(mut resp) => {
356 apply_response_framing(&mut resp);
357
358 if resp.status().is_informational() {
364 tracing::warn!(
366 "Guest returned informational response ({}) which will not be sent to the client",
367 resp.status(),
368 );
369 tracing::info!("{:#?}", resp);
370 continue;
371 }
372
373 return Some((resp, None));
374 }
375 DownstreamResponse::Pending(pending) => {
376 let mut resp = pending
377 .recv_or_else(|e| {
378 let status = e.as_status_code();
379 let err = anyhow::Error::from(e);
380 anyhow_response_with_status(&err, status)
381 })
382 .await;
383
384 apply_response_framing(&mut resp);
385
386 return Some((resp, None));
387 }
388 DownstreamResponse::HandoffToPushpin(info) => {
389 let err = NonHttpResponse::HandoffToPushpin(info).into();
390 let resp = Response::new(Body::empty());
391 return Some((resp, Some(err)));
392 }
393 DownstreamResponse::HandoffToBackend(info) => {
394 let resp = Response::new(Body::empty());
395 let err = NonHttpResponse::HandoffToBackend(info).into();
396 return Some((resp, Some(err)));
397 }
398 }
399 }
400 }
401
402 pub async fn handle_request(
440 self: Arc<Self>,
441 mut incoming_req: Request<hyper::Body>,
442 local: SocketAddr,
443 remote: SocketAddr,
444 ) -> Result<(Response<Body>, Option<anyhow::Error>), Error> {
445 let orig_req_on_upgrade = hyper::upgrade::on(&mut incoming_req);
446 let (incoming_req_parts, incoming_req_body) = incoming_req.into_parts();
447 let local_pushpin_proxy_port = self.settings.local_pushpin_proxy_port;
448
449 let (body_for_wasm, orig_body_tee) = tee(incoming_req_body).await;
450 let orig_request_info_for_pushpin = HandoffRequestInfo::from_parts(&incoming_req_parts);
451
452 let original_headers = incoming_req_parts.headers.clone();
453 let req = prepare_request(Request::from_parts(incoming_req_parts, body_for_wasm))?;
454
455 let req_id = self
456 .next_req_id
457 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
458
459 let metadata = DownstreamMetadata {
460 req_id,
461 server_addr: local,
462 client_addr: remote,
463 compliance_region: String::from(REGION_NONE),
464 original_headers,
465 };
466
467 let backends = self.settings.backends.clone();
468 let tls_config = self.tls_config.clone();
469
470 let (resp, mut err) = self.reuse_or_spawn_guest(req, metadata).await;
471
472 let span = info_span!("request", id = req_id);
473 let _span = span.enter();
474
475 info!("response status: {:?}", resp.status());
476
477 if let Some(e) = err {
478 match e.downcast::<NonHttpResponse>() {
479 Ok(NonHttpResponse::HandoffToPushpin(handoff_info)) => {
480 let backend_name = handoff_info.backend_name.clone();
481
482 info!("Pushpin handoff signaled to backend '{backend_name}'");
483
484 let local_pushpin_proxy_port = match local_pushpin_proxy_port {
488 None => {
489 error!("Pushpin handoff signaled, but Pushpin mode not enabled.");
490 let mut resp = Response::new(Body::from(hyper::Body::from(
491 "Pushpin handoff signaled, but Pushpin mode not enabled.",
492 )));
493 *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
494 return Ok((resp, None));
495 }
496 Some(port) => port,
497 };
498
499 let pushpin_addr =
500 SocketAddr::new(Ipv4Addr::LOCALHOST.into(), local_pushpin_proxy_port)
501 .to_string();
502
503 let additional_headers =
506 vec![("pushpin-route".to_string(), backend_name.clone())];
507
508 let handoff_resp = perform_handoff(
509 handoff_info.request_info,
510 orig_request_info_for_pushpin,
511 orig_body_tee,
512 orig_req_on_upgrade,
513 HandoffConfig {
514 target_addr: pushpin_addr.clone(),
515 host_header: pushpin_addr, display_name: format!("Pushpin [{backend_name}]"),
517 path_prefix: None, extra_headers: additional_headers,
519 tls_config: None, },
521 )
522 .await;
523
524 let (p, hyper_body) = handoff_resp.into_parts();
525 return Ok((Response::from_parts(p, Body::from(hyper_body)), None));
526 }
527 Ok(NonHttpResponse::HandoffToBackend(handoff_info)) => {
528 let backend_name = handoff_info.backend_name.clone();
529
530 info!("Backend handoff signaled to backend '{backend_name}'");
531
532 let backend = backends.get(backend_name.as_str());
533 let backend = match backend {
534 None => {
535 error!("Backend handoff signaled to unknown backend '{backend_name}'.");
536 let mut resp = Response::new(Body::from(hyper::Body::from(format!(
537 "Backend handoff signaled to unknown backend '{backend_name}'."
538 ))));
539 *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
540 return Ok((resp, None));
541 }
542 Some(backend) => backend,
543 };
544
545 let backend_uri = backend.uri.clone();
546 let tls_handoff_config =
547 if matches!(backend_uri.scheme_str(), Some("https" | "wss")) {
548 Some(HandoffTlsConfig {
549 ca_certs: backend.ca_certs.clone(),
550 client_cert: backend.client_cert.clone(),
551 use_sni: backend.use_sni,
552 cert_host: backend.cert_host.clone(),
553 dns_name_fallback: backend
554 .uri
555 .host()
556 .unwrap_or_default()
557 .to_string(),
558 is_grpc: backend.grpc,
559 base_tls_config: tls_config.clone(), })
561 } else {
562 None
563 };
564
565 let backend_host = backend_uri
566 .authority()
567 .map(|a| match (a.port(), backend_uri.scheme_str()) {
568 (None, Some("wss")) => format!("{}:443", a),
569 (None, Some("https")) => format!("{}:443", a),
570 (None, Some("ws")) => format!("{}:80", a),
571 (None, Some("http")) => format!("{}:80", a),
572 _ => a.to_string(), })
574 .unwrap_or_default();
575 let host_header = backend
577 .override_host
578 .clone()
579 .map(|host| host.to_str()
580 .expect("`backend.override_host`, if provided, should be a valid header value")
581 .to_string())
582 .unwrap_or_else(|| backend_host.clone());
583
584 let path_prefix = (!backend_uri.path().is_empty() && backend_uri.path() != "/")
586 .then(|| backend_uri.path().to_string());
587
588 let handoff_resp = perform_handoff(
589 handoff_info.request_info,
590 orig_request_info_for_pushpin,
591 orig_body_tee,
592 orig_req_on_upgrade,
593 HandoffConfig {
594 target_addr: backend_host,
595 host_header,
596 display_name: format!("Backend [{backend_name}]"),
597 path_prefix,
598 extra_headers: vec![], tls_config: tls_handoff_config,
600 },
601 )
602 .await;
603
604 let (p, hyper_body) = handoff_resp.into_parts();
605 return Ok((Response::from_parts(p, Body::from(hyper_body)), None));
606 }
607 Err(e) => {
608 err = Some(e);
609 }
610 }
611 }
612
613 Ok((resp, err))
614 }
615
616 pub(crate) fn retry_request(self: Arc<Self>, mut downstream: DownstreamRequest) {
619 if downstream.sender.is_closed() {
620 return;
621 }
622
623 tokio::task::spawn(async move {
624 let (sender, receiver) = mpsc::channel(10);
625 let original = std::mem::replace(&mut downstream.sender, sender);
626 let (resp, err) = self.spawn_guest(downstream, receiver).await;
627 let resp = guest_result_to_response(resp, err);
628 let _ = original.send(DownstreamResponse::Http(resp)).await;
629 });
630 }
631
632 pub async fn handle_request_with_runtime_error(
633 self: Arc<Self>,
634 incoming_req: Request<hyper::Body>,
635 local: SocketAddr,
636 remote: SocketAddr,
637 ) -> Result<Response<Body>, Error> {
638 let result = self.handle_request(incoming_req, local, remote).await?;
639 let resp = guest_result_to_response(result.0, result.1);
640
641 Ok(resp)
642 }
643
644 async fn reuse_or_spawn_guest(
645 self: Arc<Self>,
646 req: Request<Body>,
647 metadata: DownstreamMetadata,
648 ) -> (Response<Body>, Option<anyhow::Error>) {
649 let (downstream, receiver) = DownstreamRequest::new(req, metadata);
650
651 let mut next_req = NextRequest(Some((Box::new(downstream), self.clone())));
652 let mut reusable = self.pending_reuse.lock().await;
653
654 while let Some(pending) = reusable.pop() {
655 match pending.send(next_req) {
656 Ok(()) => {
657 drop(reusable);
659
660 if let Some(response) = Self::maybe_receive_response(receiver).await {
661 return response;
662 }
663 return (Response::default(), None);
664 }
665 Err(nr) => next_req = nr,
666 }
667 }
668
669 drop(reusable);
670
671 let downstream = next_req
672 .into_request()
673 .expect("request should still be unprocessed");
674 self.spawn_guest(downstream, receiver).await
675 }
676
677 async fn spawn_guest(
678 self: Arc<Self>,
679 downstream: DownstreamRequest,
680 receiver: mpsc::Receiver<DownstreamResponse>,
681 ) -> (Response<Body>, Option<anyhow::Error>) {
682 let active_cpu_time_us = Arc::new(AtomicU64::new(0));
683
684 let req_id = downstream.metadata.req_id;
687 let guest_handle = tokio::task::spawn(CpuTimeTracking::new(
688 active_cpu_time_us.clone(),
689 self.run_guest(downstream, active_cpu_time_us)
690 .instrument(info_span!("request", id = req_id)),
691 ));
692
693 if let Some(response) = Self::maybe_receive_response(receiver).await {
694 return response;
695 }
696
697 match guest_handle
698 .await
699 .expect("guest worker finished without panicking")
700 {
701 Ok(_) => (Response::new(Body::empty()), None),
702 Err(ExecutionError::WasmTrap(e)) => {
703 event!(
704 Level::ERROR,
705 "There was an error handling the request {}",
706 e.to_string()
707 );
708 (anyhow_response(&e), Some(e))
709 }
710 Err(e) => panic!("failed to run guest: {}", e),
711 }
712 }
713
714 async fn run_guest(
715 self: Arc<Self>,
716 downstream: DownstreamRequest,
717 active_cpu_time_us: Arc<AtomicU64>,
718 ) -> Result<(), ExecutionError> {
719 info!(
720 "handling request {} {}",
721 downstream.req.method(),
722 downstream.req.uri()
723 );
724 let start_timestamp = Instant::now();
725 let req_id = downstream.metadata.req_id;
726 let sandbox = Sandbox::new(downstream, active_cpu_time_us, self.clone());
727
728 let guest_profile_path = self.guest_profile_config.as_deref().map(|pcfg| {
729 let now = SystemTime::now()
730 .duration_since(SystemTime::UNIX_EPOCH)
731 .unwrap()
732 .as_secs();
733 pcfg.path.join(format!("{}-{}.json", now, req_id))
734 });
735
736 match self.instance_pre.as_ref() {
737 Instance::Component(component, instance_pre) => {
738 let profiler = self.guest_profile_config.as_deref().map(|pcfg| {
739 let program_name = "main";
740 GuestProfiler::new_component(
741 program_name,
742 pcfg.sample_period,
743 component.clone(),
744 std::iter::empty(),
745 )
746 });
747
748 let req = sandbox.downstream_request();
749 let body = sandbox.downstream_request_body();
750
751 let mut store = ComponentCtx::create_store(&self, sandbox, profiler, |ctx| {
752 ctx.arg("compute-app");
753 })
754 .map_err(ExecutionError::Context)?;
755
756 let compute = instance_pre
757 .instantiate_async(&mut store)
758 .await
759 .map_err(ExecutionError::Instantiation)?;
760
761 let result = compute
762 .fastly_compute_http_incoming()
763 .call_handle(&mut store, req.into(), body.into())
764 .await;
765
766 let outcome = match result {
767 Ok(Ok(())) => Ok(()),
768
769 Ok(Err(())) => {
770 event!(Level::ERROR, "WebAssembly exited with an error");
771 Err(ExecutionError::WasmTrap(anyhow::Error::msg("failed")))
772 }
773
774 Err(e) => {
775 if let Some(exit) = e.downcast_ref::<I32Exit>() {
776 if exit.0 == 0 {
777 Ok(())
778 } else {
779 event!(Level::ERROR, "WebAssembly exited with error: {:?}", e);
780 Err(ExecutionError::WasmTrap(e))
781 }
782 } else {
783 event!(Level::ERROR, "WebAssembly trapped: {:?}", e);
784 Err(ExecutionError::WasmTrap(e))
785 }
786 }
787 };
788
789 write_profile_component(&mut store, guest_profile_path.as_ref());
791
792 let resp = outcome
795 .as_ref()
796 .err()
797 .map(exec_err_to_response)
798 .unwrap_or_default();
799 store
800 .data_mut()
801 .sandbox
802 .close_downstream_response_sender(resp);
803
804 let request_duration = Instant::now().duration_since(start_timestamp);
805
806 info!(
807 "guest completed using {} of WebAssembly heap",
808 bytesize::ByteSize::b(store.data().limiter().memory_allocated as u64),
809 );
810
811 info!("guest completed in {:.0?}", request_duration);
812
813 outcome
814 }
815
816 Instance::Module(module, instance_pre) => {
817 let profiler = self.guest_profile_config.as_deref().map(|pcfg| {
818 let program_name = "main";
819 GuestProfiler::new(
820 program_name,
821 pcfg.sample_period,
822 vec![(program_name.to_string(), module.clone())],
823 )
824 });
825
826 let mut store = create_store(&self, sandbox, profiler, |ctx| {
831 ctx.arg("compute-app");
832 })
833 .map_err(ExecutionError::Context)?;
834
835 let instance = instance_pre
836 .instantiate_async(&mut store)
837 .await
838 .map_err(ExecutionError::Instantiation)?;
839
840 let main_func = instance
843 .get_typed_func::<(), ()>(&mut store, "_start")
844 .map_err(ExecutionError::Typechecking)?;
845
846 let outcome = match main_func.call_async(&mut store, ()).await {
848 Ok(_) => Ok(()),
849 Err(e) => {
850 if let Some(exit) = e.downcast_ref::<I32Exit>() {
851 if exit.0 == 0 {
852 Ok(())
853 } else {
854 event!(Level::ERROR, "WebAssembly exited with error: {:?}", e);
855 Err(ExecutionError::WasmTrap(e))
856 }
857 } else {
858 event!(Level::ERROR, "WebAssembly trapped: {:?}", e);
859 Err(ExecutionError::WasmTrap(e))
860 }
861 }
862 };
863
864 write_profile(&mut store, guest_profile_path.as_ref());
866
867 let resp = outcome
870 .as_ref()
871 .err()
872 .map(exec_err_to_response)
873 .unwrap_or_default();
874 store.data_mut().close_downstream_response_sender(resp);
875
876 let request_duration = Instant::now().duration_since(start_timestamp);
877
878 info!(
879 "request completed using {} of WebAssembly heap",
880 bytesize::ByteSize::b(store.data().limiter().memory_allocated as u64)
881 );
882
883 info!("request completed in {:.0?}", request_duration);
884
885 outcome
886 }
887 }
888 }
889
890 pub async fn run_main(
891 self: Arc<Self>,
892 program_name: &str,
893 args: &[String],
894 ) -> Result<(), anyhow::Error> {
895 let req = Request::get("http://example.com/").body(Body::empty())?;
897 let metadata = DownstreamMetadata {
898 req_id: 0,
899 server_addr: (Ipv4Addr::LOCALHOST, 80).into(),
900 client_addr: (Ipv4Addr::LOCALHOST, 0).into(),
901 compliance_region: String::from(REGION_NONE),
902 original_headers: Default::default(),
903 };
904 let (downstream, receiver) = DownstreamRequest::new(req, metadata);
905 let active_cpu_time_us = Arc::new(AtomicU64::new(0));
906
907 let sandbox = Sandbox::new(downstream, active_cpu_time_us.clone(), self.clone());
908
909 if let Instance::Component(_, _) = self.instance_pre.as_ref() {
910 panic!("components not currently supported with `run`");
911 }
912
913 let (module, instance_pre) = self.instance_pre.unwrap_module();
914
915 let profiler = self.guest_profile_config.as_deref().map(|pcfg| {
916 GuestProfiler::new(
917 program_name,
918 pcfg.sample_period,
919 vec![(program_name.to_string(), module.clone())],
920 )
921 });
922
923 let mut store = create_store(&self, sandbox, profiler, |builder| {
924 builder.arg(program_name);
925 for arg in args {
926 builder.arg(arg);
927 }
928 })
929 .map_err(ExecutionError::Context)?;
930
931 let instance = instance_pre
932 .instantiate_async(&mut store)
933 .await
934 .map_err(ExecutionError::Instantiation)?;
935
936 let main_func = instance
939 .get_typed_func::<(), ()>(&mut store, "_start")
940 .map_err(ExecutionError::Typechecking)?;
941
942 let result =
944 CpuTimeTracking::new(active_cpu_time_us, main_func.call_async(&mut store, ())).await;
945
946 write_profile(
948 &mut store,
949 self.guest_profile_config.as_deref().map(|cfg| &cfg.path),
950 );
951
952 store
955 .data_mut()
956 .close_downstream_response_sender(Response::default());
957
958 drop(receiver);
962
963 result
964 }
965
966 pub fn cache(&self) -> &Arc<Cache> {
967 &self.cache
968 }
969
970 pub fn config_path(&self) -> Option<&Path> {
971 self.settings.config_path.as_deref()
972 }
973
974 pub fn object_store(&self) -> &ObjectStores {
975 &self.settings.object_store
976 }
977
978 pub fn secret_stores(&self) -> &SecretStores {
979 &self.settings.secret_stores
980 }
981
982 pub fn shielding_sites(&self) -> &ShieldingSites {
983 &self.settings.shielding_sites
984 }
985
986 pub fn local_pushpin_proxy_port(&self) -> Option<u16> {
988 self.settings.local_pushpin_proxy_port
989 }
990
991 pub fn enable_local_websocket_passthrough(&self) -> bool {
993 self.settings.enable_local_websocket_passthrough
994 }
995
996 pub fn fake_valid_fastly_keys(&self) -> &FakeValidFastlyKeys {
998 &self.settings.fake_valid_fastly_keys
999 }
1000
1001 pub async fn register_pending_downstream(&self) -> Option<oneshot::Receiver<NextRequest>> {
1002 let mut pending = self.pending_reuse.lock().await;
1003
1004 if pending.len() >= NEXT_REQ_PENDING_MAX {
1005 return None;
1006 }
1007
1008 let (tx, rx) = oneshot::channel();
1009 pending.push(tx);
1010
1011 Some(rx)
1012 }
1013
1014 pub fn is_component(&self) -> bool {
1015 matches!(self.instance_pre.as_ref(), Instance::Component(_, _))
1016 }
1017}
1018
1019pub struct ExecuteCtxBuilder {
1020 module_path: PathBuf,
1022 profiling: ProfilingConfig,
1024 wasi_modules: HashSet<ExperimentalModule>,
1026 unknown_import_behavior: UnknownImportBehavior,
1028 adapt_components: bool,
1030 wasm_features: WasmFeatures,
1032 debug_info: bool,
1035 settings: Settings,
1037}
1038
1039impl ExecuteCtxBuilder {
1040 pub fn finish(self) -> Result<Arc<ExecuteCtx>, Error> {
1043 let ExecuteCtxBuilder {
1044 module_path,
1045 profiling,
1046 wasi_modules,
1047 unknown_import_behavior,
1048 adapt_components,
1049 wasm_features,
1050 debug_info,
1051 settings,
1052 } = self;
1053
1054 let guest_profile_config = profiling.guest_config();
1055
1056 let input = fs::read(&module_path)?;
1057
1058 let is_wat = module_path
1059 .extension()
1060 .map(|str| str == "wat")
1061 .unwrap_or(false);
1062
1063 let is_component = adapt::is_component(&input);
1066 let (is_wat, is_component, input) = if !is_component && adapt_components {
1067 let input = if is_wat {
1068 let text = String::from_utf8(input)
1069 .map_err(|_| anyhow::anyhow!("Failed to parse {}", module_path.display()))?;
1070 adapt::adapt_wat(&text)?
1071 } else {
1072 adapt::adapt_bytes(&input)?
1073 };
1074
1075 (false, true, input)
1076 } else {
1077 (is_wat, is_component, input)
1078 };
1079
1080 let config = &configure_wasmtime(wasm_features, profiling.native_strategy(), debug_info);
1081 let engine = Engine::new(config)?;
1082 let instance_pre = if is_component {
1083 let mut linker: component::Linker<ComponentCtx> = component::Linker::new(&engine);
1084 compute::link_host_functions(&mut linker)?;
1085 let component = if is_wat {
1086 Component::from_file(&engine, &module_path)?
1087 } else {
1088 Component::from_binary(&engine, &input)?
1089 };
1090
1091 match unknown_import_behavior {
1092 UnknownImportBehavior::LinkError => (),
1093 UnknownImportBehavior::Trap => {
1094 linker.define_unknown_imports_as_traps(&component)?
1095 }
1096 }
1097
1098 let instance_pre = linker.instantiate_pre(&component)?;
1099 Instance::Component(
1100 component,
1101 compute::bindings::AdapterServicePre::new(instance_pre)?,
1102 )
1103 } else {
1104 let mut linker = Linker::new(&engine);
1105 link_host_functions(&mut linker, &wasi_modules)?;
1106 let module = if is_wat {
1107 Module::from_file(&engine, &module_path)?
1108 } else {
1109 Module::from_binary(&engine, &input)?
1110 };
1111
1112 match unknown_import_behavior {
1113 UnknownImportBehavior::LinkError => (),
1114 UnknownImportBehavior::Trap => linker.define_unknown_imports_as_traps(&module)?,
1115 }
1116
1117 let instance_pre = linker.instantiate_pre(&module)?;
1118 Instance::Module(module, instance_pre)
1119 };
1120
1121 let epoch_increment_stop = Arc::new(AtomicBool::new(false));
1127 let engine_clone = engine.clone();
1128 let epoch_increment_stop_clone = epoch_increment_stop.clone();
1129 let sample_period = guest_profile_config
1130 .as_ref()
1131 .map(|c| c.sample_period)
1132 .unwrap_or(DEFAULT_EPOCH_INTERRUPTION_PERIOD);
1133 let epoch_increment_thread = Some(thread::spawn(move || {
1134 while !epoch_increment_stop_clone.load(Ordering::Relaxed) {
1135 thread::sleep(sample_period);
1136 engine_clone.increment_epoch();
1137 }
1138 }));
1139
1140 Ok(Arc::new(ExecuteCtx {
1141 engine,
1142 instance_pre: Arc::new(instance_pre),
1143 tls_config: TlsConfig::new()?,
1144 next_req_id: Arc::new(AtomicU64::new(0)),
1145 cache: Arc::new(Cache::default()),
1146 pending_reuse: Arc::new(AsyncMutex::new(vec![])),
1147 epoch_increment_thread,
1148 epoch_increment_stop,
1149 guest_profile_config: guest_profile_config.map(Arc::new),
1150 settings,
1151 }))
1152 }
1153
1154 pub fn with_debug_info(mut self, debug_info: bool) -> Self {
1160 self.debug_info = debug_info;
1161 self
1162 }
1163
1164 pub fn with_acls(mut self, acls: Acls) -> Self {
1166 self.settings.acls = acls;
1167 self
1168 }
1169
1170 pub fn with_backends(mut self, backends: Backends) -> Self {
1172 self.settings.backends = backends;
1173 self
1174 }
1175
1176 pub fn with_device_detection(mut self, device_detection: DeviceDetection) -> Self {
1178 self.settings.device_detection = device_detection;
1179 self
1180 }
1181
1182 pub fn with_geolocation(mut self, geolocation: Geolocation) -> Self {
1184 self.settings.geolocation = geolocation;
1185 self
1186 }
1187
1188 pub fn with_dictionaries(mut self, dictionaries: Dictionaries) -> Self {
1190 self.settings.dictionaries = dictionaries;
1191 self
1192 }
1193
1194 pub fn with_object_stores(mut self, object_store: ObjectStores) -> Self {
1196 self.settings.object_store = object_store;
1197 self
1198 }
1199
1200 pub fn with_secret_stores(mut self, secret_stores: SecretStores) -> Self {
1202 self.settings.secret_stores = secret_stores;
1203 self
1204 }
1205 pub fn with_shielding_sites(mut self, shielding_sites: ShieldingSites) -> Self {
1207 self.settings.shielding_sites = shielding_sites;
1208 self
1209 }
1210
1211 pub fn with_fake_valid_fastly_keys(
1213 mut self,
1214 fake_valid_fastly_keys: FakeValidFastlyKeys,
1215 ) -> Self {
1216 self.settings.fake_valid_fastly_keys = fake_valid_fastly_keys;
1217 self
1218 }
1219
1220 pub fn with_config_path(mut self, config_path: PathBuf) -> Self {
1222 self.settings.config_path = Some(config_path);
1223 self
1224 }
1225
1226 pub fn with_capture_logs(mut self, capture_logs: Arc<Mutex<dyn Write + Send>>) -> Self {
1229 self.settings.capture_logs = capture_logs;
1230 self
1231 }
1232
1233 pub fn with_log_stdout(mut self, log_stdout: bool) -> Self {
1235 self.settings.log_stdout = log_stdout;
1236 self
1237 }
1238
1239 pub fn with_log_stderr(mut self, log_stderr: bool) -> Self {
1241 self.settings.log_stderr = log_stderr;
1242 self
1243 }
1244
1245 pub fn with_local_pushpin_proxy_port(mut self, local_pushpin_proxy_port: Option<u16>) -> Self {
1247 self.settings.local_pushpin_proxy_port = local_pushpin_proxy_port;
1248 self
1249 }
1250
1251 pub fn with_enable_local_websocket_passthrough(
1253 mut self,
1254 enable_local_websocket_passthrough: bool,
1255 ) -> Self {
1256 self.settings.enable_local_websocket_passthrough = enable_local_websocket_passthrough;
1257 self
1258 }
1259}
1260
1261fn write_profile_to_file(profile: Box<GuestProfiler>, path: &PathBuf) {
1262 match std::fs::File::create(path)
1263 .map_err(anyhow::Error::new)
1264 .and_then(|output| profile.finish(std::io::BufWriter::new(output)))
1265 {
1266 Err(e) => {
1267 event!(
1268 Level::ERROR,
1269 "failed writing profile at {}: {e:#}",
1270 path.display()
1271 );
1272 }
1273 _ => {
1274 event!(
1275 Level::INFO,
1276 "\nProfile written to: {}\nView this profile at https://profiler.firefox.com/.",
1277 path.display()
1278 );
1279 }
1280 }
1281}
1282
1283fn write_profile(store: &mut wasmtime::Store<WasmCtx>, guest_profile_path: Option<&PathBuf>) {
1284 if let (Some(profile), Some(path)) =
1285 (store.data_mut().take_guest_profiler(), guest_profile_path)
1286 {
1287 write_profile_to_file(profile, path);
1288 }
1289}
1290
1291fn write_profile_component(
1292 store: &mut wasmtime::Store<ComponentCtx>,
1293 guest_profile_path: Option<&PathBuf>,
1294) {
1295 if let (Some(profile), Some(path)) =
1296 (store.data_mut().take_guest_profiler(), guest_profile_path)
1297 {
1298 write_profile_to_file(profile, path);
1299 }
1300}
1301
1302fn guest_result_to_response(resp: Response<Body>, err: Option<anyhow::Error>) -> Response<Body> {
1303 err.as_ref().map(anyhow_response).unwrap_or(resp)
1304}
1305
1306fn exec_err_to_response(err: &ExecutionError) -> Response<Body> {
1307 if let ExecutionError::WasmTrap(e) = err {
1308 anyhow_response(e)
1309 } else {
1310 panic!("failed to run guest: {err}")
1311 }
1312}
1313
1314fn anyhow_response(err: &anyhow::Error) -> Response<Body> {
1315 anyhow_response_with_status(err, hyper::StatusCode::INTERNAL_SERVER_ERROR)
1316}
1317
1318fn anyhow_response_with_status(err: &anyhow::Error, status: hyper::StatusCode) -> Response<Body> {
1319 Response::builder()
1320 .status(status)
1321 .body(Body::from(format!("{err:?}").into_bytes()))
1322 .unwrap()
1323}
1324
1325impl Drop for ExecuteCtx {
1326 fn drop(&mut self) {
1327 if let Some(join_handle) = self.epoch_increment_thread.take() {
1328 self.epoch_increment_stop.store(true, Ordering::Relaxed);
1329 join_handle.join().unwrap();
1330 }
1331 }
1332}
1333
1334fn configure_wasmtime(
1335 wasm_features: WasmFeatures,
1336 profiling_strategy: ProfilingStrategy,
1337 debug_info: bool,
1338) -> wasmtime::Config {
1339 use wasmtime::{Config, InstanceAllocationStrategy, WasmBacktraceDetails};
1340
1341 let mut config = Config::new();
1342 config.debug_info(debug_info);
1345 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
1346 config.async_support(true);
1347 config.epoch_interruption(true);
1348 config.profiler(profiling_strategy);
1349
1350 config.allocation_strategy(InstanceAllocationStrategy::OnDemand);
1351
1352 config.wasm_features(wasm_features, true);
1353
1354 config.relaxed_simd_deterministic(true);
1362
1363 config.wasm_wide_arithmetic(true);
1366
1367 config
1368}
1369
1370#[pin_project]
1371struct CpuTimeTracking<F> {
1372 #[pin]
1373 future: F,
1374 time_spent: Arc<AtomicU64>,
1375}
1376
1377impl<F> CpuTimeTracking<F> {
1378 fn new(time_spent: Arc<AtomicU64>, future: F) -> Self {
1379 CpuTimeTracking { future, time_spent }
1380 }
1381}
1382
1383impl<E, F: Future<Output = Result<(), E>>> Future for CpuTimeTracking<F> {
1384 type Output = F::Output;
1385
1386 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1387 let me = self.project();
1388
1389 let start = Instant::now();
1390 let result = me.future.poll(cx);
1391 let runtime = start.elapsed().as_micros() as u64;
1394 let _ = me.time_spent.fetch_add(runtime, Ordering::SeqCst);
1395 result
1396 }
1397}