github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/imports/wasi_snapshot_preview1/poll.go (about) 1 package wasi_snapshot_preview1 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/tetratelabs/wazero/api" 8 "github.com/tetratelabs/wazero/experimental/sys" 9 "github.com/tetratelabs/wazero/internal/fsapi" 10 internalsys "github.com/tetratelabs/wazero/internal/sys" 11 "github.com/tetratelabs/wazero/internal/wasip1" 12 "github.com/tetratelabs/wazero/internal/wasm" 13 ) 14 15 // pollOneoff is the WASI function named PollOneoffName that concurrently 16 // polls for the occurrence of a set of events. 17 // 18 // # Parameters 19 // 20 // - in: pointer to the subscriptions (48 bytes each) 21 // - out: pointer to the resulting events (32 bytes each) 22 // - nsubscriptions: count of subscriptions, zero returns sys.EINVAL. 23 // - resultNevents: count of events. 24 // 25 // Result (Errno) 26 // 27 // The return value is 0 except the following error conditions: 28 // - sys.EINVAL: the parameters are invalid 29 // - sys.ENOTSUP: a parameters is valid, but not yet supported. 30 // - sys.EFAULT: there is not enough memory to read the subscriptions or 31 // write results. 32 // 33 // # Notes 34 // 35 // - Since the `out` pointer nests Errno, the result is always 0. 36 // - This is similar to `poll` in POSIX. 37 // 38 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff 39 // See https://linux.die.net/man/3/poll 40 var pollOneoff = newHostFunc( 41 wasip1.PollOneoffName, pollOneoffFn, 42 []api.ValueType{i32, i32, i32, i32}, 43 "in", "out", "nsubscriptions", "result.nevents", 44 ) 45 46 type event struct { 47 eventType byte 48 userData []byte 49 errno wasip1.Errno 50 } 51 52 func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { 53 in := uint32(params[0]) 54 out := uint32(params[1]) 55 nsubscriptions := uint32(params[2]) 56 resultNevents := uint32(params[3]) 57 58 if nsubscriptions == 0 { 59 return sys.EINVAL 60 } 61 62 mem := mod.Memory() 63 64 // Ensure capacity prior to the read loop to reduce error handling. 65 inBuf, ok := mem.Read(in, nsubscriptions*48) 66 if !ok { 67 return sys.EFAULT 68 } 69 outBuf, ok := mem.Read(out, nsubscriptions*32) 70 // zero-out all buffer before writing 71 for i := range outBuf { 72 outBuf[i] = 0 73 } 74 75 if !ok { 76 return sys.EFAULT 77 } 78 79 // Eagerly write the number of events which will equal subscriptions unless 80 // there's a fault in parsing (not processing). 81 if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) { 82 return sys.EFAULT 83 } 84 85 // Loop through all subscriptions and write their output. 86 87 // Extract FS context, used in the body of the for loop for FS access. 88 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 89 // Slice of events that are processed out of the loop (blocking stdin subscribers). 90 var blockingStdinSubs []*event 91 // The timeout is initialized at max Duration, the loop will find the minimum. 92 var timeout time.Duration = 1<<63 - 1 93 // Count of all the subscriptions that have been already written back to outBuf. 94 // nevents*32 returns at all times the offset where the next event should be written: 95 // this way we ensure that there are no gaps between records. 96 nevents := uint32(0) 97 98 // Layout is subscription_u: Union 99 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u 100 for i := uint32(0); i < nsubscriptions; i++ { 101 inOffset := i * 48 102 outOffset := nevents * 32 103 104 eventType := inBuf[inOffset+8] // +8 past userdata 105 // +8 past userdata +8 contents_offset 106 argBuf := inBuf[inOffset+8+8:] 107 userData := inBuf[inOffset : inOffset+8] 108 109 evt := &event{ 110 eventType: eventType, 111 userData: userData, 112 errno: wasip1.ErrnoSuccess, 113 } 114 115 switch eventType { 116 case wasip1.EventTypeClock: // handle later 117 newTimeout, err := processClockEvent(argBuf) 118 if err != 0 { 119 return err 120 } 121 // Min timeout. 122 if newTimeout < timeout { 123 timeout = newTimeout 124 } 125 // Ack the clock event to the outBuf. 126 writeEvent(outBuf[outOffset:], evt) 127 nevents++ 128 case wasip1.EventTypeFdRead: 129 fd := int32(le.Uint32(argBuf)) 130 if fd < 0 { 131 return sys.EBADF 132 } 133 if file, ok := fsc.LookupFile(fd); !ok { 134 evt.errno = wasip1.ErrnoBadf 135 writeEvent(outBuf[outOffset:], evt) 136 nevents++ 137 } else if fd != internalsys.FdStdin && file.File.IsNonblock() { 138 writeEvent(outBuf[outOffset:], evt) 139 nevents++ 140 } else { 141 // if the fd is Stdin, and it is in blocking mode, 142 // do not ack yet, append to a slice for delayed evaluation. 143 blockingStdinSubs = append(blockingStdinSubs, evt) 144 } 145 case wasip1.EventTypeFdWrite: 146 fd := int32(le.Uint32(argBuf)) 147 if fd < 0 { 148 return sys.EBADF 149 } 150 if _, ok := fsc.LookupFile(fd); ok { 151 evt.errno = wasip1.ErrnoNotsup 152 } else { 153 evt.errno = wasip1.ErrnoBadf 154 } 155 nevents++ 156 writeEvent(outBuf[outOffset:], evt) 157 default: 158 return sys.EINVAL 159 } 160 } 161 162 sysCtx := mod.(*wasm.ModuleInstance).Sys 163 if nevents == nsubscriptions { 164 // We already wrote back all the results. We already wrote this number 165 // earlier to offset `resultNevents`. 166 // We only need to observe the timeout (nonzero if there are clock subscriptions) 167 // and return. 168 if timeout > 0 { 169 sysCtx.Nanosleep(int64(timeout)) 170 } 171 return 0 172 } 173 174 // If there are blocking stdin subscribers, check for data with given timeout. 175 stdin, ok := fsc.LookupFile(internalsys.FdStdin) 176 if !ok { 177 return sys.EBADF 178 } 179 // Wait for the timeout to expire, or for some data to become available on Stdin. 180 181 if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 { 182 return errno 183 } else if stdinReady { 184 // stdin has data ready to for reading, write back all the events 185 for i := range blockingStdinSubs { 186 evt := blockingStdinSubs[i] 187 evt.errno = 0 188 writeEvent(outBuf[nevents*32:], evt) 189 nevents++ 190 } 191 } 192 193 if nevents != nsubscriptions { 194 if !mod.Memory().WriteUint32Le(resultNevents, nevents) { 195 return sys.EFAULT 196 } 197 } 198 199 return 0 200 } 201 202 // processClockEvent supports only relative name events, as that's what's used 203 // to implement sleep in various compilers including Rust, Zig and TinyGo. 204 func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) { 205 _ /* ID */ = le.Uint32(inBuf[0:8]) // See below 206 timeout := le.Uint64(inBuf[8:16]) // nanos if relative 207 _ /* precision */ = le.Uint64(inBuf[16:24]) // Unused 208 flags := le.Uint16(inBuf[24:32]) 209 210 var err sys.Errno 211 // subclockflags has only one flag defined: subscription_clock_abstime 212 switch flags { 213 case 0: // relative time 214 case 1: // subscription_clock_abstime 215 err = sys.ENOTSUP 216 default: // subclockflags has only one flag defined. 217 err = sys.EINVAL 218 } 219 220 if err != 0 { 221 return 0, err 222 } else { 223 // https://linux.die.net/man/3/clock_settime says relative timers are 224 // unaffected. Since this function only supports relative timeout, we can 225 // skip name ID validation and use a single sleep function. 226 227 return time.Duration(timeout), 0 228 } 229 } 230 231 // writeEvent writes the event corresponding to the processed subscription. 232 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct 233 func writeEvent(outBuf []byte, evt *event) { 234 copy(outBuf, evt.userData) // userdata 235 outBuf[8] = byte(evt.errno) // uint16, but safe as < 255 236 outBuf[9] = 0 237 le.PutUint32(outBuf[10:], uint32(evt.eventType)) 238 // TODO: When FD events are supported, write outOffset+16 239 }