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