1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
11 // WASI network poller.
13 // WASI preview 1 includes a poll_oneoff host function that behaves similarly
14 // to poll(2) on Linux. Like poll(2), poll_oneoff is level triggered. It
15 // accepts one or more subscriptions to FD read or write events.
17 // Major differences to poll(2):
18 // - the events are not written to the input entries (like pollfd.revents), and
19 // instead are appended to a separate events buffer. poll_oneoff writes zero
20 // or more events to the buffer (at most one per input subscription) and
21 // returns the number of events written. Although the index of the
22 // subscriptions might not match the index of the associated event in the
23 // events buffer, both the subscription and event structs contain a userdata
24 // field and when a subscription yields an event the userdata fields will
26 // - there's no explicit timeout parameter, although a time limit can be added
27 // by using "clock" subscriptions.
28 // - each FD subscription can either be for a read or a write, but not both.
29 // This is in contrast to poll(2) which accepts a mask with POLLIN and
30 // POLLOUT bits, allowing for a subscription to either, neither, or both
33 // Since poll_oneoff is similar to poll(2), the implementation here was derived
34 // from netpoll_aix.go.
46 // Unlike poll(2), WASI's poll_oneoff doesn't accept a timeout directly. To
47 // prevent it from blocking indefinitely, a clock subscription with a
48 // timeout field needs to be submitted. Reserve a slot here for the clock
49 // subscription, and set fields that won't change between poll_oneoff calls.
51 subs = make([]subscription, 1, 128)
52 evts = make([]event, 0, 128)
53 pds = make([]*pollDesc, 0, 128)
56 eventtype := timeout.u.eventtype()
57 *eventtype = eventtypeClock
58 clock := timeout.u.subscriptionClock()
59 clock.id = clockMonotonic
63 func netpollIsPollDescriptor(fd uintptr) bool {
67 func netpollopen(fd uintptr, pd *pollDesc) int32 {
70 // We don't worry about pd.fdseq here,
71 // as mtx protects us from stale pollDescs.
75 // The 32-bit pd.user field holds the index of the read subscription in the
76 // upper 16 bits, and index of the write subscription in the lower bits.
77 // A disarmed=^uint16(0) sentinel is used to represent no subscription.
78 // There is thus a maximum of 65535 total subscriptions.
79 pd.user = uint32(disarmed)<<16 | uint32(disarmed)
85 const disarmed = 0xFFFF
87 func netpollarm(pd *pollDesc, mode int) {
92 s.userdata = userdata(uintptr(unsafe.Pointer(pd)))
94 fdReadwrite := s.u.subscriptionFdReadwrite()
95 fdReadwrite.fd = int32(pd.fd)
97 ridx := int(pd.user >> 16)
98 widx := int(pd.user & 0xFFFF)
100 if (mode == 'r' && ridx != disarmed) || (mode == 'w' && widx != disarmed) {
105 eventtype := s.u.eventtype()
108 *eventtype = eventtypeFdRead
111 *eventtype = eventtypeFdWrite
115 if len(subs) == disarmed {
119 pd.user = uint32(ridx)<<16 | uint32(widx)
121 subs = append(subs, s)
122 evts = append(evts, event{})
127 func netpolldisarm(pd *pollDesc, mode int32) {
130 removesub(int(pd.user >> 16))
132 removesub(int(pd.user & 0xFFFF))
134 removesub(int(pd.user >> 16))
135 removesub(int(pd.user & 0xFFFF))
139 func removesub(i int) {
145 pdi := (*pollDesc)(unsafe.Pointer(uintptr(subs[i].userdata)))
146 pdj := (*pollDesc)(unsafe.Pointer(uintptr(subs[j].userdata)))
148 swapsub(pdi, i, disarmed)
154 func swapsub(pd *pollDesc, from, to int) {
158 ridx := int(pd.user >> 16)
159 widx := int(pd.user & 0xFFFF)
162 } else if widx == from {
165 pd.user = uint32(ridx)<<16 | uint32(widx)
167 subs[to], subs[from] = subs[from], subs[to]
171 func netpollclose(fd uintptr) int32 {
173 for i := 0; i < len(pds); i++ {
175 netpolldisarm(pds[i], 'r'+'w')
176 pds[i] = pds[len(pds)-1]
177 pds = pds[:len(pds)-1]
185 func netpollBreak() {}
187 func netpoll(delay int64) gList {
190 // If delay >= 0, we include a subscription of type Clock that we use as
191 // a timeout. If delay < 0, we omit the subscription and allow poll_oneoff
192 // to block indefinitely.
196 clock := timeout.u.subscriptionClock()
197 clock.timeout = uint64(delay)
202 if len(pollsubs) == 0 {
207 evts = evts[:len(pollsubs)]
208 for i := range evts {
214 errno := poll_oneoff(unsafe.Pointer(&pollsubs[0]), unsafe.Pointer(&evts[0]), uint32(len(pollsubs)), unsafe.Pointer(&nevents))
217 println("errno=", errno, " len(pollsubs)=", len(pollsubs))
218 throw("poll_oneoff failed")
220 // If a timed sleep was interrupted, just return to
221 // recalculate how long we should sleep now.
230 for i := 0; i < int(nevents); i++ {
232 if e.typ == eventtypeClock {
236 hangup := e.fdReadwrite.flags&fdReadwriteHangup != 0
238 if e.typ == eventtypeFdRead || e.error != 0 || hangup {
241 if e.typ == eventtypeFdWrite || e.error != 0 || hangup {
245 pd := (*pollDesc)(unsafe.Pointer(uintptr(e.userdata)))
246 netpolldisarm(pd, mode)
247 pd.setEventErr(e.error != 0, 0)
248 netpollready(&toRun, pd, mode)