give sychronous ResourceLimiter an async alternative
This commit is contained in:
@@ -38,6 +38,7 @@ psm = "0.1.11"
|
||||
lazy_static = "1.4"
|
||||
rayon = { version = "1.0", optional = true }
|
||||
object = { version = "0.27", default-features = false, features = ['read_core', 'elf'] }
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
@@ -73,7 +74,7 @@ cache = ["wasmtime-cache"]
|
||||
|
||||
# Enables support for "async stores" as well as defining host functions as
|
||||
# `async fn` and calling functions asynchronously.
|
||||
async = ["wasmtime-fiber", "wasmtime-runtime/async"]
|
||||
async = ["wasmtime-fiber", "wasmtime-runtime/async", "async-trait"]
|
||||
|
||||
# Enables userfaultfd support in the runtime's pooling allocator when building on Linux
|
||||
uffd = ["wasmtime-runtime/uffd"]
|
||||
|
||||
@@ -551,7 +551,7 @@ impl Table {
|
||||
let init = init.into_table_element(store, ty)?;
|
||||
let table = self.wasmtime_table(store);
|
||||
unsafe {
|
||||
match (*table).grow(delta, init, store.limiter()) {
|
||||
match (*table).grow(delta, init, store) {
|
||||
Some(size) => {
|
||||
let vm = (*table).vmtable();
|
||||
*store[self.0].definition = vm;
|
||||
|
||||
@@ -15,7 +15,7 @@ use wasmtime_environ::{
|
||||
};
|
||||
use wasmtime_jit::TypeTables;
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceAllocationRequest, InstantiationError, VMContext, VMFunctionBody,
|
||||
Imports, InstanceAllocationRequest, InstantiationError, StorePtr, VMContext, VMFunctionBody,
|
||||
VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport,
|
||||
};
|
||||
|
||||
@@ -734,7 +734,7 @@ impl<'a> Instantiator<'a> {
|
||||
imports: self.cur.build(),
|
||||
shared_signatures: self.cur.module.signatures().as_module_map().into(),
|
||||
host_state: Box::new(Instance(instance_to_be)),
|
||||
store: Some(store.traitobj()),
|
||||
store: StorePtr::new(store.traitobj()),
|
||||
wasm_data: compiled_module.wasm_data(),
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1,4 +1,118 @@
|
||||
pub use wasmtime_runtime::ResourceLimiter;
|
||||
/// Value returned by [`ResourceLimiter::instances`] default method
|
||||
pub const DEFAULT_INSTANCE_LIMIT: usize = 10000;
|
||||
/// Value returned by [`ResourceLimiter::tables`] default method
|
||||
pub const DEFAULT_TABLE_LIMIT: usize = 10000;
|
||||
/// Value returned by [`ResourceLimiter::memories`] default method
|
||||
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.
|
||||
pub trait ResourceLimiter {
|
||||
/// Notifies the resource limiter that an instance's linear memory has been
|
||||
/// requested to grow.
|
||||
///
|
||||
/// * `current` is the current size of the linear memory in bytes.
|
||||
/// * `desired` is the desired size of the linear memory in bytes.
|
||||
/// * `maximum` is either the linear memory's maximum or a maximum from an
|
||||
/// instance allocator, also in bytes. A value of `None`
|
||||
/// indicates that the linear memory 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 linear
|
||||
/// memory will not grow.
|
||||
///
|
||||
/// This function is not guaranteed to be invoked for all requests to
|
||||
/// `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.
|
||||
fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option<usize>) -> bool;
|
||||
|
||||
/// Notifies the resource limiter that growing a linear memory, permitted by
|
||||
/// the `memory_growing` method, has failed.
|
||||
///
|
||||
/// Reasons for failure include: the growth exceeds the `maximum` passed to
|
||||
/// `memory_growing`, or the operating system failed to allocate additional
|
||||
/// 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.
|
||||
///
|
||||
/// * `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.
|
||||
///
|
||||
/// 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.
|
||||
fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool;
|
||||
|
||||
/// The maximum number of instances that can be created for a `Store`.
|
||||
///
|
||||
/// Module instantiation will fail if this limit is exceeded.
|
||||
///
|
||||
/// This value defaults to 10,000.
|
||||
fn instances(&self) -> usize {
|
||||
DEFAULT_INSTANCE_LIMIT
|
||||
}
|
||||
|
||||
/// The maximum number of tables that can be created for a `Store`.
|
||||
///
|
||||
/// Module instantiation will fail if this limit is exceeded.
|
||||
///
|
||||
/// This value defaults to 10,000.
|
||||
fn tables(&self) -> usize {
|
||||
DEFAULT_TABLE_LIMIT
|
||||
}
|
||||
|
||||
/// The maximum number of linear memories that can be created for a `Store`
|
||||
///
|
||||
/// Instantiation will fail with an error if this limit is exceeded.
|
||||
///
|
||||
/// This value defaults to 10,000.
|
||||
fn memories(&self) -> usize {
|
||||
DEFAULT_MEMORY_LIMIT
|
||||
}
|
||||
}
|
||||
|
||||
#[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`].
|
||||
#[async_trait::async_trait]
|
||||
pub trait ResourceLimiterAsync {
|
||||
/// Async version of [`ResourceLimiter::memory_growing`]
|
||||
async fn memory_growing(
|
||||
&mut self,
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool;
|
||||
|
||||
/// Identical to [`ResourceLimiter::memory_grow_failed`]
|
||||
fn memory_grow_failed(&mut self, error: &anyhow::Error);
|
||||
|
||||
/// Asynchronous version of [`ResourceLimiter::table_growing`]
|
||||
async fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool;
|
||||
|
||||
/// Identical to [`ResourceLimiter::instances`]`
|
||||
fn instances(&self) -> usize {
|
||||
DEFAULT_INSTANCE_LIMIT
|
||||
}
|
||||
|
||||
/// Identical to [`ResourceLimiter::tables`]`
|
||||
fn tables(&self) -> usize {
|
||||
DEFAULT_TABLE_LIMIT
|
||||
}
|
||||
|
||||
/// Identical to [`ResourceLimiter::memories`]`
|
||||
fn memories(&self) -> usize {
|
||||
DEFAULT_MEMORY_LIMIT
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to build [`StoreLimits`].
|
||||
pub struct StoreLimitsBuilder(StoreLimits);
|
||||
@@ -79,13 +193,14 @@ impl Default for StoreLimits {
|
||||
Self {
|
||||
memory_size: None,
|
||||
table_elements: None,
|
||||
instances: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
||||
tables: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
||||
memories: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
|
||||
instances: DEFAULT_INSTANCE_LIMIT,
|
||||
tables: DEFAULT_TABLE_LIMIT,
|
||||
memories: DEFAULT_MEMORY_LIMIT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
impl ResourceLimiter for StoreLimits {
|
||||
fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
||||
match self.memory_size {
|
||||
|
||||
@@ -461,7 +461,7 @@ impl Memory {
|
||||
let store = store.as_context_mut().0;
|
||||
let mem = self.wasmtime_memory(store);
|
||||
unsafe {
|
||||
match (*mem).grow(delta, store.limiter()) {
|
||||
match (*mem).grow(delta, store) {
|
||||
Some(size) => {
|
||||
let vm = (*mem).vmmemory();
|
||||
*store[self.0].definition = vm;
|
||||
|
||||
@@ -93,8 +93,8 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use wasmtime_runtime::{
|
||||
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
|
||||
OnDemandInstanceAllocator, SignalHandler, VMCallerCheckedAnyfunc, VMContext, VMExternRef,
|
||||
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
||||
OnDemandInstanceAllocator, SignalHandler, StorePtr, VMCallerCheckedAnyfunc, VMContext,
|
||||
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
||||
};
|
||||
|
||||
mod context;
|
||||
@@ -197,12 +197,18 @@ pub struct StoreInner<T> {
|
||||
/// Generic metadata about the store that doesn't need access to `T`.
|
||||
inner: StoreOpaque,
|
||||
|
||||
limiter: Option<Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>>,
|
||||
limiter: Option<ResourceLimiterInner<T>>,
|
||||
call_hook: Option<Box<dyn FnMut(&mut T, CallHook) -> Result<(), crate::Trap> + Send + Sync>>,
|
||||
// for comments about `ManuallyDrop`, see `Store::into_data`
|
||||
data: ManuallyDrop<T>,
|
||||
}
|
||||
|
||||
enum ResourceLimiterInner<T> {
|
||||
Sync(Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>),
|
||||
#[cfg(feature = "async")]
|
||||
Async(Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync) + Send + Sync>),
|
||||
}
|
||||
|
||||
// Forward methods on `StoreOpaque` to also being on `StoreInner<T>`
|
||||
impl<T> Deref for StoreInner<T> {
|
||||
type Target = StoreOpaque;
|
||||
@@ -402,7 +408,7 @@ impl<T> Store<T> {
|
||||
shared_signatures: None.into(),
|
||||
imports: Default::default(),
|
||||
module: Arc::new(wasmtime_environ::Module::default()),
|
||||
store: None,
|
||||
store: StorePtr::empty(),
|
||||
wasm_data: &[],
|
||||
})
|
||||
.expect("failed to allocate default callee")
|
||||
@@ -418,11 +424,11 @@ impl<T> Store<T> {
|
||||
modules: ModuleRegistry::default(),
|
||||
host_trampolines: HashMap::default(),
|
||||
instance_count: 0,
|
||||
instance_limit: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
||||
instance_limit: crate::DEFAULT_INSTANCE_LIMIT,
|
||||
memory_count: 0,
|
||||
memory_limit: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
|
||||
memory_limit: crate::DEFAULT_MEMORY_LIMIT,
|
||||
table_count: 0,
|
||||
table_limit: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
||||
table_limit: crate::DEFAULT_TABLE_LIMIT,
|
||||
fuel_adj: 0,
|
||||
#[cfg(feature = "async")]
|
||||
async_state: AsyncState {
|
||||
@@ -525,7 +531,36 @@ impl<T> Store<T> {
|
||||
innermost.memory_limit = memory_limit;
|
||||
|
||||
// Save the limiter accessor function:
|
||||
inner.limiter = Some(Box::new(limiter));
|
||||
inner.limiter = Some(ResourceLimiterInner::Sync(Box::new(limiter)));
|
||||
}
|
||||
|
||||
/// Configures the [`ResourceLimiterAsync`](crate::ResourceLimiterAsync) used to limit
|
||||
/// resource creation within this [`Store`]. Must be used with an async `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`].
|
||||
pub fn limiter_async(
|
||||
&mut self,
|
||||
mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
debug_assert!(self.inner.async_support());
|
||||
// Apply the limits on instances, tables, and memory given by the limiter:
|
||||
let inner = &mut self.inner;
|
||||
let (instance_limit, table_limit, memory_limit) = {
|
||||
let l = limiter(&mut inner.data);
|
||||
(l.instances(), l.tables(), l.memories())
|
||||
};
|
||||
let innermost = &mut inner.inner;
|
||||
innermost.instance_limit = instance_limit;
|
||||
innermost.table_limit = table_limit;
|
||||
innermost.memory_limit = memory_limit;
|
||||
|
||||
// Save the limiter accessor function:
|
||||
inner.limiter = Some(ResourceLimiterInner::Async(Box::new(limiter)));
|
||||
}
|
||||
|
||||
/// Configure a function that runs on calls and returns between WebAssembly
|
||||
@@ -872,11 +907,6 @@ impl<T> StoreInner<T> {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
pub fn limiter(&mut self) -> Option<&mut dyn crate::limits::ResourceLimiter> {
|
||||
let accessor = self.limiter.as_mut()?;
|
||||
Some(accessor(&mut self.data))
|
||||
}
|
||||
|
||||
pub fn call_hook(&mut self, s: CallHook) -> Result<(), Trap> {
|
||||
if let Some(hook) = &mut self.call_hook {
|
||||
hook(&mut self.data, s)
|
||||
@@ -1496,8 +1526,81 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
(&mut inner.externref_activations_table, &inner.modules)
|
||||
}
|
||||
|
||||
fn limiter(&mut self) -> Option<&mut dyn wasmtime_runtime::ResourceLimiter> {
|
||||
<Self>::limiter(self)
|
||||
fn limiter_memory_growing(
|
||||
&mut self,
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool {
|
||||
// Need to borrow async_cx before the mut borrow of the limiter.
|
||||
// self.async_cx() panicks when used with a non-async store, so
|
||||
// wrap this in an option.
|
||||
#[cfg(feature = "async")]
|
||||
let async_cx = if self.async_support() {
|
||||
Some(self.async_cx())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match self.limiter {
|
||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||
limiter(&mut self.data).memory_growing(current, desired, maximum)
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||
async_cx
|
||||
.expect("ResourceLimiterAsync requires async Store")
|
||||
.block_on(
|
||||
limiter(&mut self.data)
|
||||
.memory_growing(current, desired, maximum)
|
||||
.as_mut(),
|
||||
)
|
||||
.expect("FIXME idk how to deal with a trap here!")
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn limiter_memory_grow_failed(&mut self, error: &anyhow::Error) {
|
||||
match self.limiter {
|
||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||
limiter(&mut self.data).memory_grow_failed(error)
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
|
||||
limiter(&mut self.data).memory_grow_failed(error)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn limiter_table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool {
|
||||
// Need to borrow async_cx before the mut borrow of the limiter.
|
||||
// self.async_cx() panicks when used with a non-async store, so
|
||||
// wrap this in an option.
|
||||
#[cfg(feature = "async")]
|
||||
let async_cx = if self.async_support() {
|
||||
Some(self.async_cx())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match self.limiter {
|
||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||
limiter(&mut self.data).table_growing(current, desired, maximum)
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||
async_cx
|
||||
.expect("ResourceLimiterAsync requires async Store")
|
||||
.block_on(
|
||||
limiter(&mut self.data)
|
||||
.table_growing(current, desired, maximum)
|
||||
.as_mut(),
|
||||
)
|
||||
.expect("FIXME idk how to deal with a trap here!")
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn out_of_gas(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::{EntityIndex, GlobalIndex, MemoryIndex, Module, TableIndex};
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator,
|
||||
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator, StorePtr,
|
||||
VMFunctionImport, VMSharedSignatureIndex,
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ fn create_handle(
|
||||
imports,
|
||||
shared_signatures: shared_signature_id.into(),
|
||||
host_state,
|
||||
store: Some(store.traitobj()),
|
||||
store: StorePtr::new(store.traitobj()),
|
||||
wasm_data: &[],
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -9,7 +9,8 @@ use wasmtime_environ::{EntityIndex, Module, ModuleType, PrimaryMap, SignatureInd
|
||||
use wasmtime_jit::{CodeMemory, MmapVec};
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
|
||||
OnDemandInstanceAllocator, VMContext, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
||||
OnDemandInstanceAllocator, StorePtr, VMContext, VMFunctionBody, VMSharedSignatureIndex,
|
||||
VMTrampoline,
|
||||
};
|
||||
|
||||
struct TrampolineState<F> {
|
||||
@@ -131,7 +132,7 @@ pub unsafe fn create_raw_function(
|
||||
imports: Imports::default(),
|
||||
shared_signatures: sig.into(),
|
||||
host_state,
|
||||
store: None,
|
||||
store: StorePtr::empty(),
|
||||
wasm_data: &[],
|
||||
})?,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user