Skip to content

Commit

Permalink
Add timeout support in wazero run cli (#1173)
Browse files Browse the repository at this point in the history
- ensure the module initialization function is evaluated
  regardless if it's WASI or GoJS
- add a `-timeout duration` flag
- ensure flag gets propagated to the rt config builder
  (also ensure cache flag gets propagated to the rt config builder)
- print a message to stderr when the deadline is exceeded
- configure GitHub Actions to use `-timeout=10m` flag
  instead of GHA's `timeout-minutes: 10`

Signed-off-by: Edoardo Vacchi <[email protected]>
  • Loading branch information
evacchi authored Feb 28, 2023
1 parent 9c07b27 commit 5598e49
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 6 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ jobs:
# adding filter argument to the "Build Stdlib test binary" step.
# e.g. --test-filter "Dir.Iterator but dir is deleted during iteration"
- name: Run the test binary with wazero CLI
timeout-minutes: 10 # Prevent crashes from timing out
run: go run ./cmd/wazero run -mount=:/ test.wasm
run: go run ./cmd/wazero run -mount=:/ -timeout=10m test.wasm

build_tinygo_test_binary:
name: Build TinyGo test binary
Expand Down
Binary file added cmd/wazero/testdata/infinite_loop.wasm
Binary file not shown.
7 changes: 7 additions & 0 deletions cmd/wazero/testdata/infinite_loop.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(module $infinite_loop
(func $main (export "_start")
(loop
br 0
)
)
)
33 changes: 29 additions & 4 deletions cmd/wazero/wazero.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
Expand Down Expand Up @@ -92,11 +93,11 @@ func doCompile(args []string, stdErr io.Writer, exit func(code int)) {

c := wazero.NewRuntimeConfig()
if cache := maybeUseCacheDir(cacheDir, stdErr, exit); cache != nil {
c.WithCompilationCache(cache)
c = c.WithCompilationCache(cache)
}

ctx := context.Background()
rt := wazero.NewRuntime(ctx)
rt := wazero.NewRuntimeWithConfig(ctx, c)
defer rt.Close(ctx)

if _, err = rt.CompileModule(ctx, wasm); err != nil {
Expand Down Expand Up @@ -133,6 +134,14 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
"This may be specified multiple times. When <wasm path> is unset, <path> is used. "+
"For read-only mounts, append the suffix ':ro'.")

var timeout time.Duration
flags.DurationVar(&timeout, "timeout", 0*time.Second,
"if a wasm binary runs longer than the given duration string, then exit abruptly. "+
"The duration string is an unsigned sequence of decimal numbers, "+
"each with optional fraction and a unit suffix, such as \"300ms\", \"1.5h\" or \"2h45m\". "+
"Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\". "+
"If the duration is 0, the timeout is disabled. The default is disabled.")

var hostlogging logScopesFlag
flags.Var(&hostlogging, "hostlogging",
"a comma-separated list of host function scopes to log to stderr. "+
Expand Down Expand Up @@ -195,7 +204,17 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
rtc = wazero.NewRuntimeConfig()
}
if cache := maybeUseCacheDir(cacheDir, stdErr, exit); cache != nil {
rtc.WithCompilationCache(cache)
rtc = rtc.WithCompilationCache(cache)
}
if timeout > 0 {
newCtx, cancel := context.WithTimeout(ctx, timeout)
ctx = newCtx
defer cancel()
rtc = rtc.WithCloseOnContextDone(true)
} else if timeout < 0 {
fmt.Fprintf(stdErr, "timeout duration may not be negative, %v given\n", timeout)
printRunUsage(stdErr, flags)
exit(1)
}

rt := wazero.NewRuntimeWithConfig(ctx, rtc)
Expand Down Expand Up @@ -231,11 +250,17 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
} else if needsGo {
gojs.MustInstantiate(ctx, rt)
err = gojs.Run(ctx, rt, code, conf)
} else {
_, err = rt.InstantiateModule(ctx, code, conf)
}

if err != nil {
if exitErr, ok := err.(*sys.ExitError); ok {
exit(int(exitErr.ExitCode()))
exitCode := exitErr.ExitCode()
if exitCode == sys.ExitCodeDeadlineExceeded {
fmt.Fprintf(stdErr, "error: %v (timeout %v)\n", exitErr, timeout)
}
exit(int(exitCode))
}
fmt.Fprintf(stdErr, "error instantiating wasm binary: %v\n", err)
exit(1)
Expand Down
26 changes: 26 additions & 0 deletions cmd/wazero/wazero_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ import (
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/version"
"github.com/tetratelabs/wazero/sys"
)

//go:embed testdata/infinite_loop.wasm
var wasmInfiniteLoop []byte

//go:embed testdata/wasi_arg.wasm
var wasmWasiArg []byte

Expand Down Expand Up @@ -426,6 +430,24 @@ func TestRun(t *testing.T) {
require.True(t, len(entries) > 0)
},
},
{
name: "timeout: a binary that exceeds the deadline should print an error",
wazeroOpts: []string{"-timeout=1ms"},
wasm: wasmInfiniteLoop,
expectedStderr: "error: module \"\" closed with context deadline exceeded (timeout 1ms)\n",
expectedExitCode: int(sys.ExitCodeDeadlineExceeded),
test: func(t *testing.T) {
require.NoError(t, err)
},
},
{
name: "timeout: a binary that ends before the deadline should not print a timeout error",
wazeroOpts: []string{"-timeout=10s"},
wasm: wasmWasiRandomGet,
test: func(t *testing.T) {
require.NoError(t, err)
},
},
}

cryptoTest := test{
Expand Down Expand Up @@ -512,6 +534,10 @@ func TestRun_Errors(t *testing.T) {
message: "invalid cachedir",
args: []string{"--cachedir", notWasmPath, wasmPath},
},
{
message: "timeout duration may not be negative",
args: []string{"-timeout=-10s", wasmPath},
},
}

for _, tc := range tests {
Expand Down

0 comments on commit 5598e49

Please sign in to comment.