From addd312dda2d5f802f39f9d4f162edd78bd93107 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 8 Feb 2023 15:34:37 -0800 Subject: [PATCH] example: concurrent instantiation per Goroutine (#1107) Signed-off-by: Takeshi Yoneda --- examples/README.md | 1 + examples/concurrent-instantiation/README.md | 17 +++++ examples/concurrent-instantiation/main.go | 72 ++++++++++++++++++ .../concurrent-instantiation/main_test.go | 21 +++++ .../testdata/add.wasm | Bin 0 -> 41 bytes .../concurrent-instantiation/testdata/add.wat | 6 ++ 6 files changed, 117 insertions(+) create mode 100644 examples/concurrent-instantiation/README.md create mode 100644 examples/concurrent-instantiation/main.go create mode 100644 examples/concurrent-instantiation/main_test.go create mode 100644 examples/concurrent-instantiation/testdata/add.wasm create mode 100644 examples/concurrent-instantiation/testdata/add.wat diff --git a/examples/README.md b/examples/README.md index b11f028a..5e1d0b43 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,6 +9,7 @@ The following example projects can help you practice WebAssembly with wazero: * [basic](basic) - how to use both WebAssembly and Go-defined functions. * [import-go](import-go) - how to define, import and call a Go-defined function from a WebAssembly-defined function. +* [concurrent-instantiation](concurrent-instantiation) - how to instantiate multiple Wasm instances per Goroutine concurrently. * [multiple-results](multiple-results) - how to return more than one result from WebAssembly or Go-defined functions. * [multiple-runtimes](multiple-runtimes) - how to share compilation caches across multiple runtimes. diff --git a/examples/concurrent-instantiation/README.md b/examples/concurrent-instantiation/README.md new file mode 100644 index 00000000..a5a8a248 --- /dev/null +++ b/examples/concurrent-instantiation/README.md @@ -0,0 +1,17 @@ +## Concurrent Instantiation example + +This example demonstrates how to instantiate multiple Wasm instances per Goroutine __concurrently__. + +```bash +$ go run main.go +0 +98 +4 +40 +42 +30 +32 +24 +2 +--snip-- +``` diff --git a/examples/concurrent-instantiation/main.go b/examples/concurrent-instantiation/main.go new file mode 100644 index 00000000..19b0c44f --- /dev/null +++ b/examples/concurrent-instantiation/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + _ "embed" + "fmt" + "log" + "strconv" + "sync" + + "github.com/tetratelabs/wazero" +) + +// addWasm was generated by the following: +// +// wasm-tools parse testdata/add.wat -o testdata/add.wasm +// +//go:embed testdata/add.wasm +var addWasm []byte + +func main() { + // Choose the context to use for function calls. + ctx := context.Background() + + // Create a new WebAssembly Runtime. + r := wazero.NewRuntime(ctx) + defer r.Close(ctx) // This closes everything this Runtime created. + + // Compile the Wasm binary once so that we can skip the entire compilation time during instantiation. + compiledWasm, err := r.CompileModule(ctx, addWasm) + if err != nil { + log.Panicf("failed to compile Wasm binary: %v", err) + } + + var wg sync.WaitGroup + const goroutines = 50 + wg.Add(goroutines) + + // Instantiate the Wasm module from `compiledWsam`, and invoke the exported "add" function concurrently. + for i := 0; i < goroutines; i++ { + go func(i int) { + defer wg.Done() + + // Important: each instance needs a unique "name", so we create new wazero.ModuleConfig per instance, + // and assigns the iteration counter as the name. + config := wazero.NewModuleConfig().WithName(strconv.Itoa(i)) + + // Instantiate a new Wasm module from the already compiled `compiledWasm`. + instance, err := r.InstantiateModule(ctx, compiledWasm, config) + if err != nil { + log.Panicf("[%d] failed to instantiate %v", i, err) + } + + // Calculates "i + i" by invoking the exported "add" function. + result, err := instance.ExportedFunction("add").Call(ctx, uint64(i), uint64(i)) + if err != nil { + log.Panicf("[%d] failed to invoke \"add\": %v", i, err) + } + + // Ensure the addition "i + i" is actually calculated. + expected := uint64(i * 2) + if result[0] != expected { + log.Panicf("expected %d, but got %d", expected, result[0]) + } + + // Logs the result. + fmt.Println(expected) + }(i) + } + + wg.Wait() +} diff --git a/examples/concurrent-instantiation/main_test.go b/examples/concurrent-instantiation/main_test.go new file mode 100644 index 00000000..87eda718 --- /dev/null +++ b/examples/concurrent-instantiation/main_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" + "strings" + "testing" + + "github.com/tetratelabs/wazero/internal/testing/maintester" + "github.com/tetratelabs/wazero/internal/testing/require" +) + +// Test_main ensures the following will work: +// +// go run main.go +func Test_main(t *testing.T) { + stdout, _ := maintester.TestMain(t, main, "main") + + for i := 0; i < 50; i++ { + require.True(t, strings.Contains(stdout, strconv.Itoa(i*2))) + } +} diff --git a/examples/concurrent-instantiation/testdata/add.wasm b/examples/concurrent-instantiation/testdata/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/examples/concurrent-instantiation/testdata/add.wat b/examples/concurrent-instantiation/testdata/add.wat new file mode 100644 index 00000000..6e89d3c1 --- /dev/null +++ b/examples/concurrent-instantiation/testdata/add.wat @@ -0,0 +1,6 @@ +(module + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) +)