Skip to content

Commit

Permalink
fix: wasi fd subject to garbage collection on unix
Browse files Browse the repository at this point in the history
commit 81e41c5
Author: Edoardo Vacchi <[email protected]>
Date:   Thu Dec 28 11:07:28 2023 +0100

    wasi: sockets, share most logic across Windows and Unix

    Signed-off-by: Edoardo Vacchi <[email protected]>

commit 4f9f569
Author: Edoardo Vacchi <[email protected]>
Date:   Thu Dec 28 10:10:06 2023 +0100

    wasi: sockets, use the same strategy for impl as Windows

    Signed-off-by: Edoardo Vacchi <[email protected]>
  • Loading branch information
gaukas committed Dec 31, 2023
1 parent fbf4b2e commit 1909418
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 366 deletions.
4 changes: 2 additions & 2 deletions internal/sysfs/file_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ func writeFd(fd uintptr, buf []byte) (int, sys.Errno) {
return -1, sys.ENOSYS
}

func readSocket(h syscall.Handle, buf []byte) (int, sys.Errno) {
func readSocket(h uintptr, buf []byte) (int, sys.Errno) {
var overlapped syscall.Overlapped
var done uint32
errno := syscall.ReadFile(h, buf, &done, &overlapped)
errno := syscall.ReadFile(syscall.Handle(h), buf, &done, &overlapped)
if errno == syscall.ERROR_IO_PENDING {
errno = sys.EAGAIN
}
Expand Down
195 changes: 195 additions & 0 deletions internal/sysfs/sock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi"
socketapi "github.com/tetratelabs/wazero/internal/sock"
"github.com/tetratelabs/wazero/sys"
)
Expand Down Expand Up @@ -35,3 +36,197 @@ func (f *baseSockFile) Stat() (fs sys.Stat_t, errno experimentalsys.Errno) {
fs.Mode = os.ModeIrregular
return
}

var _ socketapi.TCPSock = (*tcpListenerFile)(nil)

type tcpListenerFile struct {
baseSockFile

tl *net.TCPListener
closed bool
nonblock bool
}

// newTCPListenerFile is a constructor for a socketapi.TCPSock.
//
// The current strategy is to wrap a net.TCPListener
// and invoking raw syscalls using syscallConnControl:
// this internal calls RawConn.Control(func(fd)), making sure
// that the underlying file descriptor is valid throughout
// the duration of the syscall.
func newDefaultTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock {
return &tcpListenerFile{tl: tl}
}

// Accept implements the same method as documented on socketapi.TCPSock
func (f *tcpListenerFile) Accept() (socketapi.TCPConn, experimentalsys.Errno) {
// Ensure we have an incoming connection, otherwise return immediately.
if f.nonblock {
if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 {
return nil, experimentalsys.EAGAIN
}
}

// Accept normally blocks goroutines, but we
// made sure that we have an incoming connection,
// so we should be safe.
if conn, err := f.tl.Accept(); err != nil {
return nil, experimentalsys.UnwrapOSError(err)
} else {
return newTcpConn(conn.(*net.TCPConn)), 0
}
}

// Close implements the same method as documented on experimentalsys.File
func (f *tcpListenerFile) Close() experimentalsys.Errno {
if !f.closed {
return experimentalsys.UnwrapOSError(f.tl.Close())
}
return 0
}

// Addr is exposed for testing.
func (f *tcpListenerFile) Addr() *net.TCPAddr {
return f.tl.Addr().(*net.TCPAddr)
}

// SetNonblock implements the same method as documented on fsapi.File
func (f *tcpListenerFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) {
f.nonblock = enabled
_, errno = syscallConnControl(f.tl, func(fd uintptr) (int, experimentalsys.Errno) {
return 0, setNonblockSocket(fd, enabled)
})
return
}

// IsNonblock implements the same method as documented on fsapi.File
func (f *tcpListenerFile) IsNonblock() bool {
return f.nonblock
}

// Poll implements the same method as documented on fsapi.File
func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
return false, experimentalsys.ENOSYS
}

var _ socketapi.TCPConn = (*tcpConnFile)(nil)

type tcpConnFile struct {
baseSockFile

tc *net.TCPConn

// nonblock is true when the underlying connection is flagged as non-blocking.
// This ensures that reads and writes return experimentalsys.EAGAIN without blocking the caller.
nonblock bool
// closed is true when closed was called. This ensures proper experimentalsys.EBADF
closed bool
}

func newTcpConn(tc *net.TCPConn) socketapi.TCPConn {
return &tcpConnFile{tc: tc}
}

// Read implements the same method as documented on experimentalsys.File
func (f *tcpConnFile) Read(buf []byte) (n int, errno experimentalsys.Errno) {
if len(buf) == 0 {
return 0, 0 // Short-circuit 0-len reads.
}
if nonBlockingFileReadSupported && f.IsNonblock() {
n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
n, err := readSocket(fd, buf)
errno = experimentalsys.UnwrapOSError(err)
errno = fileError(f, f.closed, errno)
return n, errno
})
} else {
n, errno = read(f.tc, buf)
}
if errno != 0 {
// Defer validation overhead until we've already had an error.
errno = fileError(f, f.closed, errno)
}
return
}

// Write implements the same method as documented on experimentalsys.File
func (f *tcpConnFile) Write(buf []byte) (n int, errno experimentalsys.Errno) {
if nonBlockingFileWriteSupported && f.IsNonblock() {
return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
n, err := writeSocket(fd, buf)
errno = experimentalsys.UnwrapOSError(err)
errno = fileError(f, f.closed, errno)
return n, errno
})
} else {
n, errno = write(f.tc, buf)
}
if errno != 0 {
// Defer validation overhead until we've already had an error.
errno = fileError(f, f.closed, errno)
}
return
}

// Recvfrom implements the same method as documented on socketapi.TCPConn
func (f *tcpConnFile) Recvfrom(p []byte, flags int) (n int, errno experimentalsys.Errno) {
if flags != MSG_PEEK {
errno = experimentalsys.EINVAL
return
}
return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
n, err := recvfrom(fd, p, MSG_PEEK)
errno = experimentalsys.UnwrapOSError(err)
errno = fileError(f, f.closed, errno)
return n, errno
})
}

// Shutdown implements the same method as documented on experimentalsys.Conn
func (f *tcpConnFile) Shutdown(how int) experimentalsys.Errno {
// FIXME: can userland shutdown listeners?
var err error
switch how {
case socketapi.SHUT_RD:
err = f.tc.CloseRead()
case socketapi.SHUT_WR:
err = f.tc.CloseWrite()
case socketapi.SHUT_RDWR:
return f.close()
default:
return experimentalsys.EINVAL
}
return experimentalsys.UnwrapOSError(err)
}

// Close implements the same method as documented on experimentalsys.File
func (f *tcpConnFile) Close() experimentalsys.Errno {
return f.close()
}

func (f *tcpConnFile) close() experimentalsys.Errno {
if f.closed {
return 0
}
f.closed = true
return f.Shutdown(socketapi.SHUT_RDWR)
}

// SetNonblock implements the same method as documented on fsapi.File
func (f *tcpConnFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) {
f.nonblock = enabled
_, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
return 0, experimentalsys.UnwrapOSError(setNonblockSocket(fd, enabled))
})
return
}

// IsNonblock implements the same method as documented on fsapi.File
func (f *tcpConnFile) IsNonblock() bool {
return f.nonblock
}

// Poll implements the same method as documented on fsapi.File
func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
return false, experimentalsys.ENOSYS
}
29 changes: 29 additions & 0 deletions internal/sysfs/sock_supported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build linux || darwin || windows

package sysfs

import (
"syscall"

experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
)

// syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies
// the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure.
//
// syscallConnControl streamlines the pattern of extracting the syscall.Rawconn,
// invoking its syscall.RawConn.Control method, then handling properly the errors that may occur
// within fn or returned by syscall.RawConn.Control itself.
func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, experimentalsys.Errno)) (n int, errno experimentalsys.Errno) {
syscallConn, err := conn.SyscallConn()
if err != nil {
return 0, experimentalsys.UnwrapOSError(err)
}
// Prioritize the inner errno over Control
if controlErr := syscallConn.Control(func(fd uintptr) {
n, errno = fn(fd)
}); errno == 0 {
errno = experimentalsys.UnwrapOSError(controlErr)
}
return
}
Loading

0 comments on commit 1909418

Please sign in to comment.