dispatchrun / wasi-go

A Go implementation of the WebAssembly System Interface (WASI)
Apache License 2.0
124 stars 7 forks source link

Question: sockets example? #34

Closed ydnar closed 1 year ago

ydnar commented 1 year ago

Hi there, pardon if this isn’t the right place to ask a question…

Thanks for building this! Yesterday I was able to build and run a reasonable chunk of our stack with WASI after patching a few pieces (filesystem access, a few upstream dependencies without wasm/wasip1 compatibility), and immediately ran into the fake net package that’s currently shipping in gotip.

Would love to continue grinding on this so our stack can be—at least partially—deployed into a WASM/Wasi environment.

  1. Do you have any examples of using the wasmedge socket interface in this package?
  2. Where’s a good place to start to weld ^^ onto Go net package semantics?

Thanks!

chriso commented 1 year ago

Great question! Go in v1.21 will support vanilla WASI preview 1, which has only limited networking capabilities. Specifically, you can only interact with a pre-opened socket file descriptors via net.FileListener and net.FileConn (see https://github.com/golang/go/commit/a17de43ef12250cd9a0ffdd8ff2d05fb18fcf322 and https://gist.github.com/chriso/6c71e968ef1002981a6ff46ceaa39ee3). You cannot create sockets with WASI preview 1.

This library provides a WasmEdge sockets extension, and may support other socket extensions in future (e.g. WASIX via https://github.com/stealthrocket/wasi-go/issues/32). It sets up the WebAssembly runtime on the host side with the necessary system call implementations, but the guest (in this case, Go with GOOS=wasip1) is missing them.

There are two options:

  1. when compiling your application, set GOROOT to the Go version found in https://github.com/stealthrocket/go/pull/2, where we've implemented the extra system calls on the guest side. We are working on getting this upstreamed in some form so that Go has native support for creating sockets. With this approach, no changes are required to your Go application.
  2. use an alternative net package, one that has alternative implementations of Listen and Dial that make the necessary system calls and then setup a net.Listener and net.Conn backed by either net.FileListener or net.FileConn. We may will offer a https://github.com/stealthrocket/net package soon that does this. With this approach, you'll need to inject the net.Dial function or net.Listener implementation into higher-level components in your application, for example when making HTTP requests or connecting to databases.

This is still a rapidly evolving area so we'd appreciate any feedback you have :smile:

I should add that these issues affect Go only. You should (in theory) be able to compile applications from other languages (e.g. C/C++/Rust/Zig) and get full networking support.

ydnar commented 1 year ago

Ah, makes sense. We’re using gotip for 1.21 features, including the pre-opened fd listeners. Thanks for the CLs!

One tricky bit is that net.Dialer isn’t an interface, and a few packages we use require a concrete net.Dialer, as opposed to something that implements DialContext.

What do you think the odds are that stealthrocket/go#2 lands in 1.21? Is it possible to extract that into an alternative net package?

ydnar commented 1 year ago

This is new: https://github.com/tetratelabs/wazero/releases/tag/v1.2.0

Looks like built-in support for pre-opened sockets: https://github.com/tetratelabs/wazero/pull/1493

achille-roussel commented 1 year ago

Hello @ydnar!

We open-sourced https://github.com/stealthrocket/net which has a net.Dial and net.Listen function working with socket extensions compatible with WasmEdge and https://github.com/stealthrocket/wasi-go

Let us know if this is useful and if you have any feedback!

ydnar commented 1 year ago

Nice! I'll check it out.

What are your thoughts on WASIX?

achille-roussel commented 1 year ago

On WASIX, we love that important industry actors are trying to innovate and create experiments to enable more and more applications to run on WebAssembly.

We've started a spike on adding ABI support for WASIX sockets in wasi-go https://github.com/stealthrocket/wasi-go/pull/44

At first, it seems like the WASIX ABI footprint is all-or-nothing, programs compiled to WASIX easily take dependencies on threads and other extensions, which makes it a challenge to adopt incrementally. We're gonna keep investigating and help where we can, interoperability is an amazing feature of WASM so the more we can make systems compatible with one another the better!

ydnar commented 1 year ago

On the subject of threads, what's the current state of wasi-go with respect to non-blocking IO and goroutines?

achille-roussel commented 1 year ago

wasi-go has full non-blocking support, a Go program compiled with GOOS=wasip1 will be able to do cooperative scheduling and dispatch I/O to goroutines just like any other target gang Go compiled to.

We still have a CL that needs to be merged in Go to enable non-blocking on stdio https://go-review.googlesource.com/c/go/+/498196, if you're interested in seeing this happen in 1.21 you could chime in there and voice your support for the change!

ydnar commented 1 year ago

Will do.

Would love to nudge this along more: https://go-review.googlesource.com/c/go/+/500576

achille-roussel commented 1 year ago

Ah yeah! This first attempt isn't going to make it but we have an alternative, stay tuned!

ydnar commented 1 year ago

I got this working with a proxy package that substitutes net/wasip1 primitives for net, but it’s unable to open sockets to localhost with this error:

dial tcp 0.0.0.0:6379: context deadline exceeded

(Note: this is to 127.0.0.1:6379, without having to resolve localhost) Is there a command line flag to wasirun that enables this?

The commands I’m using to build and run:

wasm: dist-dir go.sum tools $(go_source) $(resource_files)
    GOOS=wasip1 GOARCH=wasm CGO_ENABLED=0 gotip build -ldflags "$(GO_LDFLAGS)" -o $(wasm_binary)

wasm-run: $(wasm_binary)
    wasirun --non-blocking-stdio --sockets auto --listen 127.0.0.1:8080 $(wasm_binary)
achille-roussel commented 1 year ago

Would you happen to be able to share the code snippet for your application so we can reproduce the issue?

ydnar commented 1 year ago

We’re using github.com/gomodule/redigo/redis to create a connection pool. It has a config option to override DialContext, which uses our wnet.DialContext, which is aliased to wasip1.DialContext.

pool := &redis.Pool{
    MaxIdle:     64,
    MaxActive:   128,
    IdleTimeout: 5 * time.Minute,
    DialContext: func(ctx context.Context) (redis.Conn, error) {
        options := []redis.DialOption{
            redis.DialContextFunc(wnet.DialContext),
            redis.DialConnectTimeout(redisConnectTimeout),
            redis.DialReadTimeout(redisReadTimeout),
            redis.DialWriteTimeout(redisWriteTimeout),
            redis.DialPassword(password),
        }
        return redis.DialContext(ctx, platform.TCP, addr, options...)
    },
    TestOnBorrow: func(conn redis.Conn, t time.Time) error {
        if time.Since(t) < time.Minute {
            return nil
        }
        _, err := conn.Do("PING")
        return err
    },
}

wnet_wasip1.go:

//go:build wasip1

package wnet

import "github.com/stealthrocket/net/wasip1"

// Dialer implements Dial and DialContext.
type Dialer = wasip1.Dialer

// DialContext opens a new net.Conn.
var DialContext = wasip1.DialContext

// Listen opens a net.Listener.
var Listen = wasip1.Listen

wnet_unix.go:

//go:build unix

package wnet

import "net"

// Dialer implements Dial and DialContext.
type Dialer = net.Dialer

// DialContext opens a new net.Conn.
var DialContext = (&net.Dialer{}).DialContext

// Listen opens a net.Listener.
var Listen = net.Listen
ydnar commented 1 year ago

I disabled SIGTERM handling, which fixed the server request latency, allowing me to debug the networking code.

    if runtime.GOOS != "wasip1" {
        signal.Notify(done, os.Interrupt, syscall.SIGTERM)
    }
chriso commented 1 year ago

@ydnar try running wasirun with wasirun --trace to see what system calls are made.

ydnar commented 1 year ago

This is working great. Closing, thanks for your help!