Appearance
Run Wago in a service
Compile during service startup, then give each concurrent request its own instance. The requests share native code while keeping guest memory, tables, and globals isolated.
Use the fib.wasm from Run WebAssembly from Go. Replace main.go with:
go
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"sort"
"sync"
"time"
"github.com/wago-org/wago"
)
type Service struct {
runtime *wago.Runtime
module *wago.Module
}
func NewService(wasm []byte) (*Service, error) {
runtime := wago.NewRuntime()
module, err := runtime.Compile(wasm)
if err != nil {
_ = runtime.Close()
return nil, err
}
return &Service{runtime: runtime, module: module}, nil
}
func (s *Service) Fib(ctx context.Context, value int32) (int32, error) {
instance, err := s.runtime.Instantiate(ctx, s.module)
if err != nil {
return 0, err
}
defer instance.Close()
results, err := instance.InvokeValues(ctx, "fib", wago.ValueI32(value))
if err != nil {
return 0, err
}
return results[0].I32(), nil
}
func (s *Service) Close(ctx context.Context) error {
return errors.Join(s.module.Close(), s.runtime.CloseContext(ctx))
}
type response struct {
line string
err error
}
func run() error {
wasm, err := os.ReadFile("fib.wasm")
if err != nil {
return err
}
service, err := NewService(wasm)
if err != nil {
return err
}
defer func() { _ = service.Close(context.Background()) }()
jobs := []int32{10, 20, 30}
responses := make(chan response, len(jobs))
var group sync.WaitGroup
for _, value := range jobs {
group.Add(1)
go func() {
defer group.Done()
result, err := service.Fib(context.Background(), value)
responses <- response{
line: fmt.Sprintf("fib(%d) = %d", value, result),
err: err,
}
}()
}
group.Wait()
close(responses)
var lines []string
for result := range responses {
if result.err != nil {
return result.err
}
lines = append(lines, result.line)
}
sort.Strings(lines)
for _, line := range lines {
fmt.Println(line)
}
shutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return service.Close(shutdown)
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}sh
go run .
go test -race ./...text
fib(10) = 55
fib(20) = 6765
fib(30) = 832040An individual instance accepts one call at a time. Create a fresh instance per request when state should not survive. If state must persist, assign that instance to one worker and serialize access to it.
At shutdown, stop admitting requests, wait for active calls, close persistent instances, close modules, and then call Runtime.CloseContext. Runtime.Close starts shutdown and may finish asynchronously when callbacks or active work are involved; use CloseContext when the service must wait for the final result.
