@@ -9,21 +9,23 @@ use crate::host::wasm_common::module_host_actor::{
99} ;
1010use crate :: host:: ArgsTuple ;
1111use crate :: { host:: Scheduler , module_host_context:: ModuleCreationContext , replica_context:: ReplicaContext } ;
12- use anyhow :: anyhow ;
12+ use core :: sync :: atomic :: { AtomicBool , Ordering } ;
1313use core:: time:: Duration ;
1414use de:: deserialize_js;
1515use error:: { catch_exception, exception_already_thrown, log_traceback, ExcResult , Throwable } ;
1616use from_value:: cast;
1717use key_cache:: get_or_create_key_cache;
1818use ser:: serialize_to_js;
19- use spacetimedb_client_api_messages:: energy:: { EnergyQuanta , ReducerBudget } ;
19+ use spacetimedb_client_api_messages:: energy:: ReducerBudget ;
2020use spacetimedb_datastore:: locking_tx_datastore:: MutTxId ;
2121use spacetimedb_datastore:: traits:: Program ;
2222use spacetimedb_lib:: RawModuleDef ;
2323use spacetimedb_lib:: { ConnectionId , Identity } ;
2424use spacetimedb_schema:: auto_migrate:: MigrationPolicy ;
2525use std:: sync:: { Arc , LazyLock } ;
26- use v8:: { Context , ContextOptions , ContextScope , Function , HandleScope , Isolate , Local , Value } ;
26+ use std:: thread;
27+ use std:: time:: Instant ;
28+ use v8:: { Context , ContextOptions , ContextScope , Function , HandleScope , Isolate , IsolateHandle , Local , Value } ;
2729
2830mod de;
2931mod error;
@@ -81,9 +83,13 @@ impl V8RuntimeInner {
8183 ) ;
8284
8385 if true {
84- return Err :: < JsModule , _ > ( anyhow ! ( "v8_todo" ) ) ;
86+ return Err :: < JsModule , _ > ( anyhow:: anyhow !( "v8_todo" ) ) ;
8587 }
8688
89+ // TODO(v8): determine min required ABI by module and check that it's supported?
90+
91+ // TODO(v8): validate function signatures like in WASM? Is that possible with V8?
92+
8793 let desc = todo ! ( ) ;
8894 // Validate and create a common module rom the raw definition.
8995 let common = build_common_module_from_raw ( mcc, desc) ?;
@@ -121,6 +127,17 @@ impl Module for JsModule {
121127 }
122128
123129 fn create_instance ( & self ) -> Self :: Instance {
130+ // TODO(v8): consider some equivalent to `epoch_deadline_callback`
131+ // where we report `Js has been running for ...`.
132+
133+ // TODO(v8): timeout things like `extract_description`.
134+
135+ // TODO(v8): do we care about preinits / setup or are they unnecessary?
136+
137+ // TODO(v8): create `InstanceEnv`.
138+
139+ // TODO(v8): extract description.
140+
124141 todo ! ( )
125142 }
126143}
@@ -147,35 +164,49 @@ impl ModuleInstance for JsInstance {
147164 }
148165
149166 fn call_reducer ( & mut self , tx : Option < MutTxId > , params : CallReducerParams ) -> super :: ReducerCallResult {
150- // TODO(centril): snapshots, module->host calls
151- let mut isolate = Isolate :: new ( <_ >:: default ( ) ) ;
152- let scope = & mut HandleScope :: new ( & mut isolate) ;
153- let context = Context :: new ( scope, ContextOptions :: default ( ) ) ;
154- let scope = & mut ContextScope :: new ( scope, context) ;
155-
156167 self . common . call_reducer_with_tx (
157168 & self . replica_ctx . clone ( ) ,
158169 tx,
159170 params,
160171 log_traceback,
161- |tx, op, _budget| {
162- let call_result = call_call_reducer_from_op ( scope, op) ;
163- // TODO(centril): energy metrering.
164- let energy = EnergyStats {
165- used : EnergyQuanta :: ZERO ,
166- wasmtime_fuel_used : 0 ,
167- remaining : ReducerBudget :: ZERO ,
168- } ;
169- // TODO(centril): timings.
172+ |tx, op, budget| {
173+ // TODO(v8): snapshots, module->host calls
174+ // Setup V8 scope.
175+ let mut isolate: v8:: OwnedIsolate = Isolate :: new ( <_ >:: default ( ) ) ;
176+ let isolate_handle = isolate. thread_safe_handle ( ) ;
177+ let mut scope_1 = HandleScope :: new ( & mut isolate) ;
178+ let context = Context :: new ( & mut scope_1, ContextOptions :: default ( ) ) ;
179+ let mut scope_2 = ContextScope :: new ( & mut scope_1, context) ;
180+
181+ let timeout_thread_cancel_flag = run_reducer_timeout ( isolate_handle, budget) ;
182+
183+ // Call the reducer.
184+ let start = Instant :: now ( ) ;
185+ let call_result = call_call_reducer_from_op ( & mut scope_2, op) ;
186+ let total_duration = start. elapsed ( ) ;
187+ // Cancel the execution timeout in `run_reducer_timeout`.
188+ timeout_thread_cancel_flag. store ( true , Ordering :: Relaxed ) ;
189+
190+ // Handle energy and timings.
191+ let used = duration_to_budget ( total_duration) ;
192+ let remaining = budget - used;
193+ let energy = EnergyStats { budget, remaining } ;
170194 let timings = ExecutionTimings {
171- total_duration : Duration :: ZERO ,
195+ total_duration,
196+ // TODO(v8): call times.
172197 wasm_instance_env_call_times : CallTimes :: new ( ) ,
173198 } ;
199+
200+ // Fetch the currently used heap size in V8.
201+ // The used size is ostensibly fairer than the total size.
202+ drop ( scope_2) ;
203+ drop ( scope_1) ;
204+ let memory_allocation = isolate. get_heap_statistics ( ) . used_heap_size ( ) ;
205+
174206 let exec_result = ExecuteResult {
175207 energy,
176208 timings,
177- // TODO(centril): memory allocation.
178- memory_allocation : 0 ,
209+ memory_allocation,
179210 call_result,
180211 } ;
181212 ( tx, exec_result)
@@ -184,6 +215,45 @@ impl ModuleInstance for JsInstance {
184215 }
185216}
186217
218+ /// Spawns a thread that will terminate reducer execution
219+ /// when `budget` has been used up.
220+ fn run_reducer_timeout ( isolate_handle : IsolateHandle , budget : ReducerBudget ) -> Arc < AtomicBool > {
221+ let execution_done_flag = Arc :: new ( AtomicBool :: new ( false ) ) ;
222+ let execution_done_flag2 = execution_done_flag. clone ( ) ;
223+ let timeout = budget_to_duration ( budget) ;
224+
225+ // TODO(v8): Using an OS thread is a bit heavy handed...?
226+ thread:: spawn ( move || {
227+ // Sleep until the timeout.
228+ thread:: sleep ( timeout) ;
229+
230+ if execution_done_flag2. load ( Ordering :: Relaxed ) {
231+ // The reducer completed successfully.
232+ return ;
233+ }
234+
235+ // Reducer is still running.
236+ // Terminate V8 execution.
237+ isolate_handle. terminate_execution ( ) ;
238+ } ) ;
239+
240+ execution_done_flag
241+ }
242+
243+ /// Converts a [`ReducerBudget`] to a [`Duration`].
244+ fn budget_to_duration ( _budget : ReducerBudget ) -> Duration {
245+ // TODO(v8): This is fake logic that allows a maximum timeout.
246+ // Replace with sensible math.
247+ Duration :: MAX
248+ }
249+
250+ /// Converts a [`Duration`] to a [`ReducerBudget`].
251+ fn duration_to_budget ( _duration : Duration ) -> ReducerBudget {
252+ // TODO(v8): This is fake logic that allows minimum energy usage.
253+ // Replace with sensible math.
254+ ReducerBudget :: ZERO
255+ }
256+
187257/// Returns the global property `key`.
188258fn get_global_property < ' scope > (
189259 scope : & mut HandleScope < ' scope > ,
0 commit comments