Signed-off-by: Adrian Cole <adrian@tetrate.io> Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
179 lines
5.3 KiB
Go
179 lines
5.3 KiB
Go
package sysfs
|
|
|
|
import (
|
|
"context"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/tetratelabs/wazero/experimental/sys"
|
|
"github.com/tetratelabs/wazero/internal/platform"
|
|
)
|
|
|
|
// pollInterval is the interval between each calls to peekNamedPipe in selectAllHandles
|
|
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 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.
|
|
// Regular files always immediately report as ready, regardless their actual state and timeouts.
|
|
//
|
|
// If n==0 it will wait for the given timeout duration, but it will return sys.ENOSYS if timeout is nil,
|
|
// i.e. it won't block indefinitely.
|
|
//
|
|
// 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, 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 false, sys.ENOSYS
|
|
}
|
|
time.Sleep(*timeout)
|
|
return false, 0
|
|
}
|
|
|
|
n, errno = selectAllHandles(context.TODO(), r, w, e, timeout)
|
|
return n > 0, errno
|
|
}
|
|
|
|
// selectAllHandles emulates a general-purpose POSIX select on Windows.
|
|
//
|
|
// The implementation actually polls every 100 milliseconds until it reaches the given duration.
|
|
// The duration may be nil, in which case it will wait undefinely. The given ctx is
|
|
// used to allow for cancellation, and it is currently used only in tests.
|
|
//
|
|
// As indicated in the man page for select [1], r, w, e are modified upon completion:
|
|
//
|
|
// "Upon successful completion, the pselect() or select() function shall modify the objects pointed to by the readfds,
|
|
// writefds, and errorfds arguments to indicate which file descriptors are ready for reading, ready for writing,
|
|
// or have an error condition pending, respectively, and shall return the total number of ready descriptors in all the output sets"
|
|
//
|
|
// However, for our purposes, this may be pedantic because currently we do not check the values of r, w, e
|
|
// after the invocation of select; thus, this behavior may be subject to change in the future for the sake of simplicity.
|
|
//
|
|
// [1]: https://linux.die.net/man/3/select
|
|
func selectAllHandles(ctx context.Context, r, w, e *platform.FdSet, duration *time.Duration) (n int, errno sys.Errno) {
|
|
r2, w2, e2 := r.Copy(), w.Copy(), e.Copy()
|
|
n, errno = peekAllHandles(r2, w2, e2)
|
|
// Short circuit when there is an error, there is data or the duration is zero.
|
|
if errno != 0 || n > 0 || (duration != nil && *duration == time.Duration(0)) {
|
|
r.SetAll(r2)
|
|
w.SetAll(w2)
|
|
e.SetAll(e2)
|
|
return
|
|
}
|
|
|
|
// Ticker that emits at every pollInterval.
|
|
tick := time.NewTicker(pollInterval)
|
|
tickCh := tick.C
|
|
defer tick.Stop()
|
|
|
|
// Timer that expires after the given duration.
|
|
// Initialize afterCh as nil: the select below will wait forever.
|
|
var afterCh <-chan time.Time
|
|
if duration != nil {
|
|
// If duration is not nil, instantiate the timer.
|
|
after := time.NewTimer(*duration)
|
|
defer after.Stop()
|
|
afterCh = after.C
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
r.Zero()
|
|
w.Zero()
|
|
e.Zero()
|
|
return
|
|
case <-afterCh:
|
|
r.Zero()
|
|
w.Zero()
|
|
e.Zero()
|
|
return
|
|
case <-tickCh:
|
|
r2, w2, e2 = r.Copy(), w.Copy(), e.Copy()
|
|
n, errno = peekAllHandles(r2, w2, e2)
|
|
if errno != 0 || n > 0 {
|
|
r.SetAll(r2)
|
|
w.SetAll(w2)
|
|
e.SetAll(e2)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func peekAllHandles(r, w, e *platform.FdSet) (int, sys.Errno) {
|
|
// pipes are not checked on w, e
|
|
w.Pipes().Zero()
|
|
e.Pipes().Zero()
|
|
|
|
// peek pipes only for reading
|
|
errno := peekAllPipes(r.Pipes())
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
|
|
_, errno = winsock_select(r.Sockets(), w.Sockets(), e.Sockets(), &zeroDuration)
|
|
if errno != 0 {
|
|
return 0, errno
|
|
}
|
|
|
|
return r.Count() + w.Count() + e.Count(), 0
|
|
}
|
|
|
|
func peekAllPipes(pipeHandles *platform.WinSockFdSet) sys.Errno {
|
|
ready := &platform.WinSockFdSet{}
|
|
for i := 0; i < pipeHandles.Count(); i++ {
|
|
h := pipeHandles.Get(i)
|
|
bytes, errno := peekNamedPipe(h)
|
|
if bytes > 0 {
|
|
ready.Set(int(h))
|
|
}
|
|
if errno != 0 {
|
|
return sys.UnwrapOSError(errno)
|
|
}
|
|
}
|
|
*pipeHandles = *ready
|
|
return 0
|
|
}
|
|
|
|
func winsock_select(r, w, e *platform.WinSockFdSet, timeout *time.Duration) (int, sys.Errno) {
|
|
if r.Count() == 0 && w.Count() == 0 && e.Count() == 0 {
|
|
return 0, 0
|
|
}
|
|
|
|
var t *syscall.Timeval
|
|
if timeout != nil {
|
|
tv := syscall.NsecToTimeval(timeout.Nanoseconds())
|
|
t = &tv
|
|
}
|
|
|
|
rp := unsafe.Pointer(r)
|
|
wp := unsafe.Pointer(w)
|
|
ep := unsafe.Pointer(e)
|
|
tp := unsafe.Pointer(t)
|
|
|
|
r0, _, err := syscall.SyscallN(
|
|
procselect.Addr(),
|
|
uintptr(0), // the first argument is ignored and exists only for compat with BSD sockets.
|
|
uintptr(rp),
|
|
uintptr(wp),
|
|
uintptr(ep),
|
|
uintptr(tp))
|
|
return int(r0), sys.UnwrapOSError(err)
|
|
}
|