diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go index aed08b7b..b6dc758e 100644 --- a/imports/wasi_snapshot_preview1/poll.go +++ b/imports/wasi_snapshot_preview1/poll.go @@ -174,7 +174,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno return sys.EBADF } // 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 { return errno } diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go index ff88e358..bb942f66 100644 --- a/imports/wasi_snapshot_preview1/poll_test.go +++ b/imports/wasi_snapshot_preview1/poll_test.go @@ -555,8 +555,12 @@ type neverReadyTtyStdinFile struct { } // PollRead implements the same method as documented on fsapi.File -func (neverReadyTtyStdinFile) PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno) { - time.Sleep(*timeout) +func (neverReadyTtyStdinFile) PollRead(timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + switch { + case timeoutMillis <= 0: + return + } + time.Sleep(time.Duration(timeoutMillis) * time.Millisecond) return false, 0 } @@ -567,6 +571,6 @@ type pollStdinFile struct { } // 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 } diff --git a/internal/fsapi/dir.go b/internal/fsapi/dir.go index 6e99b1b5..a68a3c47 100644 --- a/internal/fsapi/dir.go +++ b/internal/fsapi/dir.go @@ -3,7 +3,6 @@ package fsapi import ( "fmt" "io/fs" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/sys" @@ -89,7 +88,7 @@ func (DirFile) Pread([]byte, int64) (int, experimentalsys.Errno) { } // 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 } diff --git a/internal/fsapi/file.go b/internal/fsapi/file.go index f40d4155..84d466a7 100644 --- a/internal/fsapi/file.go +++ b/internal/fsapi/file.go @@ -2,7 +2,6 @@ package fsapi import ( "syscall" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/sys" @@ -16,10 +15,10 @@ import ( // // # 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. // -// 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 // hard coded values and panics otherwise. More commonly, WASI maps syscall // errors to u32 numeric values. @@ -86,9 +85,9 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed. // // # Notes // @@ -109,9 +108,9 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed. // // # Notes // @@ -124,9 +123,9 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed. // // # Notes // @@ -142,10 +141,10 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed or not readable. - // - EISDIR: the file was a directory. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed or not readable. + // - sys.EISDIR: the file was a directory. // // # Notes // @@ -160,11 +159,11 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed or not readable. - // - EINVAL: the offset was negative. - // - EISDIR: the file was a directory. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed or not readable. + // - sys.EINVAL: the offset was negative. + // - sys.EISDIR: the file was a directory. // // # Notes // @@ -195,10 +194,10 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed or not readable. - // - EINVAL: the offset was negative. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed or not readable. + // - sys.EINVAL: the offset was negative. // // # Notes // @@ -210,21 +209,24 @@ type File interface { // // # 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 // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINTR: the call was interrupted prior to data being readable. // // # Notes // // - This is like `poll` in POSIX, for a single file. // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html // - No-op files, such as those which read from /dev/null, should return - // immediately true to avoid hangs (because data will never become - // available). - PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno) + // immediately true, as data will never become readable. + PollRead(timeoutMillis int32) (ready bool, errno experimentalsys.Errno) // 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 @@ -235,10 +237,10 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file was closed or not a directory. - // - ENOENT: the directory could not be read (e.g. deleted). + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file was closed or not a directory. + // - sys.ENOENT: the directory could not be read (e.g. deleted). // // # Notes // @@ -255,9 +257,9 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file was closed, not writeable, or a directory. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file was closed, not writeable, or a directory. // // # Notes // @@ -270,11 +272,11 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed or not writeable. - // - EINVAL: the offset was negative. - // - EISDIR: the file was a directory. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed or not writeable. + // - sys.EINVAL: the offset was negative. + // - sys.EISDIR: the file was a directory. // // # Notes // @@ -286,11 +288,11 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed. - // - EINVAL: the `size` is negative. - // - EISDIR: the file was a directory. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed. + // - sys.EINVAL: the `size` is negative. + // - sys.EISDIR: the file was a directory. // // # Notes // @@ -303,8 +305,8 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - EBADF: the file or directory was closed. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.EBADF: the file or directory was closed. // // # Notes // @@ -319,8 +321,8 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - EBADF: the file or directory was closed. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.EBADF: the file or directory was closed. // // # Notes // @@ -343,9 +345,9 @@ type File interface { // // # Errors // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed. + // A zero sys.Errno is success. The below are expected otherwise: + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EBADF: the file or directory was closed. // // # Notes // @@ -357,7 +359,7 @@ type File interface { // 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 // diff --git a/internal/fsapi/unimplemented.go b/internal/fsapi/unimplemented.go index b31cbd13..883463dd 100644 --- a/internal/fsapi/unimplemented.go +++ b/internal/fsapi/unimplemented.go @@ -3,7 +3,6 @@ package fsapi import ( "io/fs" "syscall" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/sys" @@ -155,7 +154,7 @@ func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno experimentalsys.E } // 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 } diff --git a/internal/sys/stdio.go b/internal/sys/stdio.go index b153e32c..0d80e155 100644 --- a/internal/sys/stdio.go +++ b/internal/sys/stdio.go @@ -3,7 +3,6 @@ package sys import ( "io" "os" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "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 -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 } diff --git a/internal/sysfs/file_test.go b/internal/sysfs/file_test.go index 6023fa9b..a6a36642 100644 --- a/internal/sysfs/file_test.go +++ b/internal/sysfs/file_test.go @@ -9,7 +9,6 @@ import ( "runtime" "testing" gofstest "testing/fstest" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/fsapi" @@ -330,10 +329,10 @@ func TestFilePollRead(t *testing.T) { rF, err := NewStdioFile(true, r) require.NoError(t, err) 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. - ready, errno := rF.PollRead(&timeout) + ready, errno := rF.PollRead(timeout) require.EqualErrno(t, 0, errno) require.False(t, ready) @@ -343,7 +342,7 @@ func TestFilePollRead(t *testing.T) { require.NoError(t, err) // We should now be able to poll ready - ready, errno = rF.PollRead(&timeout) + ready, errno = rF.PollRead(timeout) require.EqualErrno(t, 0, errno) require.True(t, ready) diff --git a/internal/sysfs/osfile.go b/internal/sysfs/osfile.go index e39c9256..4fe10261 100644 --- a/internal/sysfs/osfile.go +++ b/internal/sysfs/osfile.go @@ -6,7 +6,6 @@ import ( "os" "runtime" "syscall" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "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 -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{} fd := int(f.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 - 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 { // Defer validation overhead until we've already had an error. errno = fileError(f, f.closed, errno) } - return count > 0, errno + return ready, errno } // Readdir implements File.Readdir. Notably, this uses "Readdir", not diff --git a/internal/sysfs/select.go b/internal/sysfs/select.go index ac0861fd..6494ec3d 100644 --- a/internal/sysfs/select.go +++ b/internal/sysfs/select.go @@ -1,27 +1,25 @@ package sysfs import ( - "time" - + "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/platform" ) -// _select exposes the select(2) syscall. This is named as such to avoid -// colliding with they keyword select while not exporting the function. +// _select waits until one or more of the file descriptors become ready for +// 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. -// It must be a pointer because `nil` means "wait forever". +// The `timeoutMillis` parameter is how long to block for an event, or +// 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, -// for instance if the call returns early. +// A zero sys.Errno is success. The below are expected otherwise: +// - 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. -// -// See also: https://github.com/golang/sys/blob/master/unix/syscall_unix_test.go#L606-L617 -// -// # Notes on the Syscall +// # Impact of blocking // // Because this is a blocking syscall, it will also block the carrier thread of the goroutine, // 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. // 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. -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) } diff --git a/internal/sysfs/select_darwin.go b/internal/sysfs/select_darwin.go index eabf4f45..24e03742 100644 --- a/internal/sysfs/select_darwin.go +++ b/internal/sysfs/select_darwin.go @@ -2,22 +2,23 @@ package sysfs import ( "syscall" - "time" "unsafe" + "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/platform" ) -// syscall_select invokes select on Darwin, with the given timeout Duration. -// We implement our own version instead of relying on syscall.Select because the latter -// only returns the error and discards the result. -func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { +// syscall_select implements _select on Darwin +// +// Note: We implement our own version instead of relying on syscall.Select +// 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 - if timeout != nil { - tv := syscall.NsecToTimeval(timeout.Nanoseconds()) + if timeoutNanos >= 0 { + tv := syscall.NsecToTimeval(int64(timeoutNanos)) t = &tv } - result, _, errno := syscall_syscall6( + r1, _, err := syscall_syscall6( libc_select_trampoline_addr, uintptr(n), 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(t)), 0) - res := int(result) - if errno == 0 { - return res, nil - } - return res, errno + return r1 > 0, sys.UnwrapOSError(err) } // libc_select_trampoline_addr is the address of the diff --git a/internal/sysfs/select_linux.go b/internal/sysfs/select_linux.go index aae5e48f..09707898 100644 --- a/internal/sysfs/select_linux.go +++ b/internal/sysfs/select_linux.go @@ -2,17 +2,18 @@ package sysfs import ( "syscall" - "time" + "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/platform" ) -// syscall_select invokes select on Unix (unless Darwin), with the given timeout Duration. -func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { +// syscall_select implements _select on Linux +func syscall_select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (bool, sys.Errno) { var t *syscall.Timeval - if timeout != nil { - tv := syscall.NsecToTimeval(timeout.Nanoseconds()) + if timeoutNanos >= 0 { + tv := syscall.NsecToTimeval(int64(timeoutNanos)) 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) } diff --git a/internal/sysfs/select_test.go b/internal/sysfs/select_test.go index 4cbf84cb..5294bb99 100644 --- a/internal/sysfs/select_test.go +++ b/internal/sysfs/select_test.go @@ -12,50 +12,50 @@ import ( ) 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 { - dur := time.Duration(0) - n, err := _select(0, nil, nil, nil, &dur) - if err == sys.EINTR { + timeoutNanos := int32(0) + ready, errno := _select(0, nil, nil, nil, timeoutNanos) + if errno == sys.EINTR { t.Logf("Select interrupted") continue } - require.NoError(t, err) - require.Equal(t, 0, n) + require.EqualErrno(t, 0, errno) + require.False(t, ready) break } }) 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 for { // On some platforms (e.g. Linux), the passed-in timeval is // updated by select(2). We are not accounting for this // in our implementation. start := time.Now() - n, err := _select(0, nil, nil, nil, &dur) + ready, errno := _select(0, nil, nil, nil, timeoutNanos) took = time.Since(start) - if err == sys.EINTR { + if errno == sys.EINTR { t.Logf("Select interrupted after %v", took) continue } - require.NoError(t, err) - require.Equal(t, 0, n) + require.EqualErrno(t, 0, errno) + require.False(t, ready) break } // On some platforms the actual timeout might be arbitrarily // less than requested. - if took < dur { + if tookNanos := int32(took.Nanoseconds()); tookNanos < timeoutNanos { if runtime.GOOS == "linux" { // Linux promises to only return early if a file descriptor // becomes ready (not applicable here), or the call // is interrupted by a signal handler (explicitly retried in the loop above), // 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 { - 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) for { - n, err := _select(fd+1, rFdSet, nil, nil, nil) - if err == sys.EINTR { + ready, errno := _select(fd+1, rFdSet, nil, nil, -1) + if errno == sys.EINTR { t.Log("Select interrupted") continue } - require.NoError(t, err) - require.Equal(t, 1, n) + require.EqualErrno(t, 0, errno) + require.True(t, ready) break } }) diff --git a/internal/sysfs/select_unsupported.go b/internal/sysfs/select_unsupported.go index 400df900..9b8a320f 100644 --- a/internal/sysfs/select_unsupported.go +++ b/internal/sysfs/select_unsupported.go @@ -3,12 +3,10 @@ package sysfs import ( - "time" - "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/platform" ) -func syscall_select(n int, r, w, e *platform.FdSet, timeout *time.Duration) (int, error) { - return -1, sys.ENOSYS +func syscall_select(n int, r, w, e *platform.FdSet, timeoutNanos int32) (ready bool, errno sys.Errno) { + return false, sys.ENOSYS } diff --git a/internal/sysfs/select_windows.go b/internal/sysfs/select_windows.go index b5c1a1bd..dde41ca7 100644 --- a/internal/sysfs/select_windows.go +++ b/internal/sysfs/select_windows.go @@ -16,7 +16,7 @@ const pollInterval = 100 * time.Millisecond // zeroDuration is the zero value for time.Duration. It is used in selectAllHandles. 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). // 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 // 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 { // Don't block indefinitely. if timeout == nil { - return -1, sys.ENOSYS + return false, sys.ENOSYS } time.Sleep(*timeout) - return 0, nil + return false, 0 } - n, errno := selectAllHandles(context.TODO(), r, w, e, timeout) - if errno == 0 { - return n, nil - } - return n, errno + n, errno = selectAllHandles(context.TODO(), r, w, e, timeout) + return n > 0, errno } // selectAllHandles emulates a general-purpose POSIX select on Windows. diff --git a/internal/sysfs/select_windows_test.go b/internal/sysfs/select_windows_test.go index 1347fb00..30d7a56a 100644 --- a/internal/sysfs/select_windows_test.go +++ b/internal/sysfs/select_windows_test.go @@ -38,18 +38,18 @@ func TestSelect_Windows(t *testing.T) { close(ch) } - t.Run("syscall_select returns sys.ENOSYS when n == 0 and duration is nil", func(t *testing.T) { - n, errno := syscall_select(0, nil, nil, nil, nil) - require.Equal(t, -1, n) + t.Run("syscall_select returns sys.ENOSYS when n == 0 and timeoutNanos is negative", func(t *testing.T) { + ready, errno := syscall_select(0, nil, nil, nil, -1) 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.Pipes().Set(-1) - n, errno := syscall_select(0, &fdSet, nil, nil, nil) - require.Equal(t, -1, n) + ready, errno := syscall_select(0, &fdSet, nil, nil, -1) 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) {