Stream: git-wasmtime

Topic: wasmtime / issue #5613 resource limit exceeded when calli...


view this post on Zulip Wasmtime GitHub notifications bot (Jan 21 2023 at 05:49):

wujunzhuo opened issue #5613:

Test Case

I want to make a performance test for Go guest functions, hence export an empty function hello:

package main

func main() {}

//export hello
func hello() {}

Compile: tinygo build -o hello.wasm -target wasi

Then I use wasmtime crate to run the function repeatedly:

use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;

fn main() {
    let engine = Engine::default();
    let wasi = WasiCtxBuilder::new().build();
    let mut store = Store::new(&engine, wasi);
    let module = Module::from_file(&engine, "./hello.wasm").unwrap();

    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap();
    linker.module(&mut store, "", &module).unwrap();
    let func = linker
        .get(&mut store, "", "hello")
        .unwrap()
        .into_func()
        .unwrap()
        .typed::<(), ()>(&store)
        .unwrap();

    loop {
        func.call(&mut store, ()).unwrap();
    }
}

Expected Results

The program should be continuously running without any error.

Actual Results

wujunzhuo@wujunzhuo-macbook demo % ./target/debug/hello
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: resource limit exceeded: instance count too high at 10001', src/bin/hello.rs:22:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Versions and Environment

Wasmtime version or commit: 5.0.0

Operating system: MacOS 13.1

Architecture: M1, MacBook Air 2020

view this post on Zulip Wasmtime GitHub notifications bot (Jan 21 2023 at 05:49):

wujunzhuo labeled issue #5613:

Test Case

I want to make a performance test for Go guest functions, hence export an empty function hello:

package main

func main() {}

//export hello
func hello() {}

Compile: tinygo build -o hello.wasm -target wasi

Then I use wasmtime crate to run the function repeatedly:

use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;

fn main() {
    let engine = Engine::default();
    let wasi = WasiCtxBuilder::new().build();
    let mut store = Store::new(&engine, wasi);
    let module = Module::from_file(&engine, "./hello.wasm").unwrap();

    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap();
    linker.module(&mut store, "", &module).unwrap();
    let func = linker
        .get(&mut store, "", "hello")
        .unwrap()
        .into_func()
        .unwrap()
        .typed::<(), ()>(&store)
        .unwrap();

    loop {
        func.call(&mut store, ()).unwrap();
    }
}

Expected Results

The program should be continuously running without any error.

Actual Results

wujunzhuo@wujunzhuo-macbook demo % ./target/debug/hello
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: resource limit exceeded: instance count too high at 10001', src/bin/hello.rs:22:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Versions and Environment

Wasmtime version or commit: 5.0.0

Operating system: MacOS 13.1

Architecture: M1, MacBook Air 2020

view this post on Zulip Wasmtime GitHub notifications bot (Jan 21 2023 at 15:34):

bjorn3 commented on issue #5613:

I think tinygo produces a wasi command and not a wasi reactor. This means that loop { func.call(&mut store, ()).unwrap(); } will for every loop iteration create a new instance, initialize it, run the function and then destroys it again. Currently any state associated with a Store (including wasm instances) do not get destroyed until the entire store is destroyed. This means your loop would cause a memory leak. As precaution every Store by default has a ResourceLimiter set to allow at most 10000 instances, which is what you hit.

I think the following would work by using a separate store for every instantiation but I haven't tested it:

use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;

fn main() {
    let engine = Engine::default();
    let wasi = WasiCtxBuilder::new().build();
    let mut store = Store::new(&engine, wasi);
    let module = Module::from_file(&engine, "./hello.wasm").unwrap();

    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap();
    linker.module(&mut store, "", &module).unwrap();
    let func = linker
        .get(&mut store, "", "hello")
        .unwrap()
        .into_func()
        .unwrap()
        .typed::<(), ()>(&store)
        .unwrap();

    loop {
        let fresh_wasi = WasiCtxBuilder::new().build();
        let mut fresh_store = Store::new(&engine, fresh_wasi);
        func.call(&mut fresh_store, ()).unwrap();
    }
}

view this post on Zulip Wasmtime GitHub notifications bot (Jan 21 2023 at 17:24):

dgryski commented on issue #5613:

Is there anything tinygo can do here to make it nicer to play with?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 21 2023 at 18:06):

bjorn3 commented on issue #5613:

Wasi has two execution models:

See https://github.com/WebAssembly/WASI/blob/069e3a4aee7ecaa8b52bd3c0ebe4fc055c1b1951/legacy/application-abi.md#current-unstable-abi for a more formal specification.

Clang uses -mexec-model to switch between the command and reactor model and rustc -Zwasi-exec-model. I think it would be helpful if tinygo got a flag to switch between the two too. Basically don't export _start for a reactor, but instead _initialize and from this _initialize only initialize the runtime environment of tinygo and not run the main function. By the way I don't know if you do already, but I believe you have to add code to every exported function to initialize the runtime environment when using the command model.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 22 2023 at 08:59):

wujunzhuo commented on issue #5613:

@bjorn3 :+1: Your explanations about the wasi execution model spec is very clear~

However, it seems that we couldn't use another new store object since the linker is binding with the original one. If I initialize the whole wasi runtime and store in the loop, the memory leak problem could be overcome; but this will lead to significant performance degradation...

view this post on Zulip Wasmtime GitHub notifications bot (Jan 22 2023 at 12:49):

bjorn3 commented on issue #5613:

However, it seems that we couldn't use another new store object since the linker is binding with the original one.

I looked at the source and it seemed like it would for for wasi commands provided that you never use linker.instantiate or similar, but if you tried it, I guess I missed something.

If I initialize the whole wasi runtime and store in the loop, the memory leak problem could be overcome; but this will lead to significant performance degradation...

Yeah

view this post on Zulip Wasmtime GitHub notifications bot (Jan 23 2023 at 15:08):

alexcrichton commented on issue #5613:

It's a known issue that command modules (like the one I think you're using here) are easy to leak memory with. Note though that you're not required to execute in a command-like fashion and use Linker::module. You could instead use Linker::instantiate which would not bind the linker to a store, meaning you could use the same Linker across many stores for each instantiation.

Note that Wasmtime has had a lot of effort put into making instantiation fast, and it's expected to be viable for applications to make a new instance per request, for example.

I'll also note that there's nothing TinyGo specific here, it's more about the embedder and how the module is run.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 24 2023 at 14:22):

wujunzhuo commented on issue #5613:

Note though that you're not required to execute in a command-like fashion and use Linker::module. You could instead use Linker::instantiate which would not bind the linker to a store, meaning you could use the same Linker across many stores for each instantiation.

@alexcrichton I changed the code as per your suggestion. This time the program could work normally without memory leak anymore~

use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;

fn main() {
    let engine = Engine::default();
    let wasi = WasiCtxBuilder::new().build();
    let mut store = Store::new(&engine, wasi);
    let module = Module::from_file(&engine, "./hello.wasm").unwrap();

    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap();
    let instance = linker.instantiate(&mut store, &module).unwrap();
    linker.instance(&mut store, "", instance).unwrap();
    let func = instance
        .get_typed_func::<(), ()>(&mut store, "hello")
        .unwrap();

    loop {
        func.call(&mut store, ()).unwrap();
    }
}

view this post on Zulip Wasmtime GitHub notifications bot (Jan 24 2023 at 15:13):

alexcrichton commented on issue #5613:

Ok sounds good. I'm going to close this issue as this leak is probably best tracked by https://github.com/bytecodealliance/wasmtime/issues/1913 and I believe everything else is accounted for.

Note that in your latest example linker.instance(&mut store, "", instance).unwrap(); can be removed.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 24 2023 at 15:13):

alexcrichton closed issue #5613:

Test Case

I want to make a performance test for Go guest functions, hence export an empty function hello:

package main

func main() {}

//export hello
func hello() {}

Compile: tinygo build -o hello.wasm -target wasi

Then I use wasmtime crate to run the function repeatedly:

use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;

fn main() {
    let engine = Engine::default();
    let wasi = WasiCtxBuilder::new().build();
    let mut store = Store::new(&engine, wasi);
    let module = Module::from_file(&engine, "./hello.wasm").unwrap();

    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap();
    linker.module(&mut store, "", &module).unwrap();
    let func = linker
        .get(&mut store, "", "hello")
        .unwrap()
        .into_func()
        .unwrap()
        .typed::<(), ()>(&store)
        .unwrap();

    loop {
        func.call(&mut store, ()).unwrap();
    }
}

Expected Results

The program should be continuously running without any error.

Actual Results

wujunzhuo@wujunzhuo-macbook demo % ./target/debug/hello
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: resource limit exceeded: instance count too high at 10001', src/bin/hello.rs:22:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Versions and Environment

Wasmtime version or commit: 5.0.0

Operating system: MacOS 13.1

Architecture: M1, MacBook Air 2020


Last updated: Dec 23 2024 at 12:05 UTC