Skip to main content

wasmtime_wasi_http/
handler.rs

1//! Provides utilities useful for dispatching incoming HTTP requests
2//! `wasi:http/handler` guest instances.
3
4#[cfg(feature = "p2")]
5use crate::p2;
6#[cfg(feature = "p2")]
7use crate::p2::bindings::http::types as p2_types;
8#[cfg(feature = "p3")]
9use crate::p3;
10use crate::{WasiBody, WasiHttpCtxView};
11use futures::{
12    channel::oneshot,
13    future::{Either, FutureExt},
14    stream::{FuturesUnordered, Stream},
15};
16#[cfg(feature = "p3")]
17use p3::bindings::http::types as p3_types;
18use std::collections::VecDeque;
19use std::collections::btree_map::{BTreeMap, Entry};
20use std::error;
21use std::fmt;
22use std::future;
23use std::mem;
24use std::ops::DerefMut;
25use std::pin::{Pin, pin};
26use std::sync::{
27    Arc, Mutex,
28    atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed},
29};
30use std::task::{Context, Poll};
31use std::time::Instant;
32use tokio::sync::Notify;
33use wasmtime::component::{Accessor, Resource, TaskGroupId, TypedFuncCallConcurrent};
34#[cfg(feature = "p2")]
35use wasmtime::error::Context as _;
36use wasmtime::{AsContextMut, Result, Store, StoreContextMut, format_err};
37
38/// A Request to be handled using `ProxyHandler::handle`.
39pub type Request = http::Request<WasiBody>;
40
41/// A Response returned by `ProxyHandler::handle`.
42pub type Response = http::Response<WasiBody>;
43
44/// Represents either a `wasi:http/incoming-handler@0.2.x` or
45/// `wasi:http/handler@0.3.x` pre-instance.
46pub enum ProxyPre<T: 'static> {
47    /// A `wasi:http/incoming-handler@0.2.x` pre-instance.
48    #[cfg(feature = "p2")]
49    P2(p2::bindings::ProxyPre<T>),
50    /// A `wasi:http/handler@0.3.x` pre-instance.
51    #[cfg(feature = "p3")]
52    P3(p3::bindings::ServicePre<T>),
53}
54
55impl<T: 'static> ProxyPre<T> {
56    /// Instantiates the pre-instance.
57    pub async fn instantiate_async(&self, store: impl AsContextMut<Data = T>) -> Result<Proxy>
58    where
59        T: Send,
60    {
61        Ok(match self {
62            #[cfg(feature = "p2")]
63            Self::P2(pre) => Proxy::P2(pre.instantiate_async(store).await?),
64            #[cfg(feature = "p3")]
65            Self::P3(pre) => Proxy::P3(pre.instantiate_async(store).await?),
66        })
67    }
68}
69
70/// Represents either a `wasi:http/incoming-handler@0.2.x` or
71/// `wasi:http/handler@0.3.x` instance.
72pub enum Proxy {
73    /// A `wasi:http/incoming-handler@0.2.x` instance.
74    #[cfg(feature = "p2")]
75    P2(p2::bindings::Proxy),
76    /// A `wasi:http/handler@0.3.x` instance.
77    #[cfg(feature = "p3")]
78    P3(p3::bindings::Service),
79}
80
81/// Async MPMC channel where each item is delivered to at most one consumer.
82struct Queue<T> {
83    queue: Mutex<VecDeque<T>>,
84    notify_push: Notify,
85}
86
87impl<T> Default for Queue<T> {
88    fn default() -> Self {
89        Self {
90            queue: Default::default(),
91            notify_push: Default::default(),
92        }
93    }
94}
95
96impl<T> Queue<T> {
97    fn is_empty(&self) -> bool {
98        self.queue.lock().unwrap().is_empty()
99    }
100
101    fn try_pop(&self) -> Option<T> {
102        self.queue.lock().unwrap().pop_front()
103    }
104
105    async fn pop(&self) -> T {
106        // This code comes from the Unbounded MPMC Channel example in [the
107        // `tokio::sync::Notify`
108        // docs](https://docs.rs/tokio/latest/tokio/sync/struct.Notify.html).
109
110        let mut notified = pin!(self.notify_push.notified());
111
112        loop {
113            notified.as_mut().enable();
114            if let Some(item) = self.try_pop() {
115                return item;
116            }
117            notified.as_mut().await;
118            notified.set(self.notify_push.notified());
119        }
120    }
121}
122
123/// Represents the status of a `ProxyHandler` worker task.
124#[derive(Clone, Copy, Eq, PartialEq, Debug)]
125pub enum WorkerStatus {
126    /// The worker is not handling any requests, nor is it doing any post-return
127    /// work.  It _might_ be doing background work which the guest has indicated
128    /// can be interrupted and/or abandoned at any time, i.e. does not prevent
129    /// the instance from being disposed.
130    Idle,
131    /// The instance is handling one or more requests, waiting for each to
132    /// either produce a response or expire.
133    Requests,
134    /// All requests handled so far have either produced a response or expired,
135    /// but the guest has post-return work which needs to finish before the
136    /// instance can be considered idle.
137    PostReturn,
138}
139
140/// Represents the application-specific state of a `ProxyHandler` worker.
141///
142/// [`HandlerState::instantiate`] returns an implementation of this trait for
143/// each component instance (and thus each worker) created.  The worker uses it
144/// to determine when to exit.
145pub trait WorkerExpiration: 'static + Send + Sync {
146    /// Poll whether the worker has expired.
147    ///
148    /// This will return `Poll::Ready(())` if the worker has expired, meaning
149    /// the component instance should be dropped.  Otherwise, it will return
150    /// `Poll::Pending` and wake the `Waker` if and when it should be polled
151    /// again.
152    ///
153    /// `state` represents the current state of the worker, and `start`
154    /// represents when it transitioned into that state (or in the case of
155    /// `WorkerState::Requests`, when the most recent outstanding request
156    /// was accepted).
157    fn poll(
158        self: Pin<&mut Self>,
159        cx: &mut Context<'_>,
160        state: WorkerStatus,
161        start: Instant,
162    ) -> Poll<()>;
163}
164
165/// Represents the application-specific state of a `ProxyHandler` worker.
166///
167/// [`HandlerState::instantiate`] returns an implementation of this trait for
168/// each component instance (and thus each worker) created.  The worker uses it
169/// to determine how many requests to accept, how long to wait for the guest to
170/// produce responses, etc.
171pub trait WorkerState: 'static + Send + Sync {
172    /// The type of the associated data for [`Store`] belonging to this worker.
173    type StoreData: Send;
174
175    /// Opaque data that hosts can attach to requests which is threaded from
176    /// [`ProxyHandler::handle`] into [`WorkerState::on_request_start`].
177    type RequestData: Send + Sync;
178
179    /// Indicate whether the worker should accept another request given the
180    /// current number it is already handling concurrently and the total it has
181    /// handled so far.
182    fn should_accept_request(&self, concurrent_count: usize, total_count: usize) -> ShouldAccept;
183
184    /// Notification that a request has been accepted by the worker.
185    ///
186    /// This method can be used to record anything within `store`, if necessary.
187    /// If the `task-group-hook` feature is enabled, the task group
188    /// corresponding to the component-model-level async task about to be
189    /// created is additionally passed here.
190    ///
191    /// If the future returned by this function resolves before the guest has
192    /// produced a response, the request will be considered "expired" and the
193    /// original `ProxyHandler::handle` future will resolve to an
194    /// `Err(ExpirationError.into())`.  In addition, the worker
195    /// will stop accepting new requests but will continue running until all
196    /// requests that have been accepted by the worker have either produced a
197    /// response or expired, at which point the state of the worker will
198    /// transition to either `WorkerState::PostReturn` or `WorkerState::Idle`.
199    ///
200    /// Note that the returned future is polled from within the
201    /// `Store::run_concurrent` event loop, and due to #11869 and #11870, it may
202    /// not be polled at all for arbitrary lengths of time.  Consequently, the
203    /// `Self::Expiration` implementation (which is polled from _outside_ the
204    /// `Store::run_concurrent` event loop) must also enforce request expiration
205    /// as a second level of defence if desired.
206    ///
207    /// For example, if a request timeout of N seconds is to be enforced, the
208    /// `Self::Expiration::poll` implementation, when called with
209    /// `WorkerState::Requests` should calculate the time elapsed since the most
210    /// recent outstanding request was accepted as indicated by the `start`
211    /// parameter.  If that time is greater than N seconds, we can expire the
212    /// instance immediately, confident that all outstanding requests have
213    /// expired.
214    ///
215    /// Once #11869 and #11870 have been addressed, this "second level of
216    /// defence" will no longer be necessary.
217    fn on_request_start(
218        &self,
219        store: StoreContextMut<'_, Self::StoreData>,
220        data: Self::RequestData,
221        task_group: TaskGroupId,
222    ) -> Pin<Box<dyn Future<Output = ()> + 'static + Send + Sync>>;
223
224    /// Dispose of the store belonging to the now-exited worker.
225    ///
226    /// This may be used to e.g. collect metrics from the store or its
227    /// associated data before the store is dropped, as well as e.g. retry
228    /// failed instantiations after the store is dropped.
229    ///
230    /// If the store is being dropped due to an error (e.g. a guest trap or a
231    /// host panic) `result` will be `Err(_)`; otherwise it will be `Ok(())`.
232    fn drop(&self, store: Store<Self::StoreData>, result: Result<(), wasmtime::Error>);
233
234    /// Poll the specified future representing the event loop for the store to
235    /// which the worker belongs.
236    fn poll<T>(&self, future: Pin<&mut impl Future<Output = T>>, cx: &mut Context) -> Poll<T> {
237        future.poll(cx)
238    }
239}
240
241/// Represents the combination of a store and instance with which to handle
242/// requests.
243pub struct Instance<T: 'static, E: WorkerExpiration, S: WorkerState> {
244    /// The store to use to handle requests.
245    pub store: Store<T>,
246    /// The instance to use to handle requests.
247    pub proxy: Proxy,
248    /// `WasiHttpCtxView` getter function.
249    pub view: fn(&mut T) -> WasiHttpCtxView<'_>,
250    /// See [`WorkerExpiration`].
251    pub expiration: E,
252    /// See [`WorkerState`].
253    pub state: S,
254}
255
256/// Indicates whether a worker should accept new requests.
257pub enum ShouldAccept {
258    /// Yes, it should.
259    Yes,
260    /// No, it shouldn't (but ask again later).
261    No,
262    /// No, it shouldn't (and don't ask again).
263    Never,
264}
265
266/// Represents the application-specific state of a web server.
267pub trait HandlerState: 'static + Sync + Send + Sized {
268    /// The type of the associated data for [`Store`]s created using
269    /// [`Self::instantiate`].
270    type StoreData: Send;
271    /// The type of the `WorkerExpiration` implementation to be returned from
272    /// [`Self::instantiate`].
273    type WorkerExpiration: WorkerExpiration;
274    /// The type of the `WorkerState` implementation to be returned from
275    /// [`Self::instantiate`].
276    type WorkerState: WorkerState<StoreData = Self::StoreData>;
277
278    /// Create a new store and instance for handling one or more requests.
279    ///
280    /// Note that the implementer is responsible for applying a timeout to the
281    /// guest instantiation if appropriate (e.g. as part of an overall request
282    /// timeout).
283    fn instantiate(
284        &self,
285    ) -> impl Future<
286        Output = Result<Instance<Self::StoreData, Self::WorkerExpiration, Self::WorkerState>>,
287    > + Send;
288}
289
290struct ProxyHandlerInner<S: HandlerState> {
291    state: S,
292    request_queue: Queue<WorkerRequest<S>>,
293    worker_count: AtomicUsize,
294}
295
296/// Tracks request start times.
297///
298/// This is useful for keeping a [`WorkerState`] appraised of the most recently
299/// accepted outstanding request.
300#[derive(Default)]
301struct StartTimes(BTreeMap<Instant, usize>);
302
303impl StartTimes {
304    fn add(&mut self, time: Instant) {
305        *self.0.entry(time).or_insert(0) += 1;
306    }
307
308    fn remove(&mut self, time: Instant) {
309        let Entry::Occupied(mut entry) = self.0.entry(time) else {
310            unreachable!()
311        };
312        match *entry.get() {
313            0 => unreachable!(),
314            1 => {
315                entry.remove();
316            }
317            _ => {
318                *entry.get_mut() -= 1;
319            }
320        }
321    }
322
323    fn most_recent(&self) -> Option<Instant> {
324        self.0.last_key_value().map(|(&k, _)| k)
325    }
326}
327
328type WorkerRequest<S> = (
329    <<S as HandlerState>::WorkerState as WorkerState>::RequestData,
330    Request,
331    oneshot::Sender<Result<Response, wasmtime::Error>>,
332);
333
334struct Worker<S>
335where
336    S: HandlerState,
337{
338    handler: ProxyHandler<S>,
339    available: bool,
340}
341
342impl<S> Worker<S>
343where
344    S: HandlerState,
345{
346    fn set_available(&mut self, available: bool) {
347        if available != self.available {
348            self.available = available;
349            if available {
350                self.handler.0.worker_count.fetch_add(1, Relaxed);
351            } else {
352                // Decrement the count _before_ checking if the request queue is
353                // empty.  This helps ensure that `ProxyHandler::spawn` sees the
354                // new value before deciding whether to spawn a new worker.
355                let count = self.handler.0.worker_count.fetch_sub(1, Relaxed);
356                assert!(count >= 1);
357
358                // This addresses what would otherwise be a race condition in
359                // `ProxyHandler::spawn` where it only starts a worker if the
360                // available worker count is zero.  If we decrement the count to
361                // zero right after `ProxyHandler::spawn` checks it, then no
362                // worker will be started; thus it becomes our responsibility to
363                // start a worker here instead.
364                if count == 1 && !self.handler.0.request_queue.is_empty() {
365                    self.handler.start_worker(None);
366                }
367            }
368        }
369    }
370
371    async fn run(self, request: Option<WorkerRequest<S>>) {
372        match self.handler.0.state.instantiate().await {
373            Ok(Instance {
374                store,
375                proxy,
376                view,
377                expiration,
378                state,
379            }) => {
380                self.run_(store, proxy, view, expiration, state, request)
381                    .await
382            }
383
384            Err(error) => {
385                let error = Arc::new(error);
386                if let Some((request_data, request, tx)) = request {
387                    _ = tx.send(Err(InstantiationError {
388                        request_data,
389                        request: Mutex::new(request),
390                        error,
391                    }
392                    .into()));
393                } else {
394                    // In this case, the worker was spawned to handle any queued
395                    // requests.  Since we can't handle those requests, we send
396                    // them all an instantiation error.
397                    for (request_data, request, tx) in mem::take(
398                        self.handler
399                            .0
400                            .request_queue
401                            .queue
402                            .lock()
403                            .unwrap()
404                            .deref_mut(),
405                    ) {
406                        _ = tx.send(Err(InstantiationError {
407                            request_data,
408                            request: Mutex::new(request),
409                            error: error.clone(),
410                        }
411                        .into()));
412                    }
413                }
414            }
415        }
416    }
417
418    async fn run_(
419        mut self,
420        store: Store<S::StoreData>,
421        proxy: Proxy,
422        view: fn(&mut S::StoreData) -> WasiHttpCtxView<'_>,
423        expiration: S::WorkerExpiration,
424        state: S::WorkerState,
425        request: Option<WorkerRequest<S>>,
426    ) {
427        // NB: The code the follows is rather subtle in that it is structured
428        // carefully to give the `HandlerState` implementation full control over
429        // the component instance lifetime. Specifically, we must keep the
430        // `HandlerState` informed of the worker's state and how long it has
431        // been in that state, as well as allow it to expire the instance based
432        // on whatever combination of timeouts, dynamic resource usage, etc. it
433        // may take into consideration.
434        //
435        // Note that, when more than one request is handled concurrently in the
436        // same instance, we must stop accepting new requests as soon as any
437        // existing request reaches its expiration.  This serves to cap the
438        // amount of time we need to keep the instance alive before _all_
439        // requests have either completed or expired.
440        //
441        // As of this writing, there's an additional wrinkle that makes tracking
442        // expiration particularly tricky: per #11869 and #11870, busy guest
443        // loops, epoch interruption, and host functions registered using
444        // `Linker::func_{wrap,new}_async` all require blocking, exclusive
445        // access to the `Store`, which effectively prevents the
446        // `StoreContextMut::run_concurrent` event loop from making progress.
447        // That, in turn, prevents any concurrent tasks from executing, and also
448        // prevents the `AsyncFnOnce` passed to `run_concurrent` from being
449        // polled.  Consequently, we must poll `S::WorkerState` from _outside_
450        // the `run_concurrent` future to ensure expirations are enforced.  Once
451        // the aforementioned issues have been addressed, we'll be able to
452        // simplify the code and eliminate the need for communication between
453        // the "inside" future and the "outside" one.
454
455        // Wrap `store` in an object which, prior to leaving this scope, will
456        // pass the `store` to `HandlerState::drop`.
457        struct Dropper<S: HandlerState> {
458            state: S::WorkerState,
459            store: Option<Store<S::StoreData>>,
460        }
461
462        impl<S: HandlerState> Drop for Dropper<S> {
463            fn drop(&mut self) {
464                if let Some(store) = self.store.take() {
465                    self.state
466                        .drop(store, Err(wasmtime::format_err!("worker panicked")));
467                }
468            }
469        }
470
471        let mut dropper = Dropper::<S> {
472            state,
473            store: Some(store),
474        };
475
476        let proxy = &proxy;
477
478        let accept_concurrent = AtomicBool::new(true);
479        let status = Mutex::new((WorkerStatus::Idle, Instant::now()));
480        let mut expiration = pin!(expiration);
481
482        let function = async |accessor: &Accessor<_>| {
483            let mut reuse_count = 0;
484            let mut may_accept = true;
485            let mut futures = FuturesUnordered::new();
486            let mut start_times = StartTimes::default();
487
488            let accept_request = |(request_data, request, tx): WorkerRequest<S>,
489                                  futures: &mut FuturesUnordered<_>,
490                                  start_times: &mut StartTimes,
491                                  reuse_count: &mut usize| {
492                // Set `accept_concurrent` to false, conservatively assuming
493                // that the new task will be CPU-bound, at least to begin with.
494                // Only once the `StoreContextMut::run_concurrent` event loop
495                // returns `Pending` will we set `accept_concurrent` back to
496                // true and consider accepting more requests.
497                //
498                // This approach avoids taking on more than one CPU-bound task
499                // at a time, which would hurt throughput vs. leaving the
500                // additional requests for other workers to handle.
501                accept_concurrent.store(false, Relaxed);
502                *reuse_count += 1;
503
504                let prepared = accessor.with(|mut store| {
505                    let prepared = Prepared::new(store.as_context_mut(), proxy, request, view, tx);
506                    match prepared {
507                        Ok(prepared) => {
508                            // Notify the `HandlerState` that we're starting to
509                            // handle a request and retrieve the deadline by
510                            // which it must produce a response.
511                            //
512                            // If it fails to produce a response by the
513                            // deadline, we'll stop accepting new requests and
514                            // eventually exit the worker.
515                            let expiration = dropper.state.on_request_start(
516                                store.as_context_mut(),
517                                request_data,
518                                prepared.group(),
519                            );
520                            Ok((prepared, expiration))
521                        }
522                        Err(e) => Err(e),
523                    }
524                });
525
526                let start_time = Instant::now();
527                start_times.add(start_time);
528                *status.try_lock().unwrap() = (WorkerStatus::Requests, start_time);
529
530                futures.push(async move {
531                    let (prepared, expiration) = prepared?;
532                    let sent = prepared.run(accessor, expiration).await?;
533                    wasmtime::error::Ok((sent, start_time))
534                });
535            };
536
537            if let Some(req) = request {
538                accept_request(req, &mut futures, &mut start_times, &mut reuse_count);
539            }
540
541            // This is the main driver loop for this worker. This is modeled as
542            // a `poll_fn` which internally loops around the possible events.
543            // Events are sourced from the locals here, pinned outside of the
544            // `poll_fn` closure.
545            let mut futures = pin!(futures);
546            let handler = self.handler.clone();
547            let mut incoming_requests = pin!(futures::stream::unfold(
548                &handler.0.request_queue,
549                |queue| async move {
550                    let pair = queue.pop().await;
551                    Some((pair, queue))
552                }
553            ));
554            let func = match proxy {
555                #[cfg(feature = "p3")]
556                Proxy::P3(guest) => *guest.wasi_http_handler().func_handle().func(),
557                #[cfg(feature = "p2")]
558                Proxy::P2(guest) => *guest.wasi_http_incoming_handler().func_handle().func(),
559            };
560            future::poll_fn(|cx| {
561                loop {
562                    // First, and crucially first, poll `futures`. This way
563                    // we'll discover any tasks that may have timed out, at
564                    // which point we'll stop accepting new tasks altogether
565                    // (see below for details). This is especially important in
566                    // the case where the task was blocked on a synchronous call
567                    // to a host function which has exclusive access to the
568                    // `Store`; once that call finishes, the first thing we need
569                    // to do is time out the task. If we were to poll for a new
570                    // task first, then we'd have to wait for _that_ task to
571                    // finish or time out before we could kill the instance.
572                    match futures.as_mut().poll_next(cx) {
573                        // A request either produced a response or expired.
574                        Poll::Ready(Some(Ok((responded, start_time)))) => {
575                            // Remove its start time from the map and update the
576                            // state.
577                            start_times.remove(start_time);
578                            *status.try_lock().unwrap() =
579                                if let Some(start_time) = start_times.most_recent() {
580                                    (WorkerStatus::Requests, start_time)
581                                } else {
582                                    (WorkerStatus::PostReturn, Instant::now())
583                                };
584
585                            if responded {
586                                // Response produced; carry on!
587                            } else {
588                                // Request expired; stop accepting new requests, but
589                                // continue polling until any other, in-progress
590                                // tasks until they have either finished or expired.
591                                // This effectively kicks off a "graceful shutdown"
592                                // of the worker, allowing any other concurrent
593                                // tasks time to finish before we drop the instance.
594                                may_accept = false;
595                            }
596                        }
597
598                        // Instance trapped.
599                        Poll::Ready(Some(Err(error))) => {
600                            break Poll::Ready(Err(error));
601                        }
602
603                        Poll::Ready(None) | Poll::Pending => {}
604                    }
605
606                    let is_ready = accessor.poll_ready_for_concurrent_call(func, cx).is_ready();
607
608                    // At this point `futures` is either empty or it's `Pending`
609                    // meaning nothing is ready. Note that `Pending` here
610                    // doesn't necessarily mean all tasks are blocked on I/O.
611                    // They might simply be waiting for some deferred work to be
612                    // done by the next turn of the
613                    // `StoreContextMut::run_concurrent` event loop.  Therefore,
614                    // we check `accept_concurrent` here and only advertise we
615                    // have capacity for another task if either we have no tasks
616                    // at all or all our tasks really are blocked on I/O.
617                    self.set_available(
618                        may_accept
619                            && is_ready
620                            && match dropper
621                                .state
622                                .should_accept_request(futures.len(), reuse_count)
623                            {
624                                ShouldAccept::Yes => {
625                                    futures.is_empty() || accept_concurrent.load(Relaxed)
626                                }
627                                ShouldAccept::No => false,
628                                ShouldAccept::Never => {
629                                    may_accept = false;
630                                    false
631                                }
632                            },
633                    );
634
635                    // If we're available for accepting more requests after the
636                    // deduction above, then try to accept a new task. If that's
637                    // successful then push it into `futures` and turn this loop
638                    // again to see where we're at next time around.
639                    if self.available
640                        && let Poll::Ready(Some(req)) = incoming_requests.as_mut().poll_next(cx)
641                    {
642                        accept_request(req, &mut futures, &mut start_times, &mut reuse_count);
643                        continue;
644                    }
645
646                    // If, at this point, we still have some requests that are
647                    // being processed then go ahead and bail out of this
648                    // singular call to `poll` by saying we're not ready yet.
649                    // This means we unconditionally wait for events within
650                    // `futures` and we're also registered, optionally, for
651                    // listening for incoming connections. That's all the events
652                    // we're interested in, so this iteration of `poll` is complete.
653                    if !futures.is_empty() {
654                        break Poll::Pending;
655                    }
656
657                    // At this point `futures` is empty, and we haven't gotten
658                    // any incoming tasks. Check the store we're using to see if
659                    // there are any "interesting" tasks around. These are tasks
660                    // which act as effectively strong references to this worker
661                    // to keep it running. If there are still interesting tasks,
662                    // then we're done with this iteration of `poll`. We'll get
663                    // woken up when anything changes, but otherwise it's time
664                    // to let something else happen.
665                    if accessor.poll_no_interesting_tasks(cx).is_pending() {
666                        break Poll::Pending;
667                    }
668
669                    // And now at this point we (a) have no `futures`, (b) no
670                    // new requests are available, and (c) the store is
671                    // completely devoid of interesting work. In this situation
672                    // if we're not actually capable of accepting any more work,
673                    // then we're completely done and it's time to exit this
674                    // worker.
675                    if !(may_accept && is_ready) {
676                        break Poll::Ready(Ok(()));
677                    }
678
679                    // Finally, at this point we're idle but still eligible to
680                    // accept new work, so update the state if appropriate and
681                    // then return pending while we wait for new work.
682                    {
683                        let mut status = status.try_lock().unwrap();
684                        if status.0 != WorkerStatus::Idle {
685                            *status = (WorkerStatus::Idle, Instant::now());
686                        }
687                    }
688                    break Poll::Pending;
689                }
690            })
691            .await
692        };
693
694        let result = {
695            let mut future = pin!(
696                dropper
697                    .store
698                    .as_mut()
699                    .unwrap()
700                    .run_concurrent(function)
701                    .map(|v| v.flatten())
702            );
703
704            future::poll_fn(|cx| {
705                let poll = dropper.state.poll(future.as_mut(), cx);
706                if poll.is_pending() {
707                    // If the future returns `Pending`, that's either because it's
708                    // idle (in which case it can definitely accept a new request) or
709                    // because all its tasks are awaiting I/O, in which case it may
710                    // have capacity for additional tasks to run concurrently.
711                    //
712                    // However, per #11869 and #11870, if one of the tasks is
713                    // blocked on a sync call to a host function which has exclusive
714                    // access to the `Store`, the `StoreContextMut::run_concurrent`
715                    // event loop will be unable to make progress until that call
716                    // finishes.  Similarly, if the task loops indefinitely, subject
717                    // only to epoch interruption, the event loop will also be
718                    // stuck.  Either way, any request expirations created inside
719                    // the `AsyncFnOnce` we passed to `run_concurrent` won't have a
720                    // chance to trigger.  Consequently, we poll for instance
721                    // expiration here, outside the event loop, based on the most
722                    // recently recorded state of the worker.
723
724                    let (status, start) = *status.try_lock().unwrap();
725
726                    if let Poll::Ready(()) = expiration.as_mut().poll(cx, status, start) {
727                        return Poll::Ready(match status {
728                            WorkerStatus::Requests | WorkerStatus::PostReturn => {
729                                Err(format_err!("guest timed out"))
730                            }
731                            WorkerStatus::Idle => Ok(()),
732                        });
733                    }
734
735                    // Otherwise, if the instance has not yet expired, we set
736                    // `accept_concurrent` to true and, if it wasn't already true
737                    // before, poll the future one more time so it can ask for
738                    // another request if appropriate.
739                    if !accept_concurrent.swap(true, Relaxed) {
740                        return dropper.state.poll(future.as_mut(), cx);
741                    }
742                }
743
744                poll
745            })
746            .await
747        };
748
749        dropper.state.drop(dropper.store.take().unwrap(), result);
750    }
751}
752
753impl<S> Drop for Worker<S>
754where
755    S: HandlerState,
756{
757    fn drop(&mut self) {
758        self.set_available(false);
759    }
760}
761
762/// Represents the state of a web server.
763///
764/// Note that this supports optional instance reuse, enabled when
765/// `S::WorkerState::should_accept_request` returns [`ShouldAccept::Yes`] more
766/// than once for a given instance.  See [`WorkerState`] for details.
767pub struct ProxyHandler<S: HandlerState>(Arc<ProxyHandlerInner<S>>);
768
769impl<S: HandlerState> Clone for ProxyHandler<S> {
770    fn clone(&self) -> Self {
771        Self(self.0.clone())
772    }
773}
774
775/// This error is returned if, when handling the request, a new worker and
776/// associated instance needed to be created, but instantiation failed, e.g. due
777/// to reaching a pooling allocator limit or running out of memory.  In this
778/// case, the caller may be able to recover and retry (e.g. after waiting for
779/// existing instances to be dropped and/or freeing memory used by caches,
780/// etc.).  Otherwise, it will probably need to return an HTTP 500 error.
781pub struct InstantiationError<T> {
782    /// The host data originally passed with the request.
783    pub request_data: T,
784    /// The original request passed to `ProxyHandler::handle`.
785    ///
786    /// This is wrapped in a `Mutex` to satisfy the `Send + Sync` bounds
787    /// required by `wasmtime::Error`.
788    pub request: Mutex<Request>,
789    /// The original instantiation error.
790    ///
791    /// This is wrapped in an `Arc` because a single instantiation error may
792    /// affect multiple requests, and each caller will be given a clone.
793    pub error: Arc<wasmtime::Error>,
794}
795
796impl<T> fmt::Display for InstantiationError<T> {
797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
798        write!(f, "instantiation error: {}", self.error)
799    }
800}
801
802impl<T> fmt::Debug for InstantiationError<T> {
803    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
804        write!(f, "instantiation error: {:?}", self.error)
805    }
806}
807
808impl<T> error::Error for InstantiationError<T> {}
809
810/// Returned when the guest failed to produce a response before the expiration
811/// returned by `HandlerState::on_request_start` elapsed.
812pub struct ExpirationError;
813
814impl fmt::Display for ExpirationError {
815    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
816        fmt::Debug::fmt(self, f)
817    }
818}
819
820impl fmt::Debug for ExpirationError {
821    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
822        write!(f, "guest timed out")
823    }
824}
825
826impl error::Error for ExpirationError {}
827
828/// A worker trapped or panicked and failed to produce a result.
829pub struct TrapOrPanicError;
830
831impl fmt::Display for TrapOrPanicError {
832    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
833        fmt::Debug::fmt(self, f)
834    }
835}
836
837impl fmt::Debug for TrapOrPanicError {
838    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
839        write!(f, "worker trapped or panicked")
840    }
841}
842
843impl error::Error for TrapOrPanicError {}
844
845impl<S> ProxyHandler<S>
846where
847    S: HandlerState,
848{
849    /// Create a new `ProxyHandler` with the specified application state and
850    /// pre-instance.
851    pub fn new(state: S) -> Self {
852        Self(Arc::new(ProxyHandlerInner {
853            state,
854            request_queue: Default::default(),
855            worker_count: AtomicUsize::from(0),
856        }))
857    }
858
859    /// Handle the specified request, returning a response on success or the
860    /// tuple of the request and error on failure.
861    ///
862    /// This function will return a `wasmtime::Error` on failure, which may be
863    /// downcast to a more specific type in certain scenarios:
864    ///
865    /// - [`InstantiationError`] if a new worker was created to handle the
866    /// request but could not instantiate the guest component.
867    ///
868    /// - [`ExpirationError`] if the request expired before it produced a
869    /// response.  See [`WorkerState::on_request_start`] for details.
870    ///
871    /// - [`ErrorResponse`](crate::ErrorResponse) if the request could not be
872    /// converted into a guest request.
873    ///
874    /// - [`TrapOrPanicError`] if the worker responsible for handling the
875    /// request trapped or panicked before it produced a response.  This may be
876    /// used when a trap occurs but cannot be traced to a specific request,
877    /// e.g. during concurrent request handling.
878    ///
879    /// In other failure cases (e.g. `wasi:http/types#error-code` return values
880    /// and/or traps when executing synchronous WASIp2 handler functions), the
881    /// original error returned by the handler will be returned.
882    ///
883    /// # Backpressure
884    ///
885    /// Note that this API does not implement any form of backpressure to limit
886    /// the number of in-flight `Request`s being processed. This function
887    /// may spawn new tokio tasks, instantiate new modules under new stores, and
888    /// queue up pending `Request`s while waiting for previous instances. In all
889    /// of these situations invoking this function will consume some host-side
890    /// resources until the request is done.
891    ///
892    /// Embedders using this API must ensure to take this into account. If an
893    /// infinite number of requests can be fed into this function then it's
894    /// recommended to take a semaphore, for example, around this function call
895    /// to limit the number of concurrent requests that are being processed.
896    pub async fn handle(
897        &self,
898        data: <S::WorkerState as WorkerState>::RequestData,
899        request: Request,
900    ) -> Result<Response, wasmtime::Error> {
901        let (tx, rx) = oneshot::channel();
902        let req = (data, request, tx);
903        if self.0.worker_count.load(Relaxed) == 0 {
904            // There are no available workers; skip the queue and pass
905            // the request directly to the worker, which improves
906            // performance as measured by `wasmtime-server-rps.sh` by
907            // about 15%.
908            self.start_worker(Some(req));
909        } else {
910            let mut queue = self.0.request_queue.queue.lock().unwrap();
911            queue.push_back(req);
912
913            // Start a new worker to handle the request if the last worker just
914            // went unavailable.  See also `Worker::set_available` for what
915            // happens if the available worker count goes to zero right after we
916            // check it here, and note that we only check the count _after_
917            // we've pushed the request to the queue.
918            //
919            // The upshot is that at least one (or more) of the
920            // following will happen:
921            //
922            // - An existing worker will accept the request
923            // - We'll start a new worker here to accept the request
924            // - `Worker::set_available` will start a new worker to accept the request
925            //
926            // I.e. it should not be possible for the request to be orphaned
927            // indefinitely in the queue without being accepted except in the
928            // case of a panic or an instantiation error.  In the case of an
929            // instantiation error, we'll give the request back to the caller in
930            // an `Err(_)`, allowing the application to decide what to do next.
931            if self.0.worker_count.load(Relaxed) == 0 {
932                let req = queue.pop_back().unwrap();
933                drop(queue);
934                self.start_worker(Some(req));
935            } else {
936                drop(queue);
937                self.0.request_queue.notify_push.notify_one();
938            }
939        }
940
941        rx.await.map_err(|_| TrapOrPanicError)?
942    }
943
944    /// Return a reference to the application state.
945    pub fn state(&self) -> &S {
946        &self.0.state
947    }
948
949    fn start_worker(&self, request: Option<WorkerRequest<S>>) {
950        tokio::spawn(
951            Worker {
952                handler: self.clone(),
953                available: false,
954            }
955            .run(request),
956        );
957    }
958}
959
960/// Representation of a "prepared" call for a guest, used to extract the
961/// `TaskGroupId` before actually executing any handlers.
962///
963/// Right now this is a bit gross since it has to type out a bunch of types by
964/// hand.
965pub enum Prepared<'a, T: 'static> {
966    #[doc(hidden)]
967    #[cfg(feature = "p2")]
968    P2 {
969        guest: &'a p2::bindings::Proxy,
970        call: TypedFuncCallConcurrent<
971            T,
972            (
973                Resource<p2_types::IncomingRequest>,
974                Resource<p2_types::ResponseOutparam>,
975            ),
976            (),
977        >,
978        tx: Arc<Mutex<Option<oneshot::Sender<Result<Response, wasmtime::Error>>>>>,
979    },
980    #[doc(hidden)]
981    #[cfg(feature = "p3")]
982    P3 {
983        guest: &'a p3::bindings::Service,
984        call: TypedFuncCallConcurrent<
985            T,
986            (Resource<p3_types::Request>,),
987            (Result<Resource<p3_types::Response>, p3_types::ErrorCode>,),
988        >,
989        tx: oneshot::Sender<Result<Response, wasmtime::Error>>,
990        request_io_result: Pin<Box<dyn Future<Output = Result<(), crate::Error>> + Send>>,
991        view: fn(&mut T) -> crate::WasiHttpCtxView,
992    },
993}
994
995impl<'a, T: Send> Prepared<'a, T> {
996    /// Creates a new prepared request.
997    pub fn new(
998        mut store: StoreContextMut<'_, T>,
999        proxy: &'a Proxy,
1000        request: Request,
1001        view: fn(&mut T) -> WasiHttpCtxView<'_>,
1002        tx: oneshot::Sender<Result<Response, wasmtime::Error>>,
1003    ) -> Result<Prepared<'a, T>> {
1004        match proxy {
1005            #[cfg(feature = "p3")]
1006            Proxy::P3(guest) => {
1007                use http::header::HOST;
1008                use http::uri::Authority;
1009
1010                let (parts, body) = request.into_parts();
1011                let http::request::Parts {
1012                    method,
1013                    uri,
1014                    headers: request_headers,
1015                    ..
1016                } = parts;
1017                let http::uri::Parts {
1018                    authority,
1019                    path_and_query,
1020                    ..
1021                } = uri.into_parts();
1022                // HTTP/1.1 requires an authority in the request target or the
1023                // `Host` header (RFC 9112 section 3.2.2), so fall back to the latter.
1024                let authority = match authority {
1025                    Some(authority) => Some(authority),
1026                    None => request_headers
1027                        .get(HOST)
1028                        .and_then(|host| host.to_str().ok())
1029                        .and_then(|host| host.parse::<Authority>().ok()),
1030                };
1031                let hooks = view(store.data_mut()).hooks;
1032                let headers = crate::FieldMap::new_immutable(hooks, request_headers);
1033                // `serve` speaks plaintext HTTP, so that is the scheme of every
1034                // request, whatever scheme its request target names (as in p2).
1035                let (request, request_io_result) = p3::Request::new(
1036                    method,
1037                    Some(http::uri::Scheme::HTTP),
1038                    authority,
1039                    path_and_query,
1040                    headers,
1041                    None,
1042                    body,
1043                );
1044                let request = view(store.data_mut()).table.push(request)?;
1045
1046                Ok(Prepared::P3 {
1047                    tx,
1048                    request_io_result: Box::pin(request_io_result),
1049                    guest,
1050                    view,
1051                    call: guest
1052                        .wasi_http_handler()
1053                        .func_handle()
1054                        .start_call_concurrent(store, (request,))?,
1055                })
1056            }
1057            #[cfg(feature = "p2")]
1058            Proxy::P2(guest) => {
1059                // Here we wrap the sender in an `Arc<Mutex<Option<_>>>`, with one
1060                // clone used in the `response-outparam` and the other used to send
1061                // an error if the request expires or the handler returns without
1062                // producing a response.
1063                let tx = Arc::new(Mutex::new(Some(tx)));
1064
1065                let request = match view(store.data_mut())
1066                    .new_incoming_request(p2_types::Scheme::Http, request)
1067                {
1068                    Ok(request) => request,
1069                    Err(e) => {
1070                        // The request never reached the guest, so there is no
1071                        // `response-outparam` to report the failure with, and
1072                        // `tx` would otherwise be dropped along with the error.
1073                        if let Some(tx) = tx.lock().unwrap().take() {
1074                            _ = tx.send(Err(e));
1075                        }
1076                        wasmtime::bail!(
1077                            "request was rejected before it could be turned into a guest request"
1078                        );
1079                    }
1080                };
1081
1082                let out = view(store.data_mut()).new_response_outparam_from_callback({
1083                    let tx = tx.clone();
1084                    move |value| {
1085                        if let Some(tx) = tx.lock().unwrap().take() {
1086                            _ = tx.send(value.map_err(|e| e.into()));
1087                        }
1088                    }
1089                })?;
1090
1091                Ok(Prepared::P2 {
1092                    guest,
1093                    tx,
1094                    call: guest
1095                        .wasi_http_incoming_handler()
1096                        .func_handle()
1097                        .start_call_concurrent(store, (request, out))?,
1098                })
1099            }
1100        }
1101    }
1102
1103    fn group(&self) -> TaskGroupId {
1104        match self {
1105            #[cfg(feature = "p3")]
1106            Prepared::P3 { call, .. } => call.group(),
1107            #[cfg(feature = "p2")]
1108            Prepared::P2 { call, .. } => call.group(),
1109        }
1110    }
1111
1112    /// Executes this request to completion.
1113    pub async fn run(
1114        self,
1115        accessor: &Accessor<T>,
1116        expiration: impl Future<Output = ()>,
1117    ) -> Result<bool> {
1118        let expiration = pin!(expiration);
1119
1120        match self {
1121            #[cfg(feature = "p3")]
1122            Prepared::P3 {
1123                guest,
1124                call,
1125                tx,
1126                request_io_result,
1127                view,
1128            } => {
1129                let handle = pin!(async move {
1130                    let response = guest
1131                        .wasi_http_handler()
1132                        .func_handle()
1133                        .finish_call_concurrent(accessor, call)
1134                        .await?
1135                        .0?;
1136
1137                    accessor.with(|mut store| {
1138                        let response = view(store.get()).table.delete(response)?;
1139                        response.into_http_with_getter(&mut store, request_io_result, view)
1140                    })
1141                });
1142
1143                // TODO: We should also use `oneshot::Sender::poll_close` to be
1144                // notified when the receiver is dropped, in which case we should
1145                // expire the request since the response is no longer of interest to
1146                // the original `ProxyHandler::handle` caller.
1147                let (result, sent) = match futures::future::select(handle, expiration).await {
1148                    Either::Left((result, _)) => (result, true),
1149                    // TODO: We should also send a cancel request to the expired
1150                    // task to give it a chance to shut down gracefully, but as of
1151                    // this writing Wasmtime does not yet provide an API for doing
1152                    // that.  See issue #11833.  Instead, we let it continue running
1153                    // as a background task until it either returns a response
1154                    // (which we'll ignore) or the instance itself has expired.
1155                    Either::Right(((), _)) => (Err(ExpirationError.into()), false),
1156                };
1157
1158                _ = tx.send(result);
1159
1160                Ok(sent)
1161            }
1162            #[cfg(feature = "p2")]
1163            Prepared::P2 { guest, call, tx } => {
1164                let handle = pin!(
1165                    guest
1166                        .wasi_http_incoming_handler()
1167                        .func_handle()
1168                        .finish_call_concurrent(accessor, call)
1169                );
1170
1171                const MESSAGE: &str = "guest never invoked `response-outparam::set` method";
1172
1173                struct Dropper(
1174                    Arc<Mutex<Option<oneshot::Sender<Result<Response, wasmtime::Error>>>>>,
1175                );
1176
1177                impl Drop for Dropper {
1178                    fn drop(&mut self) {
1179                        if let Some(tx) = self.0.lock().unwrap().take() {
1180                            _ = tx.send(Err(format_err!("{MESSAGE}")));
1181                        }
1182                    }
1183                }
1184
1185                let tx = Dropper(tx);
1186
1187                // See corresponding TODO comment for the p3 case above.
1188                let (result, sent) = match futures::future::select(handle, expiration).await {
1189                    Either::Left((result, _)) => (result.context(MESSAGE), true),
1190                    // See corresponding TODO comment for the p3 case above.
1191                    Either::Right(((), _)) => (Err(ExpirationError.into()), false),
1192                };
1193
1194                if let Some(tx) = tx.0.lock().unwrap().take() {
1195                    _ = tx.send(result.and_then(|()| Err(format_err!("{MESSAGE}"))));
1196                }
1197
1198                Ok(sent)
1199            }
1200        }
1201    }
1202}