-
Notifications
You must be signed in to change notification settings - Fork 943
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
feat: add basic WASI net.Listener
via sock_accept
#2748
Conversation
36cec4e
to
3614793
Compare
3614793
to
f0645b0
Compare
Hello @rvolosatovs could you please rebase against the latest |
@rvolosatovs Can you rebase this? |
9efdb97
to
fedcfaf
Compare
@deadprogram apologies for the delay, I was on vacation right after we spoke and then had a few very busy weeks. There is a CI build failure on MacOS, but it does not seem to be caused by this PR - I may be wrong however |
77aa8ad
to
57de1cd
Compare
net
support via sock_accept
net
support via sock_accept
net.Listener
via sock_accept
@deadprogram Are you able to review this? |
57de1cd
to
823a275
Compare
I've extracted some of the required changes into smaller scoped PRs in hopes of a simpler review. |
823a275
to
bfe82d6
Compare
Signed-off-by: Roman Volosatovs <[email protected]>
Directly duplicate from Go 1.18 implementation, use fake netFD for all systems (which is only used for js&wasm in stdlib) Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
bfe82d6
to
902c2a2
Compare
@deadprogram would further splitting this PR into smaller scoped chunks assist review? |
Filed #3061 , marking as draft again |
@rvolosatovs interesting (esp that the snapshot01 version was updated after the fact!). I was curious the motivation of this? Is this a proof of concept or are folks making raw socket listeners? Reason I ask is often there are higher level libs that need some changes to support even if sock_accept works. Finally, I noticed in the past due to lack of description in the spec sometimes you need to look at another impl to see how they handle things. Did you notice how this is implemented in rust or clang?? Ex the resulting wasm (can inspect backwards via wasm2wat) looks similar between here and rust or C of the same? Cheers! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some review to help this along.
I suspect when things stabilize, unit tests would make sense, though I notice Go doesn't really test these files directly either.
|
||
// LocalAddr implements the Conn LocalAddr method. | ||
func (*netFD) LocalAddr() Addr { | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil | |
return laddr |
|
||
// RemoteAddr implements the Conn RemoteAddr method. | ||
func (*netFD) RemoteAddr() Addr { | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not available on WASI
|
||
// LocalAddr implements the Conn LocalAddr method. | ||
func (*netFD) LocalAddr() Addr { | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not available on WASI
if err != nil { | ||
return nil, wrapSyscallError("sock_accept", err) | ||
} | ||
return &netFD{File: os.NewFile(uintptr(fd), "conn"), net: "file+net", laddr: nil, raddr: nil}, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a fake addr instead of nil? or comment why? I think usually these are derived from fd, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of this is available on WASI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll delete all my comments which summarize as "please comment this" :D for now, I guess people will know that if something is nil, it is intentional.
@@ -309,6 +309,14 @@ func Getpagesize() int { | |||
return 65536 | |||
} | |||
|
|||
func SockAccept(fd int, flags int) (nfd int, err error) { | |||
var retptr0 __wasi_fd_t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verify flags or return EINVAL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Standard library does not do that for Accept4
https://github.com/golang/go/blob/a0441c7ae3dea57a0553c9ea77e184c34b7da40f/src/syscall/syscall_linux.go#L661-L677 🤔
Is it really responsibility of TinyGo standard library or rather the WASI implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WASI has no guidance on things like this. the spec is very sparse. There's no discussion of host vs guest responsibilities. knowing this, yeah most hosts will also validate and return EINVAL conventionally https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
lacking guidance, what I do is look at what wasi-libc does and prefer that, though agree you can also look at how go does it. this isn't a huge deal which way, imho
|
||
// Read implements the Conn Read method. | ||
func (c *netFD) Read(b []byte) (int, error) { | ||
// TODO: Handle EAGAIN and perform poll |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO: Handle EAGAIN and perform poll | |
// TODO: Handle EAGAIN and perform poll similar to poll/fd_unix.go |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, not necessarily same as https://github.com/golang/go/blob/master/src/internal/poll/fd_unix.go
It's circling back to https://github.com/tinygo-org/tinygo/pull/2748/files#r979834379 in my head - is it a goal of this project to be as similar to upstream Go and minimize diff or not? A simpler and more minimal poll implementation could probably suit this project better, but I am not sure what's the preferred approach. I was hoping this PR could start a discussion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave this one to the maintainers to decide. thanks for clarifying!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To the style question, my impression is that the project tries in this order:
- directly use upstream files (no copying needed)
- borrow a subset of upstream files, minimizing diffs but only borrowing the lines that are needed
- replace upstream files with something more suited to tinygo's use case (but still kind of minimizing diffs if possible)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upstream's networking is... tricky because of its interaction with goroutines, as you've noticed.
How useful is networking without getting that bit right?
Will your code (e.g. netfd.Read()) pause all goroutines while blocking on networking?
I haven't checked yet.
"time" | ||
) | ||
|
||
// Network file descriptor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Network file descriptor. | |
// wasiCon implements Con with a file returned by syscall.SockAccept. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is directly copy-pasted and adapted from https://github.com/golang/go/blob/a0441c7ae3dea57a0553c9ea77e184c34b7da40f/src/net/fd_posix.go#L16-L17, that's the approach I saw throughout this project so I followed that.
Changing this comment would make it inconsistent with upstream, which means it would not be as trivial to sync this with upstream by copy-pasting.
If we're changing this, then we should change the whole implementation as well, since the way this is designed in the standard library is not necessarily the best approach here given the set of use cases this project tries to cover.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok understand what you mean. I think in another world I would probably comment things like this in the source, but ack that's not the practice. I'll dismiss my feedback as most of them are me being curious why things are defining unused symbols or otherwise. People who know the conventions already somehow won't need to see comments like this.
return nil | ||
} | ||
|
||
func fileListener(f *os.File) (Listener, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upstream Go does not do this https://github.com/golang/go/blob/a0441c7ae3dea57a0553c9ea77e184c34b7da40f/src/net/file_unix.go#L89-L102
If a structure of the code is confusing, I don't think that explaining everything in comments is the right solution - restructuring the code is, however.
As I said, I tried to keep as close as possible to upstream Go implementation - if that's not a requirement, then I'd rather rewrite this whole thing in a way that makes much more sense especially for this use case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I would love to see your impl, but for tinygo whatever maintainers suggest is best as they have the long term stake.
The motivation is to be able to accept a connection from within a WebAssembly module via WASI. In Wasmtime and Enarx this works by calling
Both Rust (rust-lang/rust#93158) and C (WebAssembly/wasi-libc#287) support was added by @haraldh, who we work together with at @profianinc. We're making sure to have all implementations consistent. Note, that big difference of |
|
||
// LocalAddr implements the Conn LocalAddr method. | ||
func (*netFD) LocalAddr() Addr { | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not available on WASI
|
||
// RemoteAddr implements the Conn RemoteAddr method. | ||
func (*netFD) RemoteAddr() Addr { | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not available on WASI
return nil | ||
} | ||
|
||
func fileListener(f *os.File) (Listener, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upstream Go does not do this https://github.com/golang/go/blob/a0441c7ae3dea57a0553c9ea77e184c34b7da40f/src/net/file_unix.go#L89-L102
If a structure of the code is confusing, I don't think that explaining everything in comments is the right solution - restructuring the code is, however.
As I said, I tried to keep as close as possible to upstream Go implementation - if that's not a requirement, then I'd rather rewrite this whole thing in a way that makes much more sense especially for this use case
I played around with this PR and tried to use it to start an HTTP server with func run() error {
ln, err := net.FileListener(os.NewFile(uintptr(3), "socket"))
if err != nil {
return fmt.Errorf("failed to listen on fd 3: %w", err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s", r.URL.Path[1:])
})
srv := &http.Server{
Handler: mux,
}
err = srv.Serve(ln)
if err != nil {
return fmt.Errorf("failed to serve: %w", err)
}
return nil
} It seems to work after I did the following changes:
Doing this I could run the server via What would be a better way to perform the polling? Is I could imagine that the It would be really great to have some kind of support of this in TinyGo, so web services with Go could be run in WASM/WASI. |
@hlubek if were me I would probably scour GitHub for anyone using wasi-libc in C and see how they are doing it. https://github.com/WebAssembly/wasi-libc/blob/63e4489d01ad0262d995c6d9a5f1a1bab719c917/libc-bottom-half/sources/accept.c then compare polling approaches. I think these network functions are more used in rust for various docker demos. |
We're proposing this be added to Go (with |
This landed in Go via golang/go@a17de43e (and prerequisites golang/go@41893389 and golang/go@c5c21845) |
Yes, it finally happened! Closing |
@deadprogram I think this issue is not closed in TinyGo, even if it is solved for Go. There will be users who can't use Go, yet due to lack of exports. Should this be re-opened as an issue until closed? or this codebase repurposed to complete regardless of what Go does? |
Description
Add support for
sock_accept
recently added to WASI WebAssembly/WASI#458The implementation still lacks polling support, but that is something best left for another PR, since that would be a magnitudes bigger change.
Relevant documentation on flags can be found here https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fdflags
I followed the upstream Go implementation as much as possible with minimal changes
Testing
Server
Save the following program to
main.go
in your working directory:Compile with:
Wasmtime
Works on
wasmtime 0.38.0
:Enarx
Works on https://github.com/enarx/enarx with the following config:
For example, on Linux:
Client
Start in a terminal window: