use crate::ComponentConfig;
use core::fmt;
use core::fmt::Debug;
use core::time::Duration;
use std::thread;
use anyhow::Context;
use wasmtime::{InstanceAllocationStrategy, PoolingAllocationConfig};
pub const MAX_LINEAR_MEMORY: u64 = 256 * 1024 * 1024;
pub const MAX_COMPONENT_SIZE: u64 = 50 * 1024 * 1024;
pub const MAX_COMPONENTS: u32 = 10_000;
#[derive(Clone, Default)]
pub struct RuntimeBuilder {
engine_config: wasmtime::Config,
max_components: u32,
max_component_size: u64,
max_linear_memory: u64,
max_execution_time: Duration,
component_config: ComponentConfig,
force_pooling_allocator: bool,
}
impl RuntimeBuilder {
#[must_use]
pub fn new() -> Self {
let mut engine_config = wasmtime::Config::default();
engine_config.async_support(true);
engine_config.epoch_interruption(true);
engine_config.memory_init_cow(false);
engine_config.wasm_component_model(true);
Self {
engine_config,
max_components: MAX_COMPONENTS,
max_component_size: MAX_COMPONENT_SIZE,
max_linear_memory: MAX_LINEAR_MEMORY,
max_execution_time: Duration::from_secs(10 * 60),
component_config: ComponentConfig::default(),
force_pooling_allocator: false,
}
}
#[must_use]
pub fn component_config(self, component_config: ComponentConfig) -> Self {
Self {
component_config,
..self
}
}
#[must_use]
pub fn max_components(self, max_components: u32) -> Self {
Self {
max_components,
..self
}
}
#[must_use]
pub fn max_component_size(self, max_component_size: u64) -> Self {
Self {
max_component_size,
..self
}
}
#[must_use]
pub fn max_linear_memory(self, max_linear_memory: u64) -> Self {
Self {
max_linear_memory,
..self
}
}
#[must_use]
pub fn max_execution_time(self, max_execution_time: Duration) -> Self {
Self {
max_execution_time: max_execution_time.max(Duration::from_secs(1)),
..self
}
}
#[must_use]
pub fn force_pooling_allocator(self) -> Self {
Self {
force_pooling_allocator: true,
..self
}
}
#[allow(clippy::type_complexity)]
pub fn build(mut self) -> anyhow::Result<(Runtime, thread::JoinHandle<Result<(), ()>>)> {
let mut pooling_config = PoolingAllocationConfig::default();
let memories_per_component = 1;
let tables_per_component = 1;
let max_core_instances_per_component = 30;
let table_elements = 15000;
#[allow(clippy::cast_possible_truncation)]
pooling_config
.total_component_instances(self.max_components)
.total_core_instances(self.max_components)
.total_gc_heaps(self.max_components)
.total_stacks(self.max_components)
.max_component_instance_size(self.max_component_size as usize)
.max_core_instances_per_component(max_core_instances_per_component)
.max_tables_per_component(20)
.table_elements(table_elements)
.max_memories_per_component(max_core_instances_per_component * memories_per_component)
.total_memories(self.max_components * memories_per_component)
.total_tables(self.max_components * tables_per_component)
.max_memory_size(self.max_linear_memory as usize)
.linear_memory_keep_resident(10 * 1024)
.table_keep_resident(10 * 1024);
self.engine_config
.allocation_strategy(InstanceAllocationStrategy::Pooling(pooling_config));
let engine = match wasmtime::Engine::new(&self.engine_config)
.context("failed to construct engine")
{
Ok(engine) => engine,
Err(e) if self.force_pooling_allocator => {
anyhow::bail!("failed to construct engine with pooling allocator: {}", e)
}
Err(e) => {
tracing::warn!(err = %e, "failed to construct engine with pooling allocator, falling back to dynamic allocator which may result in slower startup and execution of components.");
self.engine_config
.allocation_strategy(InstanceAllocationStrategy::OnDemand);
wasmtime::Engine::new(&self.engine_config).context("failed to construct engine")?
}
};
let epoch = {
let engine = engine.weak();
thread::spawn(move || loop {
thread::sleep(Duration::from_secs(1));
let Some(engine) = engine.upgrade() else {
return Ok(());
};
engine.increment_epoch();
})
};
Ok((
Runtime {
engine,
component_config: self.component_config,
max_execution_time: self.max_execution_time,
},
epoch,
))
}
}
impl TryFrom<RuntimeBuilder> for (Runtime, thread::JoinHandle<Result<(), ()>>) {
type Error = anyhow::Error;
fn try_from(builder: RuntimeBuilder) -> Result<Self, Self::Error> {
builder.build()
}
}
#[derive(Clone)]
pub struct Runtime {
pub(crate) engine: wasmtime::Engine,
pub(crate) component_config: ComponentConfig,
pub(crate) max_execution_time: Duration,
}
impl Debug for Runtime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Runtime")
.field("component_config", &self.component_config)
.field("runtime", &"wasmtime")
.field("max_execution_time", &"max_execution_time")
.finish_non_exhaustive()
}
}
impl Runtime {
#[allow(clippy::type_complexity)]
pub fn new() -> anyhow::Result<(Self, thread::JoinHandle<Result<(), ()>>)> {
Self::builder().try_into()
}
#[must_use]
pub fn builder() -> RuntimeBuilder {
RuntimeBuilder::new()
}
#[must_use]
pub fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
}