sysfs: changes PollRead to accept int32 timeoutMillis (#1597)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-07-28 10:01:00 +08:00
committed by GitHub
parent 6b4328c66e
commit 180ff682d9
15 changed files with 159 additions and 146 deletions

View File

@@ -174,7 +174,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno
return sys.EBADF return sys.EBADF
} }
// Wait for the timeout to expire, or for some data to become available on Stdin. // Wait for the timeout to expire, or for some data to become available on Stdin.
stdinReady, errno := stdin.File.PollRead(&timeout) stdinReady, errno := stdin.File.PollRead(int32(timeout.Milliseconds()))
if errno != 0 { if errno != 0 {
return errno return errno
} }

View File

@@ -555,8 +555,12 @@ type neverReadyTtyStdinFile struct {
} }
// PollRead implements the same method as documented on fsapi.File // PollRead implements the same method as documented on fsapi.File
func (neverReadyTtyStdinFile) PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno) { func (neverReadyTtyStdinFile) PollRead(timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
time.Sleep(*timeout) switch {
case timeoutMillis <= 0:
return
}
time.Sleep(time.Duration(timeoutMillis) * time.Millisecond)
return false, 0 return false, 0
} }
@@ -567,6 +571,6 @@ type pollStdinFile struct {
} }
// PollRead implements the same method as documented on fsapi.File // PollRead implements the same method as documented on fsapi.File
func (p *pollStdinFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) { func (p *pollStdinFile) PollRead(int32) (ready bool, errno experimentalsys.Errno) {
return p.ready, 0 return p.ready, 0
} }

View File

@@ -3,7 +3,6 @@ package fsapi
import ( import (
"fmt" "fmt"
"io/fs" "io/fs"
"time"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/sys" "github.com/tetratelabs/wazero/sys"
@@ -89,7 +88,7 @@ func (DirFile) Pread([]byte, int64) (int, experimentalsys.Errno) {
} }
// PollRead implements File.PollRead // PollRead implements File.PollRead
func (DirFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) { func (DirFile) PollRead(int32) (ready bool, errno experimentalsys.Errno) {
return false, experimentalsys.ENOSYS return false, experimentalsys.ENOSYS
} }

View File

@@ -2,7 +2,6 @@ package fsapi
import ( import (
"syscall" "syscall"
"time"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/sys" "github.com/tetratelabs/wazero/sys"
@@ -16,10 +15,10 @@ import (
// //
// # Errors // # Errors
// //
// All methods that can return an error return a Errno, which is zero // All methods that can return an error return a sys.Errno, which is zero
// on success. // on success.
// //
// Restricting to Errno matches current WebAssembly host functions, // Restricting to sys.Errno matches current WebAssembly host functions,
// which are constrained to well-known error codes. For example, `GOOS=js` maps // which are constrained to well-known error codes. For example, `GOOS=js` maps
// hard coded values and panics otherwise. More commonly, WASI maps syscall // hard coded values and panics otherwise. More commonly, WASI maps syscall
// errors to u32 numeric values. // errors to u32 numeric values.
@@ -86,9 +85,9 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// //
// # Notes // # Notes
// //
@@ -109,9 +108,9 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// //
// # Notes // # Notes
// //
@@ -124,9 +123,9 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// //
// # Notes // # Notes
// //
@@ -142,10 +141,10 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not readable. // - sys.EBADF: the file or directory was closed or not readable.
// - EISDIR: the file was a directory. // - sys.EISDIR: the file was a directory.
// //
// # Notes // # Notes
// //
@@ -160,11 +159,11 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not readable. // - sys.EBADF: the file or directory was closed or not readable.
// - EINVAL: the offset was negative. // - sys.EINVAL: the offset was negative.
// - EISDIR: the file was a directory. // - sys.EISDIR: the file was a directory.
// //
// # Notes // # Notes
// //
@@ -195,10 +194,10 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not readable. // - sys.EBADF: the file or directory was closed or not readable.
// - EINVAL: the offset was negative. // - sys.EINVAL: the offset was negative.
// //
// # Notes // # Notes
// //
@@ -210,21 +209,24 @@ type File interface {
// //
// # Parameters // # Parameters
// //
// The `timeout` parameter when nil blocks up to forever. // The `timeoutMillis` parameter is how long to block for data to become
// readable, or interrupted, in milliseconds. There are two special values:
// - zero returns immediately
// - any negative value blocks any amount of time
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - sys.EINTR: the call was interrupted prior to data being readable.
// //
// # Notes // # Notes
// //
// - This is like `poll` in POSIX, for a single file. // - This is like `poll` in POSIX, for a single file.
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html
// - No-op files, such as those which read from /dev/null, should return // - No-op files, such as those which read from /dev/null, should return
// immediately true to avoid hangs (because data will never become // immediately true, as data will never become readable.
// available). PollRead(timeoutMillis int32) (ready bool, errno experimentalsys.Errno)
PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno)
// Readdir reads the contents of the directory associated with file and // Readdir reads the contents of the directory associated with file and
// returns a slice of up to n Dirent values in an arbitrary order. This is // returns a slice of up to n Dirent values in an arbitrary order. This is
@@ -235,10 +237,10 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file was closed or not a directory. // - sys.EBADF: the file was closed or not a directory.
// - ENOENT: the directory could not be read (e.g. deleted). // - sys.ENOENT: the directory could not be read (e.g. deleted).
// //
// # Notes // # Notes
// //
@@ -255,9 +257,9 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file was closed, not writeable, or a directory. // - sys.EBADF: the file was closed, not writeable, or a directory.
// //
// # Notes // # Notes
// //
@@ -270,11 +272,11 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not writeable. // - sys.EBADF: the file or directory was closed or not writeable.
// - EINVAL: the offset was negative. // - sys.EINVAL: the offset was negative.
// - EISDIR: the file was a directory. // - sys.EISDIR: the file was a directory.
// //
// # Notes // # Notes
// //
@@ -286,11 +288,11 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// - EINVAL: the `size` is negative. // - sys.EINVAL: the `size` is negative.
// - EISDIR: the file was a directory. // - sys.EISDIR: the file was a directory.
// //
// # Notes // # Notes
// //
@@ -303,8 +305,8 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// //
// # Notes // # Notes
// //
@@ -319,8 +321,8 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// //
// # Notes // # Notes
// //
@@ -343,9 +345,9 @@ type File interface {
// //
// # Errors // # Errors
// //
// A zero Errno is success. The below are expected otherwise: // A zero sys.Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function. // - sys.ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed. // - sys.EBADF: the file or directory was closed.
// //
// # Notes // # Notes
// //
@@ -357,7 +359,7 @@ type File interface {
// Close closes the underlying file. // Close closes the underlying file.
// //
// A zero Errno is returned if unimplemented or success. // A zero sys.Errno is returned if unimplemented or success.
// //
// # Notes // # Notes
// //

View File

@@ -3,7 +3,6 @@ package fsapi
import ( import (
"io/fs" "io/fs"
"syscall" "syscall"
"time"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/sys" "github.com/tetratelabs/wazero/sys"
@@ -155,7 +154,7 @@ func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno experimentalsys.E
} }
// PollRead implements File.PollRead // PollRead implements File.PollRead
func (UnimplementedFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) { func (UnimplementedFile) PollRead(int32) (ready bool, errno experimentalsys.Errno) {
return false, experimentalsys.ENOSYS return false, experimentalsys.ENOSYS
} }

View File

@@ -3,7 +3,6 @@ package sys
import ( import (
"io" "io"
"os" "os"
"time"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/fsapi"
@@ -50,7 +49,7 @@ func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) {
} }
// PollRead implements the same method as documented on fsapi.File // PollRead implements the same method as documented on fsapi.File
func (noopStdinFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) { func (noopStdinFile) PollRead(int32) (ready bool, errno experimentalsys.Errno) {
return true, 0 // always ready to read nothing return true, 0 // always ready to read nothing
} }

View File

@@ -9,7 +9,6 @@ import (
"runtime" "runtime"
"testing" "testing"
gofstest "testing/fstest" gofstest "testing/fstest"
"time"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/fsapi"
@@ -330,10 +329,10 @@ func TestFilePollRead(t *testing.T) {
rF, err := NewStdioFile(true, r) rF, err := NewStdioFile(true, r)
require.NoError(t, err) require.NoError(t, err)
buf := make([]byte, 10) buf := make([]byte, 10)
timeout := time.Duration(0) // return immediately timeout := int32(0) // return immediately
// When there's nothing in the pipe, it isn't ready. // When there's nothing in the pipe, it isn't ready.
ready, errno := rF.PollRead(&timeout) ready, errno := rF.PollRead(timeout)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.False(t, ready) require.False(t, ready)
@@ -343,7 +342,7 @@ func TestFilePollRead(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// We should now be able to poll ready // We should now be able to poll ready
ready, errno = rF.PollRead(&timeout) ready, errno = rF.PollRead(timeout)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.True(t, ready) require.True(t, ready)

View File

@@ -6,7 +6,6 @@ import (
"os" "os"
"runtime" "runtime"
"syscall" "syscall"
"time"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/fsapi"
@@ -183,17 +182,22 @@ func (f *osFile) Seek(offset int64, whence int) (newOffset int64, errno experime
} }
// PollRead implements the same method as documented on fsapi.File // PollRead implements the same method as documented on fsapi.File
func (f *osFile) PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno) { func (f *osFile) PollRead(timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
fdSet := platform.FdSet{} fdSet := platform.FdSet{}
fd := int(f.fd) fd := int(f.fd)
fdSet.Set(fd) fdSet.Set(fd)
nfds := fd + 1 // See https://man7.org/linux/man-pages/man2/select.2.html#:~:text=condition%20has%20occurred.-,nfds,-This%20argument%20should nfds := fd + 1 // See https://man7.org/linux/man-pages/man2/select.2.html#:~:text=condition%20has%20occurred.-,nfds,-This%20argument%20should
count, err := _select(nfds, &fdSet, nil, nil, timeout)
// Coerce negative timeout to -1 as that's defined in POSIX
if timeoutMillis < 0 {
timeoutMillis = -1
}
ready, err := _select(nfds, &fdSet, nil, nil, timeoutMillis)
if errno = experimentalsys.UnwrapOSError(err); errno != 0 { if errno = experimentalsys.UnwrapOSError(err); errno != 0 {
// Defer validation overhead until we've already had an error. // Defer validation overhead until we've already had an error.
errno = fileError(f, f.closed, errno) errno = fileError(f, f.closed, errno)
} }
return count > 0, errno return ready, errno
} }
// Readdir implements File.Readdir. Notably, this uses "Readdir", not // Readdir implements File.Readdir. Notably, this uses "Readdir", not

View File

@@ -1,27 +1,25 @@
package sysfs package sysfs
import ( import (
"time" "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/platform"
) )
// _select exposes the select(2) syscall. This is named as such to avoid // _select waits until one or more of the file descriptors become ready for
// colliding with they keyword select while not exporting the function. // reading or writing.
// //
// # Notes on Parameters // # Parameters
// //
// For convenience, we expose a pointer to a time.Duration instead of a pointer to a syscall.Timeval. // The `timeoutMillis` parameter is how long to block for an event, or
// It must be a pointer because `nil` means "wait forever". // interrupted, in milliseconds. There are two special values:
// - zero returns immediately
// - any negative value blocks any amount of time
// //
// However, notice that select(2) may mutate the pointed Timeval on some platforms, // A zero sys.Errno is success. The below are expected otherwise:
// for instance if the call returns early. // - sys.ENOSYS: the implementation does not support this function.
// - sys.EINTR: the call was interrupted prior to an event.
// //
// This implementation *will not* update the pointed time.Duration value accordingly. // # Impact of blocking
//
// See also: https://github.com/golang/sys/blob/master/unix/syscall_unix_test.go#L606-L617
//
// # Notes on the Syscall
// //
// Because this is a blocking syscall, it will also block the carrier thread of the goroutine, // Because this is a blocking syscall, it will also block the carrier thread of the goroutine,
// preventing any means to support context cancellation directly. // preventing any means to support context cancellation directly.
@@ -31,6 +29,14 @@ import (
// e.g. the read-end of a pipe or an eventfd on Linux. // e.g. the read-end of a pipe or an eventfd on Linux.
// When the context is canceled, we may unblock a Select call by writing to the fd, causing it to return immediately. // When the context is canceled, we may unblock a Select call by writing to the fd, causing it to return immediately.
// This however requires to do a bit of housekeeping to hide the "special" FD from the end-user. // This however requires to do a bit of housekeeping to hide the "special" FD from the end-user.
func _select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { //
return syscall_select(n, r, w, e, timeout) // # Notes
//
// - This is like `select` in POSIX except it returns if any are ready
// instead of a specific file descriptor. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html
// - This is named _select to avoid collision on the select keyword, while
// not exporting the function.
func _select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (ready bool, errno sys.Errno) {
return syscall_select(n, r, w, e, timeoutNanos)
} }

View File

@@ -2,22 +2,23 @@ package sysfs
import ( import (
"syscall" "syscall"
"time"
"unsafe" "unsafe"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/platform"
) )
// syscall_select invokes select on Darwin, with the given timeout Duration. // syscall_select implements _select on Darwin
// We implement our own version instead of relying on syscall.Select because the latter //
// only returns the error and discards the result. // Note: We implement our own version instead of relying on syscall.Select
func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { // because the latter only returns the error and discards the result.
func syscall_select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (ready bool, errno sys.Errno) {
var t *syscall.Timeval var t *syscall.Timeval
if timeout != nil { if timeoutNanos >= 0 {
tv := syscall.NsecToTimeval(timeout.Nanoseconds()) tv := syscall.NsecToTimeval(int64(timeoutNanos))
t = &tv t = &tv
} }
result, _, errno := syscall_syscall6( r1, _, err := syscall_syscall6(
libc_select_trampoline_addr, libc_select_trampoline_addr,
uintptr(n), uintptr(n),
uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(r)),
@@ -25,11 +26,7 @@ func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int
uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(e)),
uintptr(unsafe.Pointer(t)), uintptr(unsafe.Pointer(t)),
0) 0)
res := int(result) return r1 > 0, sys.UnwrapOSError(err)
if errno == 0 {
return res, nil
}
return res, errno
} }
// libc_select_trampoline_addr is the address of the // libc_select_trampoline_addr is the address of the

View File

@@ -2,17 +2,18 @@ package sysfs
import ( import (
"syscall" "syscall"
"time"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/platform"
) )
// syscall_select invokes select on Unix (unless Darwin), with the given timeout Duration. // syscall_select implements _select on Linux
func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { func syscall_select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (bool, sys.Errno) {
var t *syscall.Timeval var t *syscall.Timeval
if timeout != nil { if timeoutNanos >= 0 {
tv := syscall.NsecToTimeval(timeout.Nanoseconds()) tv := syscall.NsecToTimeval(int64(timeoutNanos))
t = &tv t = &tv
} }
return syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), t) n, err := syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), t)
return n > 0, sys.UnwrapOSError(err)
} }

View File

@@ -12,50 +12,50 @@ import (
) )
func TestSelect(t *testing.T) { func TestSelect(t *testing.T) {
t.Run("should return immediately with no fds and duration 0", func(t *testing.T) { t.Run("should return immediately with no fds and timeoutNanos 0", func(t *testing.T) {
for { for {
dur := time.Duration(0) timeoutNanos := int32(0)
n, err := _select(0, nil, nil, nil, &dur) ready, errno := _select(0, nil, nil, nil, timeoutNanos)
if err == sys.EINTR { if errno == sys.EINTR {
t.Logf("Select interrupted") t.Logf("Select interrupted")
continue continue
} }
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, 0, n) require.False(t, ready)
break break
} }
}) })
t.Run("should wait for the given duration", func(t *testing.T) { t.Run("should wait for the given duration", func(t *testing.T) {
dur := 250 * time.Millisecond timeoutNanos := int32(250 * time.Millisecond)
var took time.Duration var took time.Duration
for { for {
// On some platforms (e.g. Linux), the passed-in timeval is // On some platforms (e.g. Linux), the passed-in timeval is
// updated by select(2). We are not accounting for this // updated by select(2). We are not accounting for this
// in our implementation. // in our implementation.
start := time.Now() start := time.Now()
n, err := _select(0, nil, nil, nil, &dur) ready, errno := _select(0, nil, nil, nil, timeoutNanos)
took = time.Since(start) took = time.Since(start)
if err == sys.EINTR { if errno == sys.EINTR {
t.Logf("Select interrupted after %v", took) t.Logf("Select interrupted after %v", took)
continue continue
} }
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, 0, n) require.False(t, ready)
break break
} }
// On some platforms the actual timeout might be arbitrarily // On some platforms the actual timeout might be arbitrarily
// less than requested. // less than requested.
if took < dur { if tookNanos := int32(took.Nanoseconds()); tookNanos < timeoutNanos {
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// Linux promises to only return early if a file descriptor // Linux promises to only return early if a file descriptor
// becomes ready (not applicable here), or the call // becomes ready (not applicable here), or the call
// is interrupted by a signal handler (explicitly retried in the loop above), // is interrupted by a signal handler (explicitly retried in the loop above),
// or the timeout expires. // or the timeout expires.
t.Errorf("Select: slept for %v, expected %v", took, dur) t.Errorf("Select: slept for %v, expected %v", tookNanos, timeoutNanos)
} else { } else {
t.Logf("Select: slept for %v, requested %v", took, dur) t.Logf("Select: slept for %v, requested %v", tookNanos, timeoutNanos)
} }
} }
}) })
@@ -74,13 +74,13 @@ func TestSelect(t *testing.T) {
rFdSet.Set(fd) rFdSet.Set(fd)
for { for {
n, err := _select(fd+1, rFdSet, nil, nil, nil) ready, errno := _select(fd+1, rFdSet, nil, nil, -1)
if err == sys.EINTR { if errno == sys.EINTR {
t.Log("Select interrupted") t.Log("Select interrupted")
continue continue
} }
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, 1, n) require.True(t, ready)
break break
} }
}) })

View File

@@ -3,12 +3,10 @@
package sysfs package sysfs
import ( import (
"time"
"github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/platform"
) )
func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { func syscall_select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (ready bool, errno sys.Errno) {
return -1, sys.ENOSYS return false, sys.ENOSYS
} }

View File

@@ -16,7 +16,7 @@ const pollInterval = 100 * time.Millisecond
// zeroDuration is the zero value for time.Duration. It is used in selectAllHandles. // zeroDuration is the zero value for time.Duration. It is used in selectAllHandles.
var zeroDuration = time.Duration(0) var zeroDuration = time.Duration(0)
// syscall_select emulates the select syscall on Windows, for a subset of cases. // syscall_select implements _select on Windows, for a subset of cases.
// //
// r, w, e may contain any number of file handles, but regular files and pipes are only processed for r (Read). // r, w, e may contain any number of file handles, but regular files and pipes are only processed for r (Read).
// Stdin is a pipe, thus it is checked for readiness when present. Pipes are checked using PeekNamedPipe. // Stdin is a pipe, thus it is checked for readiness when present. Pipes are checked using PeekNamedPipe.
@@ -27,21 +27,26 @@ var zeroDuration = time.Duration(0)
// //
// Note: ideas taken from https://stackoverflow.com/questions/6839508/test-if-stdin-has-input-for-c-windows-and-or-linux // Note: ideas taken from https://stackoverflow.com/questions/6839508/test-if-stdin-has-input-for-c-windows-and-or-linux
// PeekNamedPipe: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe // PeekNamedPipe: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { func syscall_select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (ready bool, errno sys.Errno) {
var timeout *time.Duration
if timeoutNanos >= 0 {
duration := time.Duration(timeoutNanos)
timeout = &duration
}
// TODO: This impl was left alone because it will change soon.
// See https://github.com/tetratelabs/wazero/pull/1596
if n == 0 { if n == 0 {
// Don't block indefinitely. // Don't block indefinitely.
if timeout == nil { if timeout == nil {
return -1, sys.ENOSYS return false, sys.ENOSYS
} }
time.Sleep(*timeout) time.Sleep(*timeout)
return 0, nil return false, 0
} }
n, errno := selectAllHandles(context.TODO(), r, w, e, timeout) n, errno = selectAllHandles(context.TODO(), r, w, e, timeout)
if errno == 0 { return n > 0, errno
return n, nil
}
return n, errno
} }
// selectAllHandles emulates a general-purpose POSIX select on Windows. // selectAllHandles emulates a general-purpose POSIX select on Windows.

View File

@@ -38,18 +38,18 @@ func TestSelect_Windows(t *testing.T) {
close(ch) close(ch)
} }
t.Run("syscall_select returns sys.ENOSYS when n == 0 and duration is nil", func(t *testing.T) { t.Run("syscall_select returns sys.ENOSYS when n == 0 and timeoutNanos is negative", func(t *testing.T) {
n, errno := syscall_select(0, nil, nil, nil, nil) ready, errno := syscall_select(0, nil, nil, nil, -1)
require.Equal(t, -1, n)
require.EqualErrno(t, sys.ENOSYS, errno) require.EqualErrno(t, sys.ENOSYS, errno)
require.False(t, ready)
}) })
t.Run("syscall_select propagates error when peekAllPipes returns an error", func(t *testing.T) { t.Run("syscall_select propagates error when peekAllPipes returns an negative", func(t *testing.T) {
fdSet := platform.FdSet{} fdSet := platform.FdSet{}
fdSet.Pipes().Set(-1) fdSet.Pipes().Set(-1)
n, errno := syscall_select(0, &fdSet, nil, nil, nil) ready, errno := syscall_select(0, &fdSet, nil, nil, -1)
require.Equal(t, -1, n)
require.EqualErrno(t, sys.ENOSYS, errno) require.EqualErrno(t, sys.ENOSYS, errno)
require.False(t, ready)
}) })
t.Run("peekNamedPipe should report the correct state of incoming data in the pipe", func(t *testing.T) { t.Run("peekNamedPipe should report the correct state of incoming data in the pipe", func(t *testing.T) {