Skip to content

Commit 6a92b92

Browse files
committed
wasm: refactor energy; v8: energy, timings, and timeout
1 parent dbc44ad commit 6a92b92

3 files changed

Lines changed: 70 additions & 28 deletions

File tree

crates/core/src/host/v8/mod.rs

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ use crate::host::wasm_common::module_host_actor::{
1010
use crate::host::ArgsTuple;
1111
use crate::{host::Scheduler, module_host_context::ModuleCreationContext, replica_context::ReplicaContext};
1212
use anyhow::anyhow;
13-
use std::time::Instant;
13+
use core::sync::atomic::{AtomicBool, Ordering};
14+
use core::time::Duration;
1415
use de::deserialize_js;
1516
use error::{catch_exception, exception_already_thrown, log_traceback, ExcResult, Throwable};
1617
use from_value::cast;
1718
use key_cache::get_or_create_key_cache;
1819
use ser::serialize_to_js;
19-
use spacetimedb_client_api_messages::energy::{EnergyQuanta, ReducerBudget};
20+
use spacetimedb_client_api_messages::energy::ReducerBudget;
2021
use spacetimedb_datastore::locking_tx_datastore::MutTxId;
2122
use spacetimedb_datastore::traits::Program;
2223
use spacetimedb_lib::RawModuleDef;
2324
use spacetimedb_lib::{ConnectionId, Identity};
2425
use spacetimedb_schema::auto_migrate::MigrationPolicy;
2526
use std::sync::{Arc, LazyLock};
26-
use v8::{Context, ContextOptions, ContextScope, Function, HandleScope, Isolate, Local, Value};
27+
use std::thread;
28+
use std::time::Instant;
29+
use v8::{Context, ContextOptions, ContextScope, Function, HandleScope, Isolate, IsolateHandle, Local, Value};
2730

2831
mod de;
2932
mod error;
@@ -152,25 +155,28 @@ impl ModuleInstance for JsInstance {
152155
tx,
153156
params,
154157
log_traceback,
155-
|tx, op, _budget| {
158+
|tx, op, budget| {
156159
// TODO(centril): snapshots, module->host calls
157160
// Setup V8 scope.
158161
let mut isolate: v8::OwnedIsolate = Isolate::new(<_>::default());
162+
let isolate_handle = isolate.thread_safe_handle();
159163
let mut scope_1 = HandleScope::new(&mut isolate);
160164
let context = Context::new(&mut scope_1, ContextOptions::default());
161165
let mut scope_2 = ContextScope::new(&mut scope_1, context);
162166

167+
let timeout_thread_cancel_flag = run_reducer_timeout(isolate_handle, budget);
168+
163169
// Call the reducer.
164170
let start = Instant::now();
165171
let call_result = call_call_reducer_from_op(&mut scope_2, op);
166172
let total_duration = start.elapsed();
173+
// Cancel the execution timeout in `run_reducer_timeout`.
174+
timeout_thread_cancel_flag.store(true, Ordering::Relaxed);
167175

168-
// TODO(centril): energy metrering.
169-
let energy = EnergyStats {
170-
used: EnergyQuanta::ZERO,
171-
wasmtime_fuel_used: 0,
172-
remaining: ReducerBudget::ZERO,
173-
};
176+
// Handle energy and timings.
177+
let used = duration_to_budget(total_duration);
178+
let remaining = budget - used;
179+
let energy = EnergyStats { budget, remaining };
174180
let timings = ExecutionTimings {
175181
total_duration,
176182
// TODO(centril): call times.
@@ -195,6 +201,41 @@ impl ModuleInstance for JsInstance {
195201
}
196202
}
197203

204+
fn run_reducer_timeout(isolate_handle: IsolateHandle, budget: ReducerBudget) -> Arc<AtomicBool> {
205+
let execution_done_flag = Arc::new(AtomicBool::new(false));
206+
let execution_done_flag2 = execution_done_flag.clone();
207+
let timeout = budget_to_duration(budget);
208+
209+
// TODO(centril): Using an OS thread is a bit heavy handed...?
210+
thread::spawn(move || {
211+
// Sleep until the timeout.
212+
thread::sleep(timeout);
213+
214+
if execution_done_flag2.load(Ordering::Relaxed) {
215+
// The reducer completed successfully.
216+
return;
217+
}
218+
219+
// Reducer is still running.
220+
// Terminate V8 execution.
221+
isolate_handle.terminate_execution();
222+
});
223+
224+
execution_done_flag
225+
}
226+
227+
fn budget_to_duration(_budget: ReducerBudget) -> Duration {
228+
// TODO(centril): This is fake logic that allows a maximum timeout.
229+
// Replace with sensible math.
230+
Duration::MAX
231+
}
232+
233+
fn duration_to_budget(_duration: Duration) -> ReducerBudget {
234+
// TODO(centril): This is fake logic that allows minimum energy usage.
235+
// Replace with sensible math.
236+
ReducerBudget::ZERO
237+
}
238+
198239
/// Returns the global property `key`.
199240
fn get_global_property<'scope>(
200241
scope: &mut HandleScope<'scope>,

crates/core/src/host/wasm_common/module_host_actor.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tracing::span::EnteredSpan;
88
use super::instrumentation::CallTimes;
99
use crate::client::ClientConnectionSender;
1010
use crate::database_logger;
11-
use crate::energy::{EnergyMonitor, EnergyQuanta, ReducerBudget, ReducerFingerprint};
11+
use crate::energy::{EnergyMonitor, ReducerBudget, ReducerFingerprint};
1212
use crate::host::instance_env::InstanceEnv;
1313
use crate::host::module_common::{build_common_module_from_raw, ModuleCommon};
1414
use crate::host::module_host::{
@@ -60,11 +60,17 @@ pub trait WasmInstance: Send + Sync + 'static {
6060
}
6161

6262
pub struct EnergyStats {
63-
pub used: EnergyQuanta,
64-
pub wasmtime_fuel_used: u64,
63+
pub budget: ReducerBudget,
6564
pub remaining: ReducerBudget,
6665
}
6766

67+
impl EnergyStats {
68+
/// Returns the used energy amount.
69+
fn used(&self) -> ReducerBudget {
70+
(self.budget.get() - self.remaining.get()).into()
71+
}
72+
}
73+
6874
pub struct ExecutionTimings {
6975
pub total_duration: Duration,
7076
pub wasm_instance_env_call_times: CallTimes,
@@ -412,22 +418,24 @@ impl InstanceCommon {
412418
call_result,
413419
} = result;
414420

421+
let energy_used = energy.used();
422+
let energy_quanta_used = energy_used.into();
415423
vm_metrics.report(
416-
energy.wasmtime_fuel_used,
424+
energy_used.get(),
417425
timings.total_duration,
418426
&timings.wasm_instance_env_call_times,
419427
);
420428

421429
self.energy_monitor
422-
.record_reducer(&energy_fingerprint, energy.used, timings.total_duration);
430+
.record_reducer(&energy_fingerprint, energy_quanta_used, timings.total_duration);
423431
if self.allocated_memory != memory_allocation {
424432
self.metric_wasm_memory_bytes.set(memory_allocation as i64);
425433
self.allocated_memory = memory_allocation;
426434
}
427435

428436
reducer_span
429437
.record("timings.total_duration", tracing::field::debug(timings.total_duration))
430-
.record("energy.used", tracing::field::debug(energy.used));
438+
.record("energy.used", tracing::field::debug(energy_used));
431439

432440
maybe_log_long_running_reducer(reducer_name, timings.total_duration);
433441
reducer_span.exit();
@@ -486,7 +494,7 @@ impl InstanceCommon {
486494
args,
487495
},
488496
status,
489-
energy_quanta_used: energy.used,
497+
energy_quanta_used,
490498
host_execution_duration: timings.total_duration,
491499
request_id,
492500
timer,
@@ -495,7 +503,7 @@ impl InstanceCommon {
495503

496504
ReducerCallResult {
497505
outcome: ReducerOutcome::from(&event.status),
498-
energy_used: energy.used,
506+
energy_used: energy_quanta_used,
499507
execution_duration: timings.total_duration,
500508
}
501509
}

crates/core/src/host/wasmtime/wasmtime_module.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,8 @@ impl module_host_actor::WasmInstance for WasmtimeInstance {
192192
#[tracing::instrument(level = "trace", skip_all)]
193193
fn call_reducer(&mut self, op: ReducerOp<'_>, budget: ReducerBudget) -> module_host_actor::ExecuteResult {
194194
let store = &mut self.store;
195-
// note that ReducerBudget being a u64 is load-bearing here - although we convert budget right back into
196-
// EnergyQuanta at the end of this function, from_energy_quanta clamps it to a u64 range.
197-
// otherwise, we'd return something like `used: i128::MAX - u64::MAX`, which is inaccurate.
195+
// Set the fuel budget in WASM.
198196
set_store_fuel(store, budget.into());
199-
let original_fuel = get_store_fuel(store);
200197
store.set_epoch_deadline(EPOCH_TICKS_PER_SECOND);
201198

202199
// Prepare sender identity and connection ID, as LITTLE-ENDIAN byte arrays.
@@ -231,14 +228,10 @@ impl module_host_actor::WasmInstance for WasmtimeInstance {
231228

232229
let call_result = call_result.map(|code| handle_error_sink_code(code, error));
233230

231+
// Compute fuel and heap usage.
234232
let remaining_fuel = get_store_fuel(store);
235-
236233
let remaining: ReducerBudget = remaining_fuel.into();
237-
let energy = module_host_actor::EnergyStats {
238-
used: (budget - remaining).into(),
239-
wasmtime_fuel_used: original_fuel.0 - remaining_fuel.0,
240-
remaining,
241-
};
234+
let energy = module_host_actor::EnergyStats { budget, remaining };
242235
let memory_allocation = store.data().get_mem().memory.data_size(&store);
243236

244237
module_host_actor::ExecuteResult {

0 commit comments

Comments
 (0)