@@ -11,16 +11,18 @@ use spacetimedb_paths::server::ServerDataDir;
1111use std:: borrow:: Cow ;
1212use std:: time:: Duration ;
1313use wasmtime:: { self , Engine , Linker , StoreContext , StoreContextMut } ;
14- pub use wasmtime_module:: { WasmtimeInstance , WasmtimeModule } ;
14+ pub use wasmtime_module:: { WasmtimeAsyncModule , WasmtimeInstance , WasmtimeModule } ;
1515
1616#[ cfg( unix) ]
1717mod pooling_stack_creator;
1818mod wasm_instance_env;
1919mod wasmtime_module;
2020
2121pub struct WasmtimeRuntime {
22- engine : Engine ,
23- linker : Box < Linker < WasmInstanceEnv > > ,
22+ sync_engine : Engine ,
23+ sync_linker : Box < Linker < WasmInstanceEnv > > ,
24+ async_engine : Engine ,
25+ async_linker : Box < Linker < WasmInstanceEnv > > ,
2426 config : WasmConfig ,
2527}
2628
@@ -46,76 +48,108 @@ pub(crate) fn epoch_ticker(mut on_tick: impl 'static + Send + FnMut() -> Option<
4648
4749impl WasmtimeRuntime {
4850 pub fn new ( data_dir : Option < & ServerDataDir > , runtime_config : WasmConfig ) -> Self {
49- let mut config = wasmtime:: Config :: new ( ) ;
50- config
51- . cranelift_opt_level ( wasmtime:: OptLevel :: Speed )
52- . consume_fuel ( true )
53- . epoch_interruption ( true )
54- . wasm_backtrace_details ( wasmtime:: WasmBacktraceDetails :: Enable )
55- // We need async support to enable suspending execution of procedures
56- // when waiting for e.g. HTTP responses or the transaction lock.
57- // We don't enable either fuel-based or epoch-based yielding
58- // (see https://docs.wasmtime.dev/api/wasmtime/struct.Store.html#method.epoch_deadline_async_yield_and_update
59- // and https://docs.wasmtime.dev/api/wasmtime/struct.Store.html#method.fuel_async_yield_interval)
60- // so reducers will always execute to completion during the first `Future::poll` call,
61- // and procedures will only yield when performing an asynchronous operation.
62- // These futures are executed on a separate single-threaded executor not related to the "global" Tokio runtime,
63- // which is responsible only for executing WASM. See `crate::util::jobs` for this infrastructure.
64- . async_support ( true ) ;
51+ let sync_config = wasmtime_config ( data_dir, false ) ;
52+ let async_config = wasmtime_config ( data_dir, true ) ;
6553
66- #[ cfg( unix) ]
67- config
68- . async_stack_size ( self :: pooling_stack_creator:: ASYNC_STACK_SIZE )
69- . with_host_stack ( self :: pooling_stack_creator:: PoolingStackCreator :: new ( ) ) ;
54+ let sync_engine = Engine :: new ( & sync_config) . unwrap ( ) ;
55+ let async_engine = Engine :: new ( & async_config) . unwrap ( ) ;
7056
71- // Offer a compile-time flag for enabling perfmap generation,
72- // so `perf` can display JITted symbol names.
73- // Ideally we would be able to configure this at runtime via a flag to `spacetime start`,
74- // but this is good enough for now.
75- #[ cfg( feature = "perfmap" ) ]
76- config. profiler ( wasmtime:: ProfilingStrategy :: PerfMap ) ;
77-
78- if let Some ( data_dir) = data_dir {
79- let mut cache_config = wasmtime:: CacheConfig :: new ( ) ;
80- cache_config. with_directory ( data_dir. wasmtime_cache ( ) . 0 ) ;
81- match wasmtime:: Cache :: new ( cache_config) {
82- Ok ( cache) => {
83- config. cache ( Some ( cache) ) ;
84- }
85- Err ( e) => {
86- // caching is just an optimization, so if it fails, just log and continue
87- tracing:: warn!( "failed to set up wasmtime cache: {e:#}" )
88- }
57+ let weak_sync_engine = sync_engine. weak ( ) ;
58+ let weak_async_engine = async_engine. weak ( ) ;
59+ epoch_ticker ( move || {
60+ let mut ticked = false ;
61+ if let Some ( engine) = weak_sync_engine. upgrade ( ) {
62+ engine. increment_epoch ( ) ;
63+ ticked = true ;
8964 }
65+ if let Some ( engine) = weak_async_engine. upgrade ( ) {
66+ engine. increment_epoch ( ) ;
67+ ticked = true ;
68+ }
69+ ticked. then_some ( ( ) )
70+ } ) ;
71+
72+ let mut sync_linker = Box :: new ( Linker :: new ( & sync_engine) ) ;
73+ WasmtimeModule :: link_imports ( & mut sync_linker) . unwrap ( ) ;
74+
75+ let mut async_linker = Box :: new ( Linker :: new ( & async_engine) ) ;
76+ WasmtimeAsyncModule :: link_imports ( & mut async_linker) . unwrap ( ) ;
77+
78+ let config = runtime_config;
79+ WasmtimeRuntime {
80+ sync_engine,
81+ sync_linker,
82+ async_engine,
83+ async_linker,
84+ config,
9085 }
86+ }
87+ }
9188
92- let engine = Engine :: new ( & config) . unwrap ( ) ;
89+ fn wasmtime_config ( data_dir : Option < & ServerDataDir > , async_support : bool ) -> wasmtime:: Config {
90+ let mut config = wasmtime:: Config :: new ( ) ;
91+ config
92+ . cranelift_opt_level ( wasmtime:: OptLevel :: Speed )
93+ . consume_fuel ( true )
94+ . epoch_interruption ( true )
95+ . wasm_backtrace_details ( wasmtime:: WasmBacktraceDetails :: Enable ) ;
9396
94- let weak_engine = engine. weak ( ) ;
95- epoch_ticker ( move || {
96- let engine = weak_engine. upgrade ( ) ?;
97- engine. increment_epoch ( ) ;
98- Some ( ( ) )
99- } ) ;
97+ if async_support {
98+ // Procedure instances need async support to suspend execution when waiting for
99+ // e.g. HTTP responses or the transaction lock. Main-lane instances use a
100+ // separate sync engine so reducers/views do not pay Wasmtime's fiber overhead.
101+ config. async_support ( true ) ;
100102
101- let mut linker = Box :: new ( Linker :: new ( & engine) ) ;
102- WasmtimeModule :: link_imports ( & mut linker) . unwrap ( ) ;
103+ #[ cfg( unix) ]
104+ config
105+ . async_stack_size ( self :: pooling_stack_creator:: ASYNC_STACK_SIZE )
106+ . with_host_stack ( self :: pooling_stack_creator:: PoolingStackCreator :: new ( ) ) ;
107+ }
103108
104- let config = runtime_config;
105- WasmtimeRuntime { engine, linker, config }
109+ // Offer a compile-time flag for enabling perfmap generation,
110+ // so `perf` can display JITted symbol names.
111+ // Ideally we would be able to configure this at runtime via a flag to `spacetime start`,
112+ // but this is good enough for now.
113+ #[ cfg( feature = "perfmap" ) ]
114+ config. profiler ( wasmtime:: ProfilingStrategy :: PerfMap ) ;
115+
116+ if let Some ( data_dir) = data_dir {
117+ let mut cache_config = wasmtime:: CacheConfig :: new ( ) ;
118+ cache_config. with_directory ( data_dir. wasmtime_cache ( ) . 0 ) ;
119+ match wasmtime:: Cache :: new ( cache_config) {
120+ Ok ( cache) => {
121+ config. cache ( Some ( cache) ) ;
122+ }
123+ Err ( e) => {
124+ // caching is just an optimization, so if it fails, just log and continue
125+ tracing:: warn!( "failed to set up wasmtime cache: {e:#}" )
126+ }
127+ }
106128 }
129+
130+ config
107131}
108132
109133pub type Module = WasmModuleHostActor < WasmtimeModule > ;
134+ pub type ProcedureModule = WasmModuleHostActor < WasmtimeAsyncModule > ;
110135pub type ModuleInstance = WasmModuleInstance < WasmtimeInstance > ;
111136
112- const THREAD_NAME_DATABASE_ID_SUFFIX_LEN : usize = 8 ;
137+ // Linux thread names expose at most 15 bytes, so keep the database identity
138+ // suffix short enough to survive after the `wasm-main-`/`wasm-proc-` prefix.
139+ const THREAD_NAME_DATABASE_ID_SUFFIX_LEN : usize = 5 ;
140+
141+ fn wasm_main_worker_thread_name ( database_identity : & spacetimedb_lib:: Identity ) -> String {
142+ let hex = database_identity. to_hex ( ) ;
143+ // We use the tail of the identity to avoid the common structured prefix.
144+ let suffix = & hex. as_str ( ) [ hex. as_str ( ) . len ( ) - THREAD_NAME_DATABASE_ID_SUFFIX_LEN ..] ;
145+ format ! ( "wasm-main-{suffix}" )
146+ }
113147
114- fn wasm_executor_thread_name ( database_identity : & spacetimedb_lib:: Identity ) -> String {
148+ fn wasm_procedure_executor_thread_name ( database_identity : & spacetimedb_lib:: Identity ) -> String {
115149 let hex = database_identity. to_hex ( ) ;
116150 // We use the tail of the identity to avoid the common structured prefix.
117151 let suffix = & hex. as_str ( ) [ hex. as_str ( ) . len ( ) - THREAD_NAME_DATABASE_ID_SUFFIX_LEN ..] ;
118- format ! ( "wasm-{suffix}" )
152+ format ! ( "wasm-proc- {suffix}" )
119153}
120154
121155impl WasmtimeRuntime {
@@ -126,7 +160,7 @@ impl WasmtimeRuntime {
126160 core : AllocatedJobCore ,
127161 ) -> anyhow:: Result < super :: module_host:: ModuleWithInstance > {
128162 let module =
129- wasmtime:: Module :: new ( & self . engine , program_bytes) . map_err ( ModuleCreationError :: WasmCompileError ) ?;
163+ wasmtime:: Module :: new ( & self . sync_engine , program_bytes) . map_err ( ModuleCreationError :: WasmCompileError ) ?;
130164
131165 let func_imports = module
132166 . imports ( )
@@ -136,17 +170,29 @@ impl WasmtimeRuntime {
136170 abi:: verify_supported ( WasmtimeModule :: IMPLEMENTED_ABI , abi) ?;
137171
138172 let module = self
139- . linker
173+ . sync_linker
140174 . instantiate_pre ( & module)
141175 . map_err ( InitializationError :: Instantiation ) ?;
176+ let procedure_module =
177+ wasmtime:: Module :: new ( & self . async_engine , program_bytes) . map_err ( ModuleCreationError :: WasmCompileError ) ?;
178+ let procedure_module = self
179+ . async_linker
180+ . instantiate_pre ( & procedure_module)
181+ . map_err ( InitializationError :: Instantiation ) ?;
142182
143183 let module = WasmtimeModule :: new ( module) ;
144- let executor_thread_name = wasm_executor_thread_name ( & mcc. replica_ctx . database_identity ) ;
184+ let procedure_module = WasmtimeAsyncModule :: new ( procedure_module) ;
185+ let main_thread_name = wasm_main_worker_thread_name ( & mcc. replica_ctx . database_identity ) ;
186+ let procedure_thread_name = wasm_procedure_executor_thread_name ( & mcc. replica_ctx . database_identity ) ;
145187
146188 let ( module, init_inst) = WasmModuleHostActor :: new ( mcc, module) ?;
189+ let procedure_module = module. with_runtime_module ( procedure_module) ?;
147190 Ok ( super :: module_host:: ModuleWithInstance :: Wasm {
148191 module,
149- executor : core. spawn_named_async_executor ( executor_thread_name) ,
192+ procedure_module,
193+ main_thread_name,
194+ procedure_thread_name,
195+ core,
150196 init_inst : Box :: new ( init_inst) ,
151197 procedure_instance_pool_size : self . config . procedure_instance_pool_size ,
152198 } )
0 commit comments