Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement proc_raise #290

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 76 additions & 11 deletions internal/wasi/wasi.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const (
// FunctionFdClose closes a file descriptor.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_close
FunctionFdClose = "fd_close"

// ImportFdClose is the WebAssembly 1.0 (MVP) Text format import of FunctionFdClose
ImportFdClose = `(import "wasi_snapshot_preview1" "fd_close"
(func $wasi.fd_close (param $fd i32) (result (;errno;) i32)))`
Expand All @@ -102,6 +103,7 @@ const (
// FunctionFdPrestatGet returns the prestat data of a file descriptor.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_get
FunctionFdPrestatGet = "fd_prestat_get"

// ImportFdPrestatGet is the WebAssembly 1.0 (MVP) Text format import of FunctionFdPrestatGet
ImportFdPrestatGet = `(import "wasi_snapshot_preview1" "fd_prestat_get"
(func $wasi.fd_prestat_get (param $fd i32) (param $result.prestat i32) (result (;errno;) i32)))`
Expand All @@ -118,6 +120,7 @@ const (
// FunctionFdRead read bytes from a file descriptor
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
FunctionFdRead = "fd_read"

// ImportFdRead is the WebAssembly 1.0 (MVP) Text format import of FunctionFdPrestatGet
ImportFdRead = `(import "wasi_snapshot_preview1" "fd_read"
(func $wasi.fd_read (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32)))`
Expand All @@ -131,7 +134,8 @@ const (
// FunctionFdWrite write bytes to a file descriptor
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
FunctionFdWrite = "fd_write"
// ImportFdWrite is the WebAssembly 1.0 (MVP) Text format import of FunctionFdPrestatGet

// ImportFdWrite is the WebAssembly 1.0 (MVP) Text format import of FunctionFdWrite
ImportFdWrite = `(import "wasi_snapshot_preview1" "fd_write"
(func $wasi.fd_write (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32)))`

Expand All @@ -148,19 +152,21 @@ const (
FunctionPollOneoff = "poll_oneoff"

// FunctionProcExit terminates the execution of the module with an exit code.
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#proc_exit
FunctionProcExit = "proc_exit"

// ImportProcExit is the WebAssembly 1.0 (MVP) Text format import of ProcExit
//
// See ImportProcExit
// See API.ProcExit
// See FunctionProcExit
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#proc_exit
ImportProcExit = `(import "wasi_snapshot_preview1" "proc_exit"
(func $wasi.proc_exit (param $rval i32)))`

FunctionProcRaise = "proc_raise"
// ImportProcRaise is the WebAssembly 1.0 (MVP) Text format import of FunctionProcRaise
ImportProcRaise = `(import "wasi_snapshot_preview1" "proc_raise"
(func $wasi.proc_raise (param $signal i32) (result (;errno;) i32)))`

// FunctionProcRaise send a signal to the wazero process.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#proc_raise
FunctionProcRaise = "proc_raise"

FunctionSchedYield = "sched_yield"

// FunctionRandomGet write random data in buffer
Expand Down Expand Up @@ -536,7 +542,22 @@ type SnapshotPreview1 interface {
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
ProcExit(rval uint32)

// TODO: ProcRaise
// ProcRaise is the WASI function that sends a signal to the current wazero process.
//
// * signal - the signal to send.
//
// The wasi.Errno returned is wasi.ErrnoSuccess except the following error conditions:
// * ErrnoInval - If the given signal is an invalid or unsupported,
// or a platform-derived error happens during sending a signal.
//
// Note: SignalKill is the only valid signal on all platforms, as Golang supports only that signal in a portable way.
// In addition, SignalInterrupt is also valid on non-Windows platforms.
// Note: This is similar to `raise` in POSIX.
// Note: ImportProcRaise shows this signature in the WebAssembly 1.0 (MVP) Text Format.
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_raise
// See https://linux.die.net/man/3/raise
ProcRaise(signal uint32)

// TODO: SchedYield

// RandomGet is the WASI function named FunctionRandomGet that write random data in buffer (rand.Read()).
Expand Down Expand Up @@ -621,8 +642,8 @@ func SnapshotPreview1Functions(opts ...Option) (nameToGoFunc map[string]interfac
// TODO: FunctionPathSymlink
// TODO: FunctionPathUnlinkFile
// TODO: FunctionPollOneoff
FunctionProcExit: a.ProcExit,
// TODO: FunctionProcRaise
FunctionProcExit: a.ProcExit,
FunctionProcRaise: a.ProcRaise,
// TODO: FunctionSchedYield
FunctionRandomGet: a.RandomGet,
// TODO: FunctionSockRecv
Expand Down Expand Up @@ -887,6 +908,50 @@ func (a *wasiAPI) ProcExit(exitCode uint32) {
panic(wasi.ExitCode(exitCode))
}

// Signal represents a WASI signal that ProcRaise can send.
//
// Note: Wazero does not support all signals defined by WASI, but only supports signals Golang's os package defines.
// See os.Signal
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#signal
type Signal uint8

const (
// Signal to interrupt a process.
SignalInterrupt Signal = 2
// Signal to terminate a process.
SignalKill Signal = 9
)

// OsSignal returns the corresponding os.Signal value to the Signal
func (s Signal) OsSignal() (os.Signal, error) {
switch s {
case SignalInterrupt:
return os.Interrupt, nil
case SignalKill:
return os.Kill, nil
default:
return nil, fmt.Errorf("unsupported Signal(%d)", s)
}
}

// ProcRaise implements SnapshotPreview1.ProcRaise
func (a *wasiAPI) ProcRaise(signal uint32) wasi.Errno {
self, err := os.FindProcess(os.Getpid())
if err != nil {
// Unlikely to happen because this finds the current process
return wasi.ErrnoSrch
}
osSignal, err := Signal(signal).OsSignal()
if err != nil {
return wasi.ErrnoInval
}
err = self.Signal(osSignal)
if err != nil {
return wasi.ErrnoInval
}
return wasi.ErrnoSuccess
}

// RandomGet implements SnapshotPreview1.RandomGet
func (a *wasiAPI) RandomGet(ctx wasm.ModuleContext, buf uint32, bufLen uint32) (errno wasi.Errno) {
randomBytes := make([]byte, bufLen)
Expand Down
55 changes: 54 additions & 1 deletion internal/wasi/wasi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import (
"fmt"
"io"
"math/rand"
"os"
"os/signal"
"runtime"
"testing"
"time"

"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -997,7 +1001,56 @@ func TestSnapshotPreview1_ProcExit(t *testing.T) {
}
}

// TODO: TestSnapshotPreview1_ProcRaise TestSnapshotPreview1_ProcRaise_Errors
func TestSnapshotPreview1_ProcRaise(t *testing.T) {
if runtime.GOOS == "windows" {
// On Windows, Go only supports os.Kill signal, which is not suitable for testing since it's not catchable.
// So, we just skip this test on Windows.
return
}

store, ctx, fn := instantiateWasmStore(t, FunctionProcRaise, ImportProcRaise, moduleName)

// Setup channel to detect a signal raised.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
defer func() {
signal.Reset(os.Interrupt)
}()

results, err := store.Engine.Call(ctx, fn, uint64(SignalInterrupt))
require.NoError(t, err)

isSignalReceived := false
select {
case <-sigCh:
isSignalReceived = true
case <-time.After(100 * time.Millisecond):
}

require.True(t, isSignalReceived)
require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // results[0] is the errno
}

func TestSnapshotPreview1_ProcRaise_Errors(t *testing.T) {
store, ctx, fn := instantiateWasmStore(t, FunctionProcRaise, ImportProcRaise, moduleName)

t.Run("Invalid signal number", func(t *testing.T) {
results, err := store.Engine.Call(ctx, fn, uint64(127)) // 127 is an arbitrary invalid signal that does not exist
require.NoError(t, err)
require.Equal(t, wasi.ErrnoInval, wasi.Errno(results[0])) // results[0] is the errno
})

if runtime.GOOS == "windows" {
t.Run("Windows doesn't support raising os.Interrupt", func(t *testing.T) {
// On Windows, Go only supports os.Kill signal, which is not suitable for testing since it's not catchable.
// So, what we can is just to check that ProcRaise returns ErrnoInval for SignalInterrupt on Windows.
results, err := store.Engine.Call(ctx, fn, uint64(SignalInterrupt))
require.NoError(t, err)
require.Equal(t, wasi.ErrnoInval, wasi.Errno(results[0])) // results[0] is the errno
})
}
}

// TODO: TestSnapshotPreview1_SchedYield TestSnapshotPreview1_SchedYield_Errors

func TestSnapshotPreview1_RandomGet(t *testing.T) {
Expand Down