AngelicosPhosphoros opened issue #13338:
I have a question about using fuel in conjuction with
Scenario: suppose we run some long-running calculation but want to let a guest only use some amount of fuel before interruption and continue in some future (imagine running a WASM plugin for a game engine that compute something in the background and we let the computation progress for 1000 fuel in each event loop iteration).
How can we achieve that?
Documentation suggests using
Store::set_fuelfor that, but it is not possible because theStoreis mutably borrowed by the asynchronous guest call until it finishes, and it finishes when the computation is finished.So I do not understand how I can add fuel to ongoing computation between yields.
Example of my code for now:
use wasmtime::component::{Component, Linker}; use wasmtime::{Config, component}; use wasmtime::{Engine, Store}; use crate::nbody::Output; component::bindgen!({ world: "the-world", inline: " package nbody:nbody@0.1.0; interface nbody-calc { record output { original: f64, final: f64, } compute: func(steps: u32) -> output; } world the-world { export nbody-calc; } ", exports: { default: async, } }); const WASM_BYTES: &[u8] = include_bytes!("../../target/wasm32-wasip2/release/nbody_rs.wasm"); #[must_use] #[allow(clippy::missing_panics_doc)] // hardcoded wasm pub fn wasm_prepare() -> (Store<()>, TheWorld) { let mut config = Config::new(); config.consume_fuel(true); let engine = Engine::new(&config).unwrap(); let component = Component::from_binary(&engine, WASM_BYTES).unwrap(); let linker = Linker::new(&engine); let mut store: Store<()> = Store::new(&engine, ()); let bindings: TheWorld = TheWorld::instantiate(&mut store, &component, &linker).unwrap(); (store, bindings) } #[must_use] #[allow(clippy::missing_panics_doc)] // hardcoded wasm pub fn wasm_compute(store: &mut Store<()>, bindings: &TheWorld, steps: u32) -> Output { store.set_fuel(1000).unwrap(); let res = pollster::block_on(async { // Where I put set_fuel(1000) to add fuel // in case of exhausting all fuel? bindings .nbody_nbody_nbody_calc() .call_compute(store, steps) .await .unwrap() }); Output { original: res.original, final_: res.final_, } }
AngelicosPhosphoros commented on issue #13338:
I think, I found the way:
pub fn wasm_compute(store: &mut Store<()>, bindings: &TheWorld, steps: u32) -> Output { use std::task::{Context, Poll, Wake}; struct DummyWaker; impl Wake for DummyWaker { fn wake(self: Arc<Self>) {} } store.set_fuel(u64::MAX).unwrap(); store.fuel_async_yield_interval(Some(1000)).unwrap(); let mut fut = core::pin::pin!( the_world .nbody_nbody_nbody_calc() .call_compute(store, steps) ); let res = loop { let waker = Arc::new(DummyWaker).into(); let mut ctx = Context::from_waker(&waker); match fut.as_mut().poll(&mut ctx) { Poll::Ready(res) => break res, Poll::Pending => { // Can do something here. // Like saving the pinned future, // returning from a function // and running core event loop once // before returning back here. } } }; let res = res.unwrap(); Output { original: res.original, final_: res.final_, } }
alexcrichton commented on issue #13338:
In addition to what you already have, I think
epoch_deadline_callbackwould also solve this?
Last updated: Jun 01 2026 at 09:49 UTC