Stream: git-wasmtime

Topic: wasmtime / issue #13338 How to add fuel when yielding exe...


view this post on Zulip Wasmtime GitHub notifications bot (May 11 2026 at 22:48):

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_fuel for that, but it is not possible because the Store is 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_,
    }
}

view this post on Zulip Wasmtime GitHub notifications bot (May 11 2026 at 23:42):

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_,
    }
}

view this post on Zulip Wasmtime GitHub notifications bot (May 11 2026 at 23:55):

alexcrichton commented on issue #13338:

In addition to what you already have, I think epoch_deadline_callback would also solve this?

view this post on Zulip Wasmtime GitHub notifications bot (Aug 13 2026 at 14:41):

dishmaker commented on issue #13338:

Can I use epoch_deadline_callback to implement hardware interrupts?
Or is it illegal/unsafe?

let interrupt_handler = instance.get_typed_func::<(), (i32,)>(&mut store, "my_hw_interrupt_handler")?;

store.epoch_deadline_callback(move |mut store| {
    // simulate hardware interrupt
    let fut = interrupt_handler.call_async(&mut store, ());
    let fut = pin!(fut);
    let _poll = fut.poll(&mut Context::from_waker(&Waker::noop()));
    // returns Poll::Ready(Err("wasm trap: interrupt"))

    Ok(wasmtime::UpdateDeadline::Yield(1000))
});

view this post on Zulip Wasmtime GitHub notifications bot (Aug 13 2026 at 20:46):

alexcrichton commented on issue #13338:

For that specific use case I'd recommend against doing that. There's some discussion in https://github.com/bytecodealliance/wasmtime/issues/14048 but our current conclusion is that while you can technically do that in epoch_deadline_callback today you shouldn't be able to and we're likely to remove it.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 25 2026 at 18:06):

alexcrichton added the wasmtime:fuel label to Issue #13338.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 25 2026 at 18:07):

alexcrichton commented on issue #13338:

I believe this is now resolved, so I'm going to close this.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 25 2026 at 18:07):

alexcrichton closed 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_fuel for that, but it is not possible because the Store is 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_,
    }
}


Last updated: Aug 30 2026 at 09:07 UTC