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