wasmcloud_runtime/
runtime.rs1use crate::{experimental::Features, ComponentConfig};
2
3use core::fmt;
4use core::fmt::Debug;
5use core::time::Duration;
6
7use std::thread;
8
9use anyhow::Context;
10use wasmtime::{InstanceAllocationStrategy, PoolingAllocationConfig};
11
12pub const MAX_LINEAR_MEMORY: u32 = 256 * 1024 * 1024;
14pub const MAX_COMPONENT_SIZE: u64 = 50 * 1024 * 1024;
16pub const MAX_COMPONENTS: u32 = 10_000;
18
19pub const DEFAULT_MAX_CORE_INSTANCES_PER_COMPONENT: u32 = 30;
21
22#[derive(Clone, Default)]
24pub struct RuntimeBuilder {
25 engine_config: wasmtime::Config,
26 max_core_instances_per_component: u32,
28 max_components: u32,
29 max_component_size: u64,
30 max_linear_memory: u32,
31 max_execution_time: Duration,
32 component_config: ComponentConfig,
33 force_pooling_allocator: bool,
34 experimental_features: Features,
35}
36
37impl RuntimeBuilder {
38 #[must_use]
40 pub fn new() -> Self {
41 let mut engine_config = wasmtime::Config::default();
42 engine_config.async_support(true);
43 engine_config.epoch_interruption(true);
44 engine_config.wasm_component_model(true);
45
46 Self {
47 engine_config,
48 max_components: MAX_COMPONENTS,
49 max_component_size: MAX_COMPONENT_SIZE,
52 max_linear_memory: MAX_LINEAR_MEMORY,
53 max_core_instances_per_component: DEFAULT_MAX_CORE_INSTANCES_PER_COMPONENT,
54 max_execution_time: Duration::from_secs(10 * 60),
55 component_config: ComponentConfig::default(),
56 force_pooling_allocator: false,
57 experimental_features: Features::default(),
58 }
59 }
60
61 #[must_use]
63 pub fn component_config(self, component_config: ComponentConfig) -> Self {
64 Self {
65 component_config,
66 ..self
67 }
68 }
69
70 #[must_use]
72 pub fn max_components(self, max_components: u32) -> Self {
73 Self {
74 max_components,
75 ..self
76 }
77 }
78
79 #[must_use]
81 pub fn max_core_instances_per_component(self, max_core_instances_per_component: u32) -> Self {
82 Self {
83 max_core_instances_per_component,
84 ..self
85 }
86 }
87
88 #[must_use]
90 pub fn max_component_size(self, max_component_size: u64) -> Self {
91 Self {
92 max_component_size,
93 ..self
94 }
95 }
96
97 #[must_use]
99 pub fn max_linear_memory(self, max_linear_memory: u32) -> Self {
100 Self {
101 max_linear_memory,
102 ..self
103 }
104 }
105
106 #[must_use]
110 pub fn max_execution_time(self, max_execution_time: Duration) -> Self {
111 Self {
112 max_execution_time: max_execution_time.max(Duration::from_secs(1)),
113 ..self
114 }
115 }
116
117 #[must_use]
119 pub fn force_pooling_allocator(self) -> Self {
120 Self {
121 force_pooling_allocator: true,
122 ..self
123 }
124 }
125
126 #[must_use]
128 pub fn experimental_features(self, experimental_features: Features) -> Self {
129 Self {
130 experimental_features,
131 ..self
132 }
133 }
134
135 #[allow(clippy::type_complexity)]
141 pub fn build(mut self) -> anyhow::Result<(Runtime, thread::JoinHandle<Result<(), ()>>)> {
142 let mut pooling_config = PoolingAllocationConfig::default();
143
144 let memories_per_component = 1;
149 let tables_per_component = 1;
150 let table_elements = 15000;
151
152 #[allow(clippy::cast_possible_truncation)]
153 pooling_config
154 .total_component_instances(self.max_components)
155 .total_core_instances(self.max_components)
156 .total_gc_heaps(self.max_components)
157 .total_stacks(self.max_components)
158 .max_component_instance_size(self.max_component_size as usize)
159 .max_core_instances_per_component(self.max_core_instances_per_component)
160 .max_tables_per_component(20)
161 .table_elements(table_elements)
162 .max_memories_per_component(
166 self.max_core_instances_per_component * memories_per_component,
167 )
168 .total_memories(self.max_components * memories_per_component)
169 .total_tables(self.max_components * tables_per_component)
170 .max_memory_size(self.max_linear_memory as usize)
176 .linear_memory_keep_resident(10 * 1024)
178 .table_keep_resident(10 * 1024);
179 self.engine_config
180 .allocation_strategy(InstanceAllocationStrategy::Pooling(pooling_config.clone()));
181 let engine = match wasmtime::Engine::new(&self.engine_config)
182 .context("failed to construct engine")
183 {
184 Ok(engine) => engine,
185 Err(e) if self.force_pooling_allocator => {
186 anyhow::bail!("failed to construct engine with pooling allocator: {}", e)
187 }
188 Err(e) => {
189 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.");
190 self.engine_config
191 .allocation_strategy(InstanceAllocationStrategy::OnDemand);
192 wasmtime::Engine::new(&self.engine_config).context("failed to construct engine")?
193 }
194 };
195 let epoch = {
196 let engine = engine.weak();
197 thread::spawn(move || loop {
198 thread::sleep(Duration::from_secs(1));
199 let Some(engine) = engine.upgrade() else {
200 return Ok(());
201 };
202 engine.increment_epoch();
203 })
204 };
205 let max_memory_limits = self.max_linear_memory as usize;
206 let pool_config = pooling_config.clone();
207 Ok((
208 Runtime {
209 engine_config: self.engine_config,
210 pooling_config: pool_config,
211 engine,
212 component_config: self.component_config,
213 max_execution_time: self.max_execution_time,
214 experimental_features: self.experimental_features,
215 max_linear_memory: max_memory_limits,
216 },
217 epoch,
218 ))
219 }
220}
221
222impl TryFrom<RuntimeBuilder> for (Runtime, thread::JoinHandle<Result<(), ()>>) {
223 type Error = anyhow::Error;
224
225 fn try_from(builder: RuntimeBuilder) -> Result<Self, Self::Error> {
226 builder.build()
227 }
228}
229
230#[derive(Clone)]
232pub struct Runtime {
233 pub(crate) engine_config: wasmtime::Config,
234 pub(crate) engine: wasmtime::Engine,
235 pub(crate) component_config: ComponentConfig,
236 pub(crate) max_execution_time: Duration,
237 pub(crate) experimental_features: Features,
238 pub(crate) pooling_config: wasmtime::PoolingAllocationConfig,
239 pub(crate) max_linear_memory: usize,
240}
241
242impl Debug for Runtime {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 f.debug_struct("Runtime")
245 .field("component_config", &self.component_config)
246 .field("runtime", &"wasmtime")
247 .field("max_execution_time", &"max_execution_time")
248 .field("pooling_config", &self.pooling_config)
249 .field("engine_config", &self.engine_config)
250 .finish_non_exhaustive()
251 }
252}
253
254impl Runtime {
255 #[allow(clippy::type_complexity)]
261 pub fn new() -> anyhow::Result<(Self, thread::JoinHandle<Result<(), ()>>)> {
262 Self::builder().try_into()
263 }
264
265 #[must_use]
267 pub fn builder() -> RuntimeBuilder {
268 RuntimeBuilder::new()
269 }
270
271 #[must_use]
273 pub fn version(&self) -> &'static str {
274 env!("CARGO_PKG_VERSION")
275 }
276
277 #[must_use]
279 pub fn engine(&self) -> &wasmtime::Engine {
280 &self.engine
281 }
282
283 pub(crate) fn skip_feature_gated_instance(&self, instance: &str) -> bool {
285 match instance {
286 "wasmcloud:messaging/producer@0.3.0"
287 | "wasmcloud:messaging/request-reply@0.3.0"
288 | "wasmcloud:messaging/types@0.3.0" => {
289 self.experimental_features.wasmcloud_messaging_v3
290 }
291 "wasmcloud:identity/store@0.0.1" => {
292 self.experimental_features.workload_identity_interface
293 }
294 "wrpc:rpc/context@0.1.0"
295 | "wrpc:rpc/error@0.1.0"
296 | "wrpc:rpc/invoker@0.1.0"
297 | "wrpc:rpc/transport@0.1.0" => self.experimental_features.rpc_interface,
298 _ => false,
299 }
300 }
301}