diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go index a5357e7966..fe60352e82 100644 --- a/imports/wasi_snapshot_preview1/poll_test.go +++ b/imports/wasi_snapshot_preview1/poll_test.go @@ -376,18 +376,18 @@ func Test_pollOneoff_Stdin(t *testing.T) { out: 128, // past in resultNevents: 512, // past out expectedMem: []byte{ - // Clock is acknowledged first. - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata - byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit - wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum + // First an illegal file with custom user data should be acknowledged. + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // userdata + byte(wasip1.ErrnoBadf), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - // Then an illegal file with custom user data. - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // userdata - byte(wasip1.ErrnoBadf), 0x0, // errno is 16 bit - wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + // Clock is acknowledged then. + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, diff --git a/imports/wasi_snapshot_preview1/w_poll_oneoff.go b/imports/wasi_snapshot_preview1/w_poll_oneoff.go index ac3e031a61..195be25149 100644 --- a/imports/wasi_snapshot_preview1/w_poll_oneoff.go +++ b/imports/wasi_snapshot_preview1/w_poll_oneoff.go @@ -57,12 +57,17 @@ func alternativePollOneoffFn(_ context.Context, mod api.Module, params []uint64) return sys.EFAULT } + // start by writing 0 to resultNevents + if !mod.Memory().WriteUint32Le(resultNevents, 0) { + return sys.EFAULT + } + // Extract FS context, used in the body of the for loop for FS access. fsc := mod.(*wasm.ModuleInstance).Sys.FS() - + // Slice of events that are processed out of the loop (blocking stdin subscribers). + var blockingStdinSubs []*event // The timeout is initialized at max Duration, the loop will find the minimum. var timeout time.Duration = 1<<63 - 1 - // Count of all the subscriptions that have been already written back to outBuf. // nevents*32 returns at all times the offset where the next event should be written: // this way we ensure that there are no gaps between records. @@ -116,9 +121,15 @@ func alternativePollOneoffFn(_ context.Context, mod api.Module, params []uint64) evt.errno = wasip1.ErrnoBadf writeEvent(outBuf[outOffset:], evt) nevents++ - } else if guestFd == internalsys.FdStdin { // stdin is always ready to read (can read 0/EOF etc) - writeEvent(outBuf[outOffset:], evt) - nevents++ + } else if guestFd == internalsys.FdStdin { // stdin is always checked with Poll function later. + if file.File.IsNonblock() { // non-blocking stdin is always ready to read + writeEvent(outBuf[outOffset:], evt) + nevents++ + } else { + // if the fd is Stdin, and it is in blocking mode, + // do not ack yet, append to a slice for delayed evaluation. + blockingStdinSubs = append(blockingStdinSubs, evt) + } } else if hostFd := file.File.Fd(); hostFd == 0 { evt.errno = wasip1.ErrnoNotsup writeEvent(outBuf[outOffset:], evt) @@ -177,40 +188,57 @@ func alternativePollOneoffFn(_ context.Context, mod api.Module, params []uint64) writeEvent(outBuf[nevents*32:], clkevent) nevents++ } - // write nevents to resultNevents - if !mem.WriteUint32Le(resultNevents, nevents) { - return sys.EFAULT - } - return 0 } // If there are I/O subscriptions, we call poll on the I/O fds with the updated timeout. + if len(hostPollSubs) > 0 { + pollNevents, err := internalsysfs.Poll(hostPollSubs, int32(timeout.Milliseconds())) + if err != 0 { + return err + } - pollNevents, err := internalsysfs.Poll(hostPollSubs, int32(timeout.Milliseconds())) - if err != 0 { - return err + if pollNevents > 0 { // if there are events triggered + // iterate over hostPollSubs and if the revent is set, write back + // the event + for i, pollFd := range hostPollSubs { + if pollFd.Revents&pollFd.Events != 0 { + // write back the event + writeEvent(outBuf[nevents*32:], ioEvents[i]) + nevents++ + } else if pollFd.Revents != 0 { + // write back the event + writeEvent(outBuf[nevents*32:], ioEvents[i]) + nevents++ + } + } + } else { // otherwise it means that the timeout expired + // Ack the clock event if there is one (it can also be a default max timeout) + if clkevent != nil { + writeEvent(outBuf[nevents*32:], clkevent) + nevents++ + } + } } - if pollNevents > 0 { // if there are events triggered - // iterate over hostPollSubs and if the revent is set, write back - // the event - for i, pollFd := range hostPollSubs { - if pollFd.Revents&pollFd.Events != 0 { - // write back the event - writeEvent(outBuf[nevents*32:], ioEvents[i]) - nevents++ - } else if pollFd.Revents != 0 { - // write back the event - writeEvent(outBuf[nevents*32:], ioEvents[i]) + // If there are blocking stdin subscribers, check for data with given timeout. + if len(blockingStdinSubs) > 0 { + stdin, ok := fsc.LookupFile(internalsys.FdStdin) + if !ok { + return sys.EBADF + } + + // Wait for the timeout to expire, or for some data to become available on Stdin. + if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 { + return errno + } else if stdinReady { + // stdin has data ready to for reading, write back all the events + for i := range blockingStdinSubs { + evt := blockingStdinSubs[i] + evt.errno = 0 + writeEvent(outBuf[nevents*32:], evt) nevents++ } } - } else { // otherwise it means that the timeout expired - // Ack the clock event if there is one (it can also be a default max timeout) - if clkevent != nil { - writeEvent(outBuf[nevents*32:], clkevent) - nevents++ - } } // write nevents to resultNevents