From 985ed07c3f66f8c4f26f4b92cd3c41b2cea0142c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 20 May 2022 12:06:11 -0500 Subject: [PATCH] Improve documentation around `ResourceLimiter` (#4173) * Improve documentation around `ResourceLimiter` This commit takes a pass through the `Store::limiter` method and related types/traits to improve the documentation with an example and soup up any recent developments in the documentation. Closes #4138 * Fix a broken doc link --- crates/wasmtime/src/limits.rs | 85 ++++++++++++++++++++++++++++------- crates/wasmtime/src/store.rs | 72 +++++++++++++++++++++++------ 2 files changed, 127 insertions(+), 30 deletions(-) diff --git a/crates/wasmtime/src/limits.rs b/crates/wasmtime/src/limits.rs index 687f01371c..de0e1ffaca 100644 --- a/crates/wasmtime/src/limits.rs +++ b/crates/wasmtime/src/limits.rs @@ -7,8 +7,26 @@ pub const DEFAULT_MEMORY_LIMIT: usize = 10000; /// Used by hosts to limit resource consumption of instances. /// -/// An instance can be created with a resource limiter so that hosts can take into account -/// non-WebAssembly resource usage to determine if a linear memory or table should grow. +/// This trait is used in conjunction with the +/// [`Store::limiter`](crate::Store::limiter) to synchronously limit the +/// allocation of resources within a store. As a store-level limit this means +/// that all creation of instances, memories, and tables are limited within the +/// store. Resources limited via this trait are primarily related to memory and +/// limiting CPU resources needs to be done with something such as +/// [`Config::consume_fuel`](crate::Config::consume_fuel) or +/// [`Config::epoch_interruption`](crate::Config::epoch_interruption). +/// +/// Note that this trait does not limit 100% of memory allocated via a +/// [`Store`](crate::Store). Wasmtime will still allocate memory to track data +/// structures and additionally embedder-specific memory allocations are not +/// tracked via this trait. This trait only limits resources allocated by a +/// WebAssembly instance itself. +/// +/// This trait is intended for synchronously limiting the resources of a module. +/// If your use case requires blocking to answer whether a request is permitted +/// or not and you're otherwise working in an asynchronous context the +/// [`ResourceLimiterAsync`] trait is also provided to avoid blocking an OS +/// thread while a limit is determined. pub trait ResourceLimiter { /// Notifies the resource limiter that an instance's linear memory has been /// requested to grow. @@ -19,6 +37,9 @@ pub trait ResourceLimiter { /// instance allocator, also in bytes. A value of `None` /// indicates that the linear memory is unbounded. /// + /// The `current` and `desired` amounts are guaranteed to always be + /// multiples of the WebAssembly page size, 64KiB. + /// /// This function should return `true` to indicate that the growing /// operation is permitted or `false` if not permitted. Returning `true` /// when a maximum has been exceeded will have no effect as the linear @@ -28,6 +49,12 @@ pub trait ResourceLimiter { /// `memory.grow`. Requests where the allocation requested size doesn't fit /// in `usize` or exceeds the memory's listed maximum size may not invoke /// this method. + /// + /// Returning `false` from this method will cause the `memory.grow` + /// instruction in a module to return -1 (failure), or in the case of an + /// embedder API calling [`Memory::new`](crate::Memory::new) or + /// [`Memory::grow`](crate::Memory::grow) an error will be returned from + /// those methods. fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option) -> bool; /// Notifies the resource limiter that growing a linear memory, permitted by @@ -38,16 +65,24 @@ pub trait ResourceLimiter { /// memory. In that case, `error` might be downcastable to a `std::io::Error`. fn memory_grow_failed(&mut self, _error: &anyhow::Error) {} - /// Notifies the resource limiter that an instance's table has been requested to grow. + /// Notifies the resource limiter that an instance's table has been + /// requested to grow. /// /// * `current` is the current number of elements in the table. /// * `desired` is the desired number of elements in the table. - /// * `maximum` is either the table's maximum or a maximum from an instance allocator. - /// A value of `None` indicates that the table is unbounded. + /// * `maximum` is either the table's maximum or a maximum from an instance + /// allocator. A value of `None` indicates that the table is unbounded. /// - /// This function should return `true` to indicate that the growing operation is permitted or - /// `false` if not permitted. Returning `true` when a maximum has been exceeded will have no - /// effect as the table will not grow. + /// This function should return `true` to indicate that the growing + /// operation is permitted or `false` if not permitted. Returning `true` + /// when a maximum has been exceeded will have no effect as the table will + /// not grow. + /// + /// Currently in Wasmtime each table element requires a pointer's worth of + /// space (e.g. `mem::size_of::()`). + /// + /// Like `memory_growing` returning `false` from this function will cause + /// `table.grow` to return -1 or embedder APIs will return an error. fn table_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool; /// Notifies the resource limiter that growing a linear memory, permitted by @@ -68,7 +103,7 @@ pub trait ResourceLimiter { /// The maximum number of tables that can be created for a `Store`. /// - /// Module instantiation will fail if this limit is exceeded. + /// Creation of tables will fail if this limit is exceeded. /// /// This value defaults to 10,000. fn tables(&self) -> usize { @@ -77,7 +112,7 @@ pub trait ResourceLimiter { /// The maximum number of linear memories that can be created for a `Store` /// - /// Instantiation will fail with an error if this limit is exceeded. + /// Creation of memories will fail with an error if this limit is exceeded. /// /// This value defaults to 10,000. fn memories(&self) -> usize { @@ -85,15 +120,24 @@ pub trait ResourceLimiter { } } -#[cfg(feature = "async")] -/// Used by hosts to limit resource consumption of instances. Identical to -/// [`ResourceLimiter`], except that the `memory_growing` and `table_growing` -/// functions are async. Must be used with an async [`Store`](`crate::Store`). +/// Used by hosts to limit resource consumption of instances, blocking +/// asynchronously if necessary. +/// +/// This trait is identical to [`ResourceLimiter`], except that the +/// `memory_growing` and `table_growing` functions are `async`. Must be used +/// with an async [`Store`](`crate::Store`) configured via +/// [`Config::async_support`](crate::Config::async_support). /// /// This trait is used with /// [`Store::limiter_async`](`crate::Store::limiter_async`)`: see those docs /// for restrictions on using other Wasmtime interfaces with an async resource -/// limiter. +/// limiter. Additionally see [`ResourceLimiter`] for more information about +/// limiting resources from WebAssembly. +/// +/// The `async` here enables embedders that are already using asynchronous +/// execution of WebAssembly to block the WebAssembly, but no the OS thread, to +/// answer the question whether growing a memory or table is allowed. +#[cfg(feature = "async")] #[async_trait::async_trait] pub trait ResourceLimiterAsync { /// Async version of [`ResourceLimiter::memory_growing`] @@ -134,6 +178,9 @@ pub struct StoreLimitsBuilder(StoreLimits); impl StoreLimitsBuilder { /// Creates a new [`StoreLimitsBuilder`]. + /// + /// See the documentation on each builder method for the default for each + /// value. pub fn new() -> Self { Self(StoreLimits::default()) } @@ -195,6 +242,13 @@ impl StoreLimitsBuilder { } /// Provides limits for a [`Store`](crate::Store). +/// +/// This type is created with a [`StoreLimitsBuilder`] and is typically used in +/// conjuction with [`Store::limiter`](crate::Store::limiter). +/// +/// This is a convenience type included to avoid needing to implement the +/// [`ResourceLimiter`] trait if your use case fits in the static configuration +/// that this [`StoreLimits`] provides. pub struct StoreLimits { memory_size: Option, table_elements: Option, @@ -215,7 +269,6 @@ impl Default for StoreLimits { } } -#[cfg_attr(feature = "async", async_trait::async_trait)] impl ResourceLimiter for StoreLimits { fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option) -> bool { match self.memory_size { diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 430e201816..6a5ec7f3c3 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -567,12 +567,57 @@ impl Store { } } - /// Configures the [`ResourceLimiter`](crate::ResourceLimiter) used to limit - /// resource creation within this [`Store`]. + /// Configures the [`ResourceLimiter`] used to limit resource creation + /// within this [`Store`]. + /// + /// Whenever resources such as linear memory, tables, or instances are + /// allocated the `limiter` specified here is invoked with the store's data + /// `T` and the returned [`ResourceLimiter`] is used to limit the operation + /// being allocated. The returned [`ResourceLimiter`] is intended to live + /// within the `T` itself, for example by storing a + /// [`StoreLimits`](crate::StoreLimits). /// /// Note that this limiter is only used to limit the creation/growth of /// resources in the future, this does not retroactively attempt to apply /// limits to the [`Store`]. + /// + /// # Examples + /// + /// ``` + /// use wasmtime::*; + /// + /// struct MyApplicationState { + /// my_state: u32, + /// limits: StoreLimits, + /// } + /// + /// let engine = Engine::default(); + /// let my_state = MyApplicationState { + /// my_state: 42, + /// limits: StoreLimitsBuilder::new() + /// .memory_size(1 << 20 /* 1 MB */) + /// .instances(2) + /// .build(), + /// }; + /// let mut store = Store::new(&engine, my_state); + /// store.limiter(|state| &mut state.limits); + /// + /// // Creation of smaller memories is allowed + /// Memory::new(&mut store, MemoryType::new(1, None)).unwrap(); + /// + /// // Creation of a larger memory, however, will exceed the 1MB limit we've + /// // configured + /// assert!(Memory::new(&mut store, MemoryType::new(1000, None)).is_err()); + /// + /// // The number of instances in this store is limited to 2, so the third + /// // instance here should fail. + /// let module = Module::new(&engine, "(module)").unwrap(); + /// assert!(Instance::new(&mut store, &module, &[]).is_ok()); + /// assert!(Instance::new(&mut store, &module, &[]).is_ok()); + /// assert!(Instance::new(&mut store, &module, &[]).is_err()); + /// ``` + /// + /// [`ResourceLimiter`]: crate::ResourceLimiter pub fn limiter( &mut self, mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync + 'static, @@ -592,20 +637,12 @@ impl Store { inner.limiter = Some(ResourceLimiterInner::Sync(Box::new(limiter))); } - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] /// Configures the [`ResourceLimiterAsync`](crate::ResourceLimiterAsync) - /// used to limit resource creation within this [`Store`]. Must be used - /// with an async `Store`!. + /// used to limit resource creation within this [`Store`]. /// - /// Note that this limiter is only used to limit the creation/growth of - /// resources in the future, this does not retroactively attempt to apply - /// limits to the [`Store`]. - /// - /// This variation on the [`ResourceLimiter`](`crate::ResourceLimiter`) - /// makes the `memory_growing` and `table_growing` functions `async`. This - /// means that, as part of your resource limiting strategy, the async - /// resource limiter may yield execution until a resource becomes - /// available. + /// This method is an asynchronous variant of the [`Store::limiter`] method + /// where the embedder can block the wasm request for more resources with + /// host `async` execution of futures. /// /// By using a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) /// with a [`Store`], you can no longer use @@ -617,7 +654,14 @@ impl Store { /// [`Memory::grow_async`](`crate::Memory::grow_async`), /// [`Table::new_async`](`crate::Table::new_async`), and /// [`Table::grow_async`](`crate::Table::grow_async`). + /// + /// Note that this limiter is only used to limit the creation/growth of + /// resources in the future, this does not retroactively attempt to apply + /// limits to the [`Store`]. Additionally this must be used with an async + /// [`Store`] configured via + /// [`Config::async_support`](crate::Config::async_support). #[cfg(feature = "async")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] pub fn limiter_async( &mut self, mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync)