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

[water-api] new API design draft #2

Merged
merged 17 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 11 additions & 11 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package water

type Config struct {
// WASI contains the compiled WASI binary in bytes.
WASI []byte

// Dialer is used to dial a network connection.
Dialer Dialer
Dialer Dialer // A dialer supporting multiple network types.
Feature Feature // Bit-masked experimental features
WABin []byte // WebAssembly module binary.
WAConfig []byte // WebAssembly module config file, if any.
}

// init() checks if the Config is valid and initializes
// the Config with default values if optional fields are not provided.
// complete the config by filling in the missing optional fields
// with fault values and panic if any of the required fields are not
// provided.
func (c *Config) init() {
if len(c.WASI) == 0 {
panic("water: WASI binary is not provided")
}

if c.Dialer == nil {
c.Dialer = DefaultDialer()
}

if len(c.WABin) == 0 {
panic("water: WASI binary is not provided")
}
}
114 changes: 114 additions & 0 deletions core.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package water

import (
"fmt"
"net"

"github.com/bytecodealliance/wasmtime-go/v12"
)

// RuntimeConn defines an interface that all implementations of RuntimeConn
// must implement no matter of which version it is.
type RuntimeConn interface {
net.Conn
}

// runtimeCore provides the WASM runtime base and is an internal struct
// that every RuntimeXxx implementation will embed.
type runtimeCore struct {
// wasmtime
engine *wasmtime.Engine
module *wasmtime.Module
store *wasmtime.Store
linker *wasmtime.Linker
instance *wasmtime.Instance

// wasi imports
deferFuncs []func() // defer functions to be called by WASM module on exit

// wasi exports
_init *wasmtime.Func
_version *wasmtime.Func

// other wasi related stuff
wd *WasiDialer
}

func NewCore() *runtimeCore {
return &runtimeCore{}
}

func (c *runtimeCore) Defer(f func()) {
c.deferFuncs = append(c.deferFuncs, f)
}

func (c *runtimeCore) linkDefer() error {
if c.linker == nil {
return fmt.Errorf("linker not set")
}

if err := c.linker.DefineFunc(c.store, "env", "defer", c._defer); err != nil {
return fmt.Errorf("(*wasmtime.Linker).DefineFunc: %w", err)
}

return nil
}

func (c *runtimeCore) linkDialer(dialer Dialer, address string) error {
if c.linker == nil {
return fmt.Errorf("linker not set")
}

if dialer == nil {
return fmt.Errorf("dialer not set")
}

if c.wd == nil {
c.wd = NewWasiDialer(address, dialer, c.store)
} else {
return fmt.Errorf("wasi dialer already set, double-linking?")
}

if err := c.linker.DefineFunc(c.store, "env", "dial", c.wd.WasiDialerFunc); err != nil {
return fmt.Errorf("(*wasmtime.Linker).DefineFunc: %w", err)
}

return nil
}

func (c *runtimeCore) initializeConn() (RuntimeConn, error) {
// get _init and _version functions
c._init = c.instance.GetFunc(c.store, "_init")
if c._init == nil {
return nil, fmt.Errorf("instantiated WASM module does not export _init function")
}
c._version = c.instance.GetFunc(c.store, "_version")
if c._version == nil {
return nil, fmt.Errorf("instantiated WASM module does not export _version function")
}

// initialize WASM instance.
// In a _init() call, the WASM module will setup all its internal states
_, err := c._init.Call(c.store)
if err != nil {
return nil, fmt.Errorf("errored upon calling _init function: %w", err)
}

// get version
// In a _version() call, the WASM module will return its version
ret, err := c._version.Call(c.store)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not too sure how Go handles function with no return, but I forgot to add a check for it if there is no return value for the _version function in Rust previously.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function returns successfully, then the interface{} return argument will be the result of the function. If there were 0 results then this value is nil. If there was one result then this is that result. Otherwise if there were multiple results then []Val is returned.

From func (*Func) Call()'s definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But let's forget about the multi-value for V0 (version in terms of of water API). It is not FFI-friendly and we don't take risks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's way neater in Go, not like Rust, you need to pass in a &mut [Val] with the exact length of the return values, otherwise it will fail the call.

Copy link
Contributor Author

@gaukas gaukas Sep 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail the call

What's the behavior then? Returning some bad values, an error (a trap), or panic the entire host program (that's the worst)?

Copy link
Member

@erikziyunchi erikziyunchi Sep 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it will panic the the entire host 🫠, it's returning a Result<Err(E)> if I do .unwrap() or ? then it will panic the host where it's actually avoidable by adding some handling code for that case. But currently I'm passing something like this to the call to avoid it.

let mut res = vec![Val::I32(0); version_fn.ty(&self.store).results().len()];

if err != nil {
return nil, fmt.Errorf("errored upon calling _version function: %w", err)
}
if ver, ok := ret.(int32); !ok {
return nil, fmt.Errorf("_version function returned non-int32 value")
} else {
return RuntimeConnWithVersion(c, ver)
}
}

func (c *runtimeCore) _defer() {
for _, f := range c.deferFuncs {
f()
}
}
66 changes: 66 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"crypto/rand"
"fmt"
"net"
"os"
"sync"
)

func main() {
// get temp dir
tempDir := os.TempDir()
// append OS-specific path separator
tempDir += string(os.PathSeparator)
// randomize a socket name
randBytes := make([]byte, 16)
if _, err := rand.Read(randBytes); err != nil {
panic(fmt.Errorf("water: rand.Read returned error: %w", err))
}
tempDir += fmt.Sprintf("%x", randBytes)

// create a one-time use UnixListener
ul, err := net.Listen("unix", tempDir)
if err != nil {
panic(fmt.Errorf("water: net.Listen returned error: %w", err))
}
defer ul.Close()

var waConn net.Conn
var wg *sync.WaitGroup = new(sync.WaitGroup)
wg.Add(1)
go func() {
defer wg.Done()
var err error
waConn, err = ul.Accept()
if err != nil {
panic(fmt.Errorf("water: ul.Accept returned error: %w", err))
}
}()

// dial the one-time use UnixListener
uc, err := net.Dial("unix", ul.Addr().String())
if err != nil {
panic(fmt.Errorf("water: net.Dial returned error: %w", err))
}
defer uc.Close()
wg.Wait()

// write to uc, read from waConn
uc.Write([]byte("hello"))
buf := make([]byte, 128)
n, err := waConn.Read(buf)
if err != nil {
panic(fmt.Errorf("water: waConn.Read returned error: %w", err))
}
fmt.Printf("read %d bytes from waConn: %s\n", n, string(buf[:n]))

// write to waConn, read from uc
waConn.Write([]byte("world"))
n, err = uc.Read(buf)
if err != nil {
panic(fmt.Errorf("water: uc.Read returned error: %w", err))
}
fmt.Printf("read %d bytes from uc: %s\n", n, string(buf[:n]))
}
12 changes: 12 additions & 0 deletions feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package water

type Feature uint64

// Feature is a bit mask of experimental features of WATER.
const (
FEATURE_DUMMY Feature = 1 << iota // a dummy feature that does nothing.
FEATURE_RESERVED // reserved for future use
// ...
FEATURE_CWAL Feature = 0xFFFFFFFFFFFFFFFF // CWAL = Can't Wait Any Longer
FEATURE_NONE Feature = 0 // NONE = No Experimental Features
)
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/gaukas/water

go 1.21.0
go 1.18

require github.com/bytecodealliance/wasmtime-go/v11 v11.0.0
replace github.com/bytecodealliance/wasmtime-go/v12 v12.0.0 => github.com/refraction-networking/wasmtime-go/v12 v12.0.0

require github.com/bytecodealliance/wasmtime-go/v12 v12.0.0
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
github.com/bytecodealliance/wasmtime-go/v11 v11.0.0 h1:SwLgbjbFpQ1tf5vIbWexaZABezBSL8WmzP+foLyi0lE=
github.com/bytecodealliance/wasmtime-go/v11 v11.0.0/go.mod h1:9btfEuCkOP7EDR9a7LqDXrfQ7dtWeGlDHt3buV5UyjY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/refraction-networking/wasmtime-go/v12 v12.0.0 h1:crB/5HgsjL5OvfXwCT6YthaoL19tDYdK3R6NOKw42n8=
github.com/refraction-networking/wasmtime-go/v12 v12.0.0/go.mod h1:xLry3QSj/6qi3uMlhbeCoJhf2gWtuXfNTS6TKhz9M+s=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
75 changes: 0 additions & 75 deletions internal/filesocket/bundle.go

This file was deleted.

Loading