1dcd65446SJoel Dice //! Provides utilities useful for dispatching incoming HTTP requests
2dcd65446SJoel Dice //! `wasi:http/handler` guest instances.
3dcd65446SJoel Dice 
4dcd65446SJoel Dice #[cfg(feature = "p3")]
5dcd65446SJoel Dice use crate::p3;
6dcd65446SJoel Dice use futures::stream::{FuturesUnordered, StreamExt};
7dcd65446SJoel Dice use std::collections::VecDeque;
8dcd65446SJoel Dice use std::collections::btree_map::{BTreeMap, Entry};
9dcd65446SJoel Dice use std::future;
10dcd65446SJoel Dice use std::pin::{Pin, pin};
11dcd65446SJoel Dice use std::sync::{
12dcd65446SJoel Dice     Arc, Mutex,
13dcd65446SJoel Dice     atomic::{
14dcd65446SJoel Dice         AtomicBool, AtomicU64, AtomicUsize,
15dcd65446SJoel Dice         Ordering::{Relaxed, SeqCst},
16dcd65446SJoel Dice     },
17dcd65446SJoel Dice };
18dcd65446SJoel Dice use std::task::Poll;
19dcd65446SJoel Dice use std::time::{Duration, Instant};
20dcd65446SJoel Dice use tokio::sync::Notify;
21dcd65446SJoel Dice use wasmtime::AsContextMut;
22dcd65446SJoel Dice use wasmtime::component::Accessor;
2385d8cc06SNick Fitzgerald use wasmtime::{Result, Store, StoreContextMut, format_err};
24dcd65446SJoel Dice 
25dcd65446SJoel Dice /// Alternative p2 bindings generated with `exports: { default: async | store }`
26dcd65446SJoel Dice /// so we can use `TypedFunc::call_concurrent` with both p2 and p3 instances.
27365e2d89SAlex Crichton #[cfg(feature = "p2")]
28dcd65446SJoel Dice pub mod p2 {
29dcd65446SJoel Dice     #[expect(missing_docs, reason = "bindgen-generated code")]
30dcd65446SJoel Dice     pub mod bindings {
31dcd65446SJoel Dice         wasmtime::component::bindgen!({
32dcd65446SJoel Dice             path: "wit",
33dcd65446SJoel Dice             world: "wasi:http/proxy",
34dcd65446SJoel Dice             imports: { default: tracing },
35dcd65446SJoel Dice             exports: { default: async | store },
36dcd65446SJoel Dice             require_store_data_send: true,
37dcd65446SJoel Dice             with: {
38dcd65446SJoel Dice                 // http is in this crate
39365e2d89SAlex Crichton                 "wasi:http": crate::p2::bindings::http,
40dcd65446SJoel Dice                 // Upstream package dependencies
41dcd65446SJoel Dice                 "wasi:io": wasmtime_wasi::p2::bindings::io,
42dcd65446SJoel Dice             }
43dcd65446SJoel Dice         });
44dcd65446SJoel Dice 
45dcd65446SJoel Dice         pub use wasi::*;
46dcd65446SJoel Dice     }
47dcd65446SJoel Dice }
48dcd65446SJoel Dice 
49dcd65446SJoel Dice /// Represents either a `wasi:http/incoming-handler@0.2.x` or
50dcd65446SJoel Dice /// `wasi:http/handler@0.3.x` pre-instance.
51dcd65446SJoel Dice pub enum ProxyPre<T: 'static> {
52dcd65446SJoel Dice     /// A `wasi:http/incoming-handler@0.2.x` pre-instance.
53365e2d89SAlex Crichton     #[cfg(feature = "p2")]
54dcd65446SJoel Dice     P2(p2::bindings::ProxyPre<T>),
55dcd65446SJoel Dice     /// A `wasi:http/handler@0.3.x` pre-instance.
56dcd65446SJoel Dice     #[cfg(feature = "p3")]
571cc0bcffSBailey Hayes     P3(p3::bindings::ServicePre<T>),
58dcd65446SJoel Dice }
59dcd65446SJoel Dice 
60dcd65446SJoel Dice impl<T: 'static> ProxyPre<T> {
instantiate_async(&self, store: impl AsContextMut<Data = T>) -> Result<Proxy> where T: Send,61dcd65446SJoel Dice     async fn instantiate_async(&self, store: impl AsContextMut<Data = T>) -> Result<Proxy>
62dcd65446SJoel Dice     where
63dcd65446SJoel Dice         T: Send,
64dcd65446SJoel Dice     {
65dcd65446SJoel Dice         Ok(match self {
66365e2d89SAlex Crichton             #[cfg(feature = "p2")]
67dcd65446SJoel Dice             Self::P2(pre) => Proxy::P2(pre.instantiate_async(store).await?),
68dcd65446SJoel Dice             #[cfg(feature = "p3")]
69dcd65446SJoel Dice             Self::P3(pre) => Proxy::P3(pre.instantiate_async(store).await?),
70dcd65446SJoel Dice         })
71dcd65446SJoel Dice     }
72dcd65446SJoel Dice }
73dcd65446SJoel Dice 
74dcd65446SJoel Dice /// Represents either a `wasi:http/incoming-handler@0.2.x` or
75dcd65446SJoel Dice /// `wasi:http/handler@0.3.x` instance.
76dcd65446SJoel Dice pub enum Proxy {
77dcd65446SJoel Dice     /// A `wasi:http/incoming-handler@0.2.x` instance.
78365e2d89SAlex Crichton     #[cfg(feature = "p2")]
79dcd65446SJoel Dice     P2(p2::bindings::Proxy),
80dcd65446SJoel Dice     /// A `wasi:http/handler@0.3.x` instance.
81dcd65446SJoel Dice     #[cfg(feature = "p3")]
821cc0bcffSBailey Hayes     P3(p3::bindings::Service),
83dcd65446SJoel Dice }
84dcd65446SJoel Dice 
85dcd65446SJoel Dice /// Represents a task to run using a `wasi:http/incoming-handler@0.2.x` or
86dcd65446SJoel Dice /// `wasi:http/handler@0.3.x` instance.
87dcd65446SJoel Dice pub type TaskFn<T> = Box<
88dcd65446SJoel Dice     dyn for<'a> FnOnce(&'a Accessor<T>, &'a Proxy) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
89dcd65446SJoel Dice         + Send,
90dcd65446SJoel Dice >;
91dcd65446SJoel Dice 
92dcd65446SJoel Dice /// Async MPMC channel where each item is delivered to at most one consumer.
93dcd65446SJoel Dice struct Queue<T> {
94dcd65446SJoel Dice     queue: Mutex<VecDeque<T>>,
95dcd65446SJoel Dice     notify: Notify,
96dcd65446SJoel Dice }
97dcd65446SJoel Dice 
98dcd65446SJoel Dice impl<T> Default for Queue<T> {
default() -> Self99dcd65446SJoel Dice     fn default() -> Self {
100dcd65446SJoel Dice         Self {
101dcd65446SJoel Dice             queue: Default::default(),
102dcd65446SJoel Dice             notify: Default::default(),
103dcd65446SJoel Dice         }
104dcd65446SJoel Dice     }
105dcd65446SJoel Dice }
106dcd65446SJoel Dice 
107dcd65446SJoel Dice impl<T> Queue<T> {
is_empty(&self) -> bool108dcd65446SJoel Dice     fn is_empty(&self) -> bool {
109dcd65446SJoel Dice         self.queue.lock().unwrap().is_empty()
110dcd65446SJoel Dice     }
111dcd65446SJoel Dice 
push(&self, item: T)112dcd65446SJoel Dice     fn push(&self, item: T) {
113dcd65446SJoel Dice         self.queue.lock().unwrap().push_back(item);
114dcd65446SJoel Dice         self.notify.notify_one();
115dcd65446SJoel Dice     }
116dcd65446SJoel Dice 
try_pop(&self) -> Option<T>117dcd65446SJoel Dice     fn try_pop(&self) -> Option<T> {
118dcd65446SJoel Dice         self.queue.lock().unwrap().pop_front()
119dcd65446SJoel Dice     }
120dcd65446SJoel Dice 
pop(&self) -> T121dcd65446SJoel Dice     async fn pop(&self) -> T {
122dcd65446SJoel Dice         // This code comes from the Unbound MPMC Channel example in [the
123dcd65446SJoel Dice         // `tokio::sync::Notify`
124dcd65446SJoel Dice         // docs](https://docs.rs/tokio/latest/tokio/sync/struct.Notify.html).
125dcd65446SJoel Dice 
126dcd65446SJoel Dice         let mut notified = pin!(self.notify.notified());
127dcd65446SJoel Dice 
128dcd65446SJoel Dice         loop {
129dcd65446SJoel Dice             notified.as_mut().enable();
130dcd65446SJoel Dice             if let Some(item) = self.try_pop() {
131dcd65446SJoel Dice                 return item;
132dcd65446SJoel Dice             }
133dcd65446SJoel Dice             notified.as_mut().await;
134dcd65446SJoel Dice             notified.set(self.notify.notified());
135dcd65446SJoel Dice         }
136dcd65446SJoel Dice     }
137dcd65446SJoel Dice }
138dcd65446SJoel Dice 
139dcd65446SJoel Dice /// Bundles a [`Store`] with a callback to write a profile (if configured).
140dcd65446SJoel Dice pub struct StoreBundle<T: 'static> {
141dcd65446SJoel Dice     /// The [`Store`] to use to handle requests.
142dcd65446SJoel Dice     pub store: Store<T>,
143dcd65446SJoel Dice     /// Callback to write a profile (if enabled) once all requests have been
144dcd65446SJoel Dice     /// handled.
145dcd65446SJoel Dice     pub write_profile: Box<dyn FnOnce(StoreContextMut<T>) + Send>,
146dcd65446SJoel Dice }
147dcd65446SJoel Dice 
148dcd65446SJoel Dice /// Represents the application-specific state of a web server.
149dcd65446SJoel Dice pub trait HandlerState: 'static + Sync + Send {
150dcd65446SJoel Dice     /// The type of the associated data for [`Store`]s created using
151bc4582c3SAlex Crichton     /// [`Self::new_store`].
152dcd65446SJoel Dice     type StoreData: Send;
153dcd65446SJoel Dice 
154dcd65446SJoel Dice     /// Create a new [`Store`] for handling one or more requests.
155dcd65446SJoel Dice     ///
156dcd65446SJoel Dice     /// The `req_id` parameter is the value passed in the call to
157dcd65446SJoel Dice     /// [`ProxyHandler::spawn`] that created the worker to which the new `Store`
158dcd65446SJoel Dice     /// will belong.  See that function's documentation for details.
new_store(&self, req_id: Option<u64>) -> Result<StoreBundle<Self::StoreData>>159dcd65446SJoel Dice     fn new_store(&self, req_id: Option<u64>) -> Result<StoreBundle<Self::StoreData>>;
160dcd65446SJoel Dice 
161dcd65446SJoel Dice     /// Maximum time allowed to handle a request.
162dcd65446SJoel Dice     ///
163dcd65446SJoel Dice     /// In practice, a guest may be allowed to run up to 2x this time in the
164dcd65446SJoel Dice     /// case of instance reuse to avoid penalizing concurrent requests being
165dcd65446SJoel Dice     /// handled by the same instance.
request_timeout(&self) -> Duration166dcd65446SJoel Dice     fn request_timeout(&self) -> Duration;
167dcd65446SJoel Dice 
168dcd65446SJoel Dice     /// Maximum time to keep an idle instance around before dropping it.
idle_instance_timeout(&self) -> Duration169dcd65446SJoel Dice     fn idle_instance_timeout(&self) -> Duration;
170dcd65446SJoel Dice 
171dcd65446SJoel Dice     /// Maximum number of requests to handle using a single instance before
172dcd65446SJoel Dice     /// dropping it.
max_instance_reuse_count(&self) -> usize173dcd65446SJoel Dice     fn max_instance_reuse_count(&self) -> usize;
174dcd65446SJoel Dice 
175dcd65446SJoel Dice     /// Maximum number of requests to handle concurrently using a single
176dcd65446SJoel Dice     /// instance.
max_instance_concurrent_reuse_count(&self) -> usize177dcd65446SJoel Dice     fn max_instance_concurrent_reuse_count(&self) -> usize;
178dcd65446SJoel Dice 
179dcd65446SJoel Dice     /// Called when a worker exits with an error.
handle_worker_error(&self, error: wasmtime::Error)18085d8cc06SNick Fitzgerald     fn handle_worker_error(&self, error: wasmtime::Error);
181dcd65446SJoel Dice }
182dcd65446SJoel Dice 
183dcd65446SJoel Dice struct ProxyHandlerInner<S: HandlerState> {
184dcd65446SJoel Dice     state: S,
185dcd65446SJoel Dice     instance_pre: ProxyPre<S::StoreData>,
186dcd65446SJoel Dice     next_id: AtomicU64,
187dcd65446SJoel Dice     task_queue: Queue<TaskFn<S::StoreData>>,
188dcd65446SJoel Dice     worker_count: AtomicUsize,
189dcd65446SJoel Dice }
190dcd65446SJoel Dice 
191dcd65446SJoel Dice /// Helper utility to track the start times of tasks accepted by a worker.
192dcd65446SJoel Dice ///
193dcd65446SJoel Dice /// This is used to ensure that timeouts are enforced even when the
194dcd65446SJoel Dice /// `StoreContextMut::run_concurrent` event loop is unable to make progress due
195dcd65446SJoel Dice /// to the guest either busy looping or being blocked on a synchronous call to a
196dcd65446SJoel Dice /// host function which has exclusive access to the `Store`.
197dcd65446SJoel Dice #[derive(Default)]
198dcd65446SJoel Dice struct StartTimes(BTreeMap<Instant, usize>);
199dcd65446SJoel Dice 
200dcd65446SJoel Dice impl StartTimes {
add(&mut self, time: Instant)201dcd65446SJoel Dice     fn add(&mut self, time: Instant) {
202dcd65446SJoel Dice         *self.0.entry(time).or_insert(0) += 1;
203dcd65446SJoel Dice     }
204dcd65446SJoel Dice 
remove(&mut self, time: Instant)205dcd65446SJoel Dice     fn remove(&mut self, time: Instant) {
206dcd65446SJoel Dice         let Entry::Occupied(mut entry) = self.0.entry(time) else {
207dcd65446SJoel Dice             unreachable!()
208dcd65446SJoel Dice         };
209dcd65446SJoel Dice         match *entry.get() {
210dcd65446SJoel Dice             0 => unreachable!(),
211dcd65446SJoel Dice             1 => {
212dcd65446SJoel Dice                 entry.remove();
213dcd65446SJoel Dice             }
214dcd65446SJoel Dice             _ => {
215dcd65446SJoel Dice                 *entry.get_mut() -= 1;
216dcd65446SJoel Dice             }
217dcd65446SJoel Dice         }
218dcd65446SJoel Dice     }
219dcd65446SJoel Dice 
earliest(&self) -> Option<Instant>220dcd65446SJoel Dice     fn earliest(&self) -> Option<Instant> {
221dcd65446SJoel Dice         self.0.first_key_value().map(|(&k, _)| k)
222dcd65446SJoel Dice     }
223dcd65446SJoel Dice }
224dcd65446SJoel Dice 
225dcd65446SJoel Dice struct Worker<S>
226dcd65446SJoel Dice where
227dcd65446SJoel Dice     S: HandlerState,
228dcd65446SJoel Dice {
229dcd65446SJoel Dice     handler: ProxyHandler<S>,
230dcd65446SJoel Dice     available: bool,
231dcd65446SJoel Dice }
232dcd65446SJoel Dice 
233dcd65446SJoel Dice impl<S> Worker<S>
234dcd65446SJoel Dice where
235dcd65446SJoel Dice     S: HandlerState,
236dcd65446SJoel Dice {
set_available(&mut self, available: bool)237dcd65446SJoel Dice     fn set_available(&mut self, available: bool) {
238dcd65446SJoel Dice         if available != self.available {
239dcd65446SJoel Dice             self.available = available;
240dcd65446SJoel Dice             if available {
241dcd65446SJoel Dice                 self.handler.0.worker_count.fetch_add(1, Relaxed);
242dcd65446SJoel Dice             } else {
243dcd65446SJoel Dice                 // Here we use `SeqCst` to ensure the load/store is ordered
244dcd65446SJoel Dice                 // correctly with respect to the `Queue::is_empty` check we do
245dcd65446SJoel Dice                 // below.
246dcd65446SJoel Dice                 let count = self.handler.0.worker_count.fetch_sub(1, SeqCst);
247dcd65446SJoel Dice                 // This addresses what would otherwise be a race condition in
248dcd65446SJoel Dice                 // `ProxyHandler::spawn` where it only starts a worker if the
249dcd65446SJoel Dice                 // available worker count is zero.  If we decrement the count to
250dcd65446SJoel Dice                 // zero right after `ProxyHandler::spawn` checks it, then no
251dcd65446SJoel Dice                 // worker will be started; thus it becomes our responsibility to
252dcd65446SJoel Dice                 // start a worker here instead.
253dcd65446SJoel Dice                 if count == 1 && !self.handler.0.task_queue.is_empty() {
254dcd65446SJoel Dice                     self.handler.start_worker(None, None);
255dcd65446SJoel Dice                 }
256dcd65446SJoel Dice             }
257dcd65446SJoel Dice         }
258dcd65446SJoel Dice     }
259dcd65446SJoel Dice 
run(mut self, task: Option<TaskFn<S::StoreData>>, req_id: Option<u64>)260dcd65446SJoel Dice     async fn run(mut self, task: Option<TaskFn<S::StoreData>>, req_id: Option<u64>) {
261dcd65446SJoel Dice         if let Err(error) = self.run_(task, req_id).await {
262dcd65446SJoel Dice             self.handler.0.state.handle_worker_error(error);
263dcd65446SJoel Dice         }
264dcd65446SJoel Dice     }
265dcd65446SJoel Dice 
run_( &mut self, task: Option<TaskFn<S::StoreData>>, req_id: Option<u64>, ) -> Result<()>266dcd65446SJoel Dice     async fn run_(
267dcd65446SJoel Dice         &mut self,
268dcd65446SJoel Dice         task: Option<TaskFn<S::StoreData>>,
269dcd65446SJoel Dice         req_id: Option<u64>,
270dcd65446SJoel Dice     ) -> Result<()> {
271dcd65446SJoel Dice         // NB: The code the follows is rather subtle in that it is structured
272dcd65446SJoel Dice         // carefully to provide a few key invariants related to how instance
273dcd65446SJoel Dice         // reuse and request timeouts interact:
274dcd65446SJoel Dice         //
275dcd65446SJoel Dice         // - A task must never be allowed to run for more than 2x the request
276dcd65446SJoel Dice         // timeout, if any.
277dcd65446SJoel Dice         //
278dcd65446SJoel Dice         // - Every task we accept here must be allowed to run for at least 1x
279dcd65446SJoel Dice         // the request timeout, if any.
280dcd65446SJoel Dice         //
281dcd65446SJoel Dice         // - When more than one task is run concurrently in the same instance,
282dcd65446SJoel Dice         // we must stop accepting new tasks as soon as any existing task reaches
283dcd65446SJoel Dice         // the request timeout.  This serves to cap the amount of time we need
284dcd65446SJoel Dice         // to keep the instance alive before _all_ tasks have either completed
285dcd65446SJoel Dice         // or timed out.
286dcd65446SJoel Dice         //
287dcd65446SJoel Dice         // As of this writing, there's an additional wrinkle that makes
288dcd65446SJoel Dice         // guaranteeing those invariants particularly tricky: per #11869 and
289dcd65446SJoel Dice         // #11870, busy guest loops, epoch interruption, and host functions
290dcd65446SJoel Dice         // registered using `Linker::func_{wrap,new}_async` all require
291dcd65446SJoel Dice         // blocking, exclusive access to the `Store`, which effectively prevents
292dcd65446SJoel Dice         // the `StoreContextMut::run_concurrent` event loop from making
293dcd65446SJoel Dice         // progress.  That, in turn, prevents any concurrent tasks from
294dcd65446SJoel Dice         // executing, and also prevents the `AsyncFnOnce` passed to
295dcd65446SJoel Dice         // `run_concurrent` from being polled.  Consequently, we must rely on a
296dcd65446SJoel Dice         // "second line of defense" to ensure tasks are timed out promptly,
297dcd65446SJoel Dice         // which is to check for timeouts _outside_ the `run_concurrent` future.
298dcd65446SJoel Dice         // Once the aforementioned issues have been addressed, we'll be able to
299dcd65446SJoel Dice         // remove that check and its associated baggage.
300dcd65446SJoel Dice 
301dcd65446SJoel Dice         let handler = &self.handler.0;
302dcd65446SJoel Dice 
303dcd65446SJoel Dice         let StoreBundle {
304dcd65446SJoel Dice             mut store,
305dcd65446SJoel Dice             write_profile,
306dcd65446SJoel Dice         } = handler.state.new_store(req_id)?;
307dcd65446SJoel Dice 
308dcd65446SJoel Dice         let request_timeout = handler.state.request_timeout();
309dcd65446SJoel Dice         let idle_instance_timeout = handler.state.idle_instance_timeout();
310dcd65446SJoel Dice         let max_instance_reuse_count = handler.state.max_instance_reuse_count();
311dcd65446SJoel Dice         let max_instance_concurrent_reuse_count =
312dcd65446SJoel Dice             handler.state.max_instance_concurrent_reuse_count();
313dcd65446SJoel Dice 
314dcd65446SJoel Dice         let proxy = &handler.instance_pre.instantiate_async(&mut store).await?;
315dcd65446SJoel Dice         let accept_concurrent = AtomicBool::new(true);
316dcd65446SJoel Dice         let task_start_times = Mutex::new(StartTimes::default());
317dcd65446SJoel Dice 
318dcd65446SJoel Dice         let mut future = pin!(store.run_concurrent(async |accessor| {
319dcd65446SJoel Dice             let mut reuse_count = 0;
320dcd65446SJoel Dice             let mut timed_out = false;
321dcd65446SJoel Dice             let mut futures = FuturesUnordered::new();
322dcd65446SJoel Dice 
323dcd65446SJoel Dice             let accept_task = |task: TaskFn<S::StoreData>,
324dcd65446SJoel Dice                                futures: &mut FuturesUnordered<_>,
325dcd65446SJoel Dice                                reuse_count: &mut usize| {
326dcd65446SJoel Dice                 // Set `accept_concurrent` to false, conservatively assuming
327dcd65446SJoel Dice                 // that the new task will be CPU-bound, at least to begin with.
328dcd65446SJoel Dice                 // Only once the `StoreContextMut::run_concurrent` event loop
329dcd65446SJoel Dice                 // returns `Pending` will we set `accept_concurrent` back to
330dcd65446SJoel Dice                 // true and consider accepting more tasks.
331dcd65446SJoel Dice                 //
332dcd65446SJoel Dice                 // This approach avoids taking on more than one CPU-bound task
333dcd65446SJoel Dice                 // at a time, which would hurt throughput vs. leaving the
334dcd65446SJoel Dice                 // additional tasks for other workers to handle.
335dcd65446SJoel Dice                 accept_concurrent.store(false, Relaxed);
336dcd65446SJoel Dice                 *reuse_count += 1;
337dcd65446SJoel Dice 
338dcd65446SJoel Dice                 let start_time = Instant::now().checked_add(request_timeout);
339dcd65446SJoel Dice                 if let Some(start_time) = start_time {
340dcd65446SJoel Dice                     task_start_times.lock().unwrap().add(start_time);
341dcd65446SJoel Dice                 }
342dcd65446SJoel Dice 
343dcd65446SJoel Dice                 futures.push(tokio::time::timeout(request_timeout, async move {
344dcd65446SJoel Dice                     (task)(accessor, proxy).await;
345dcd65446SJoel Dice                     start_time
346dcd65446SJoel Dice                 }));
347dcd65446SJoel Dice             };
348dcd65446SJoel Dice 
349dcd65446SJoel Dice             if let Some(task) = task {
350dcd65446SJoel Dice                 accept_task(task, &mut futures, &mut reuse_count);
351dcd65446SJoel Dice             }
352dcd65446SJoel Dice 
353dcd65446SJoel Dice             let handler = self.handler.clone();
354dcd65446SJoel Dice             while !(futures.is_empty() && reuse_count >= max_instance_reuse_count) {
355dcd65446SJoel Dice                 let new_task = {
356dcd65446SJoel Dice                     let future_count = futures.len();
357dcd65446SJoel Dice                     let mut next_future = pin!(async {
358dcd65446SJoel Dice                         if futures.is_empty() {
359dcd65446SJoel Dice                             future::pending().await
360dcd65446SJoel Dice                         } else {
361dcd65446SJoel Dice                             futures.next().await.unwrap()
362dcd65446SJoel Dice                         }
363dcd65446SJoel Dice                     });
364dcd65446SJoel Dice                     let mut next_task = pin!(tokio::time::timeout(
365dcd65446SJoel Dice                         if future_count == 0 {
366dcd65446SJoel Dice                             idle_instance_timeout
367dcd65446SJoel Dice                         } else {
368dcd65446SJoel Dice                             Duration::MAX
369dcd65446SJoel Dice                         },
370dcd65446SJoel Dice                         handler.0.task_queue.pop()
371dcd65446SJoel Dice                     ));
372dcd65446SJoel Dice                     // Poll any existing tasks, and if they're all `Pending`
373dcd65446SJoel Dice                     // _and_ we haven't reached any reuse limits yet, poll for a
374dcd65446SJoel Dice                     // new task from the queue.
375dcd65446SJoel Dice                     //
376dcd65446SJoel Dice                     // Note the the order of operations here is important.  By
377*ab78bd82SHo Kim                     // polling `next_future` first, we'll discover any tasks that
378dcd65446SJoel Dice                     // may have timed out, at which point we'll stop accepting
379dcd65446SJoel Dice                     // new tasks altogether (see below for details).  This is
380*ab78bd82SHo Kim                     // especially important in the case where the task was
381dcd65446SJoel Dice                     // blocked on a synchronous call to a host function which
382dcd65446SJoel Dice                     // has exclusive access to the `Store`; once that call
383dcd65446SJoel Dice                     // finishes, the first think we need to do is time out the
384dcd65446SJoel Dice                     // task.  If we were to poll for a new task first, then we'd
385dcd65446SJoel Dice                     // have to wait for _that_ task to finish or time out before
386dcd65446SJoel Dice                     // we could kill the instance.
387dcd65446SJoel Dice                     future::poll_fn(|cx| match next_future.as_mut().poll(cx) {
388dcd65446SJoel Dice                         Poll::Pending => {
389dcd65446SJoel Dice                             // Note that `Pending` here doesn't necessarily mean
390dcd65446SJoel Dice                             // all tasks are blocked on I/O.  They might simply
391dcd65446SJoel Dice                             // be waiting for some deferred work to be done by
392dcd65446SJoel Dice                             // the next turn of the
393dcd65446SJoel Dice                             // `StoreContextMut::run_concurrent` event loop.
394dcd65446SJoel Dice                             // Therefore, we check `accept_concurrent` here and
395dcd65446SJoel Dice                             // only advertise we have capacity for another task
396dcd65446SJoel Dice                             // if either we have no tasks at all or all our
397dcd65446SJoel Dice                             // tasks really are blocked on I/O.
398dcd65446SJoel Dice                             self.set_available(
399dcd65446SJoel Dice                                 reuse_count < max_instance_reuse_count
400dcd65446SJoel Dice                                     && future_count < max_instance_concurrent_reuse_count
401dcd65446SJoel Dice                                     && (future_count == 0 || accept_concurrent.load(Relaxed)),
402dcd65446SJoel Dice                             );
403dcd65446SJoel Dice 
404dcd65446SJoel Dice                             if self.available {
405dcd65446SJoel Dice                                 next_task.as_mut().poll(cx).map(Some)
406dcd65446SJoel Dice                             } else {
407dcd65446SJoel Dice                                 Poll::Pending
408dcd65446SJoel Dice                             }
409dcd65446SJoel Dice                         }
410dcd65446SJoel Dice                         Poll::Ready(Ok(start_time)) => {
411dcd65446SJoel Dice                             // Task completed; carry on!
412dcd65446SJoel Dice                             if let Some(start_time) = start_time {
413dcd65446SJoel Dice                                 task_start_times.lock().unwrap().remove(start_time);
414dcd65446SJoel Dice                             }
415dcd65446SJoel Dice                             Poll::Ready(None)
416dcd65446SJoel Dice                         }
417dcd65446SJoel Dice                         Poll::Ready(Err(_)) => {
418dcd65446SJoel Dice                             // Task timed out; stop accepting new tasks, but
419dcd65446SJoel Dice                             // continue polling until any other, in-progress
420dcd65446SJoel Dice                             // tasks until they have either finished or timed
421dcd65446SJoel Dice                             // out.  This effectively kicks off a "graceful
422dcd65446SJoel Dice                             // shutdown" of the worker, allowing any other
423dcd65446SJoel Dice                             // concurrent tasks time to finish before we drop
424dcd65446SJoel Dice                             // the instance.
425dcd65446SJoel Dice                             //
426dcd65446SJoel Dice                             // TODO: We should also send a cancel request to the
427dcd65446SJoel Dice                             // timed-out task to give it a chance to shut down
428dcd65446SJoel Dice                             // gracefully (and delay dropping the instance for a
429dcd65446SJoel Dice                             // reasonable amount of time), but as of this
430dcd65446SJoel Dice                             // writing Wasmtime does not yet provide an API for
431dcd65446SJoel Dice                             // doing that.  See issue #11833.
432dcd65446SJoel Dice                             timed_out = true;
433dcd65446SJoel Dice                             reuse_count = max_instance_reuse_count;
434dcd65446SJoel Dice                             Poll::Ready(None)
435dcd65446SJoel Dice                         }
436dcd65446SJoel Dice                     })
437dcd65446SJoel Dice                     .await
438dcd65446SJoel Dice                 };
439dcd65446SJoel Dice 
440dcd65446SJoel Dice                 match new_task {
441dcd65446SJoel Dice                     Some(Ok(task)) => {
442dcd65446SJoel Dice                         accept_task(task, &mut futures, &mut reuse_count);
443dcd65446SJoel Dice                     }
444dcd65446SJoel Dice                     Some(Err(_)) => break,
445dcd65446SJoel Dice                     None => {}
446dcd65446SJoel Dice                 }
447dcd65446SJoel Dice             }
448dcd65446SJoel Dice 
449dcd65446SJoel Dice             accessor.with(|mut access| write_profile(access.as_context_mut()));
450dcd65446SJoel Dice 
451dcd65446SJoel Dice             if timed_out {
45285d8cc06SNick Fitzgerald                 Err(format_err!("guest timed out"))
453dcd65446SJoel Dice             } else {
45485d8cc06SNick Fitzgerald                 wasmtime::error::Ok(())
455dcd65446SJoel Dice             }
456dcd65446SJoel Dice         }));
457dcd65446SJoel Dice 
458dcd65446SJoel Dice         let mut sleep = pin!(tokio::time::sleep(Duration::MAX));
459dcd65446SJoel Dice 
460dcd65446SJoel Dice         future::poll_fn(|cx| {
461dcd65446SJoel Dice             let poll = future.as_mut().poll(cx);
462dcd65446SJoel Dice             if poll.is_pending() {
463dcd65446SJoel Dice                 // If the future returns `Pending`, that's either because it's
464dcd65446SJoel Dice                 // idle (in which case it can definitely accept a new task) or
465dcd65446SJoel Dice                 // because all its tasks are awaiting I/O, in which case it may
466dcd65446SJoel Dice                 // have capacity for additional tasks to run concurrently.
467dcd65446SJoel Dice                 //
468dcd65446SJoel Dice                 // However, if one of the tasks is blocked on a sync call to a
469dcd65446SJoel Dice                 // host function which has exclusive access to the `Store`, the
470dcd65446SJoel Dice                 // `StoreContextMut::run_concurrent` event loop will be unable
471dcd65446SJoel Dice                 // to make progress until that call finishes.  Similarly, if the
472dcd65446SJoel Dice                 // task loops indefinitely, subject only to epoch interruption,
473dcd65446SJoel Dice                 // the event loop will also be stuck.  Either way, any task
474dcd65446SJoel Dice                 // timeouts created inside the `AsyncFnOnce` we passed to
475dcd65446SJoel Dice                 // `run_concurrent` won't have a chance to trigger.
476dcd65446SJoel Dice                 // Consequently, we need to _also_ enforce timeouts here,
477dcd65446SJoel Dice                 // outside the event loop.
478dcd65446SJoel Dice                 //
479dcd65446SJoel Dice                 // Therefore, we check if the oldest outstanding task has been
480dcd65446SJoel Dice                 // running for at least `request_timeout*2`, which is the
481dcd65446SJoel Dice                 // maximum time needed for any other concurrent tasks to
482dcd65446SJoel Dice                 // complete or time out, at which point we can safely discard
483dcd65446SJoel Dice                 // the instance.  If that deadline has not yet arrived, we
484dcd65446SJoel Dice                 // schedule a wakeup to occur when it does.
485dcd65446SJoel Dice                 //
486dcd65446SJoel Dice                 // We uphold the "never kill an instance with a task which has
487dcd65446SJoel Dice                 // been running for less than the request timeout" invariant
488dcd65446SJoel Dice                 // here by noting that this timeout will only trigger if the
489dcd65446SJoel Dice                 // `AsyncFnOnce` we passed to `run_concurrent` has been unable
490dcd65446SJoel Dice                 // to run for at least the past `request_timeout` amount of
491dcd65446SJoel Dice                 // time, meaning it can't possibly have accepted a task newer
492dcd65446SJoel Dice                 // than that.
493dcd65446SJoel Dice                 if let Some(deadline) = task_start_times
494dcd65446SJoel Dice                     .lock()
495dcd65446SJoel Dice                     .unwrap()
496dcd65446SJoel Dice                     .earliest()
497dcd65446SJoel Dice                     .and_then(|v| v.checked_add(request_timeout.saturating_mul(2)))
498dcd65446SJoel Dice                 {
499dcd65446SJoel Dice                     sleep.as_mut().reset(deadline.into());
500dcd65446SJoel Dice                     // Note that this will schedule a wakeup for later if the
501dcd65446SJoel Dice                     // deadline has not yet arrived:
502dcd65446SJoel Dice                     if sleep.as_mut().poll(cx).is_ready() {
503dcd65446SJoel Dice                         // Deadline has been reached; kill the instance with an
504dcd65446SJoel Dice                         // error.
50585d8cc06SNick Fitzgerald                         return Poll::Ready(Err(format_err!("guest timed out")));
506dcd65446SJoel Dice                     }
507dcd65446SJoel Dice                 }
508dcd65446SJoel Dice 
509dcd65446SJoel Dice                 // Otherwise, if no timeouts have elapsed, we set
510dcd65446SJoel Dice                 // `accept_concurrent` to true and, if it wasn't already true
511dcd65446SJoel Dice                 // before, poll the future one more time so it can ask for
512dcd65446SJoel Dice                 // another task if appropriate.
513dcd65446SJoel Dice                 if !accept_concurrent.swap(true, Relaxed) {
514dcd65446SJoel Dice                     return future.as_mut().poll(cx);
515dcd65446SJoel Dice                 }
516dcd65446SJoel Dice             }
517dcd65446SJoel Dice 
518dcd65446SJoel Dice             poll
519dcd65446SJoel Dice         })
520dcd65446SJoel Dice         .await?
521dcd65446SJoel Dice     }
522dcd65446SJoel Dice }
523dcd65446SJoel Dice 
524dcd65446SJoel Dice impl<S> Drop for Worker<S>
525dcd65446SJoel Dice where
526dcd65446SJoel Dice     S: HandlerState,
527dcd65446SJoel Dice {
drop(&mut self)528dcd65446SJoel Dice     fn drop(&mut self) {
529dcd65446SJoel Dice         self.set_available(false);
530dcd65446SJoel Dice     }
531dcd65446SJoel Dice }
532dcd65446SJoel Dice 
533dcd65446SJoel Dice /// Represents the state of a web server.
534dcd65446SJoel Dice ///
535dcd65446SJoel Dice /// Note that this supports optional instance reuse, enabled when
536dcd65446SJoel Dice /// `S::max_instance_reuse_count()` returns a number greater than one.  See
537bc4582c3SAlex Crichton /// [`Self::spawn`] for details.
538dcd65446SJoel Dice pub struct ProxyHandler<S: HandlerState>(Arc<ProxyHandlerInner<S>>);
539dcd65446SJoel Dice 
540dcd65446SJoel Dice impl<S: HandlerState> Clone for ProxyHandler<S> {
clone(&self) -> Self541dcd65446SJoel Dice     fn clone(&self) -> Self {
542dcd65446SJoel Dice         Self(self.0.clone())
543dcd65446SJoel Dice     }
544dcd65446SJoel Dice }
545dcd65446SJoel Dice 
546dcd65446SJoel Dice impl<S> ProxyHandler<S>
547dcd65446SJoel Dice where
548dcd65446SJoel Dice     S: HandlerState,
549dcd65446SJoel Dice {
550dcd65446SJoel Dice     /// Create a new `ProxyHandler` with the specified application state and
551dcd65446SJoel Dice     /// pre-instance.
new(state: S, instance_pre: ProxyPre<S::StoreData>) -> Self552dcd65446SJoel Dice     pub fn new(state: S, instance_pre: ProxyPre<S::StoreData>) -> Self {
553dcd65446SJoel Dice         Self(Arc::new(ProxyHandlerInner {
554dcd65446SJoel Dice             state,
555dcd65446SJoel Dice             instance_pre,
556dcd65446SJoel Dice             next_id: AtomicU64::from(0),
557dcd65446SJoel Dice             task_queue: Default::default(),
558dcd65446SJoel Dice             worker_count: AtomicUsize::from(0),
559dcd65446SJoel Dice         }))
560dcd65446SJoel Dice     }
561dcd65446SJoel Dice 
562dcd65446SJoel Dice     /// Push a task to the task queue for this handler.
563dcd65446SJoel Dice     ///
564dcd65446SJoel Dice     /// This will either spawn a new background worker to run the task or
565dcd65446SJoel Dice     /// deliver it to an already-running worker.
566dcd65446SJoel Dice     ///
567dcd65446SJoel Dice     /// The `req_id` will be passed to `<S as HandlerState>::new_store` _if_ a
568dcd65446SJoel Dice     /// new worker is started for this task.  It is intended to be used as a
569dcd65446SJoel Dice     /// "request identifier" corresponding to that task and can be used e.g. to
570dcd65446SJoel Dice     /// prefix all logging from the `Store` with that identifier.  Note that a
571dcd65446SJoel Dice     /// non-`None` value only makes sense when `<S as
572dcd65446SJoel Dice     /// HandlerState>::max_instance_reuse_count == 1`; otherwise the identifier
573dcd65446SJoel Dice     /// will not match subsequent tasks handled by the worker.
spawn(&self, req_id: Option<u64>, task: TaskFn<S::StoreData>)574dcd65446SJoel Dice     pub fn spawn(&self, req_id: Option<u64>, task: TaskFn<S::StoreData>) {
575dcd65446SJoel Dice         match self.0.state.max_instance_reuse_count() {
576dcd65446SJoel Dice             0 => panic!("`max_instance_reuse_count` must be at least 1"),
577dcd65446SJoel Dice             _ => {
578dcd65446SJoel Dice                 if self.0.worker_count.load(Relaxed) == 0 {
579dcd65446SJoel Dice                     // There are no available workers; skip the queue and pass
580dcd65446SJoel Dice                     // the task directly to the worker, which improves
581dcd65446SJoel Dice                     // performance as measured by `wasmtime-server-rps.sh` by
582dcd65446SJoel Dice                     // about 15%.
583dcd65446SJoel Dice                     self.start_worker(Some(task), req_id);
584dcd65446SJoel Dice                 } else {
585dcd65446SJoel Dice                     self.0.task_queue.push(task);
586dcd65446SJoel Dice                     // Start a new worker to handle the task if the last worker
587dcd65446SJoel Dice                     // just went unavailable.  See also `Worker::set_available`
588dcd65446SJoel Dice                     // for what happens if the available worker count goes to
589dcd65446SJoel Dice                     // zero right after we check it here, and note that we only
590dcd65446SJoel Dice                     // check the count _after_ we've pushed the task to the
591dcd65446SJoel Dice                     // queue.  We use `SeqCst` here to ensure that we get an
592dcd65446SJoel Dice                     // updated view of `worker_count` as it exists after the
593dcd65446SJoel Dice                     // `Queue::push` above.
594dcd65446SJoel Dice                     //
595dcd65446SJoel Dice                     // The upshot is that at least one (or more) of the
596dcd65446SJoel Dice                     // following will happen:
597dcd65446SJoel Dice                     //
598dcd65446SJoel Dice                     // - An existing worker will accept the task
599dcd65446SJoel Dice                     // - We'll start a new worker here to accept the task
600dcd65446SJoel Dice                     // - `Worker::set_available` will start a new worker to accept the task
601dcd65446SJoel Dice                     //
602dcd65446SJoel Dice                     // I.e. it should not be possible for the task to be
603dcd65446SJoel Dice                     // orphaned indefinitely in the queue without being
604dcd65446SJoel Dice                     // accepted.
605dcd65446SJoel Dice                     if self.0.worker_count.load(SeqCst) == 0 {
606dcd65446SJoel Dice                         self.start_worker(None, None);
607dcd65446SJoel Dice                     }
608dcd65446SJoel Dice                 }
609dcd65446SJoel Dice             }
610dcd65446SJoel Dice         }
611dcd65446SJoel Dice     }
612dcd65446SJoel Dice 
613dcd65446SJoel Dice     /// Generate a unique request ID.
next_req_id(&self) -> u64614dcd65446SJoel Dice     pub fn next_req_id(&self) -> u64 {
615dcd65446SJoel Dice         self.0.next_id.fetch_add(1, Relaxed)
616dcd65446SJoel Dice     }
617dcd65446SJoel Dice 
618dcd65446SJoel Dice     /// Return a reference to the application state.
state(&self) -> &S619dcd65446SJoel Dice     pub fn state(&self) -> &S {
620dcd65446SJoel Dice         &self.0.state
621dcd65446SJoel Dice     }
622dcd65446SJoel Dice 
623dcd65446SJoel Dice     /// Return a reference to the pre-instance.
instance_pre(&self) -> &ProxyPre<S::StoreData>624dcd65446SJoel Dice     pub fn instance_pre(&self) -> &ProxyPre<S::StoreData> {
625dcd65446SJoel Dice         &self.0.instance_pre
626dcd65446SJoel Dice     }
627dcd65446SJoel Dice 
start_worker(&self, task: Option<TaskFn<S::StoreData>>, req_id: Option<u64>)628dcd65446SJoel Dice     fn start_worker(&self, task: Option<TaskFn<S::StoreData>>, req_id: Option<u64>) {
629dcd65446SJoel Dice         tokio::spawn(
630dcd65446SJoel Dice             Worker {
631dcd65446SJoel Dice                 handler: self.clone(),
632dcd65446SJoel Dice                 available: false,
633dcd65446SJoel Dice             }
634dcd65446SJoel Dice             .run(task, req_id),
635dcd65446SJoel Dice         );
636dcd65446SJoel Dice     }
637dcd65446SJoel Dice }
638