Source file src/runtime/netpoll_wasip1.go

     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.
     4  
     5  //go:build wasip1
     6  
     7  package runtime
     8  
     9  import "unsafe"
    10  
    11  // WASI network poller.
    12  //
    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.
    16  //
    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
    25  //   match.
    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
    31  //   reads and writes.
    32  //
    33  // Since poll_oneoff is similar to poll(2), the implementation here was derived
    34  // from netpoll_aix.go.
    35  
    36  const _EINTR = 27
    37  
    38  var (
    39  	evts []event
    40  	subs []subscription
    41  	pds  []*pollDesc
    42  	mtx  mutex
    43  )
    44  
    45  func netpollinit() {
    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.
    50  
    51  	subs = make([]subscription, 1, 128)
    52  	evts = make([]event, 0, 128)
    53  	pds = make([]*pollDesc, 0, 128)
    54  
    55  	timeout := &subs[0]
    56  	eventtype := timeout.u.eventtype()
    57  	*eventtype = eventtypeClock
    58  	clock := timeout.u.subscriptionClock()
    59  	clock.id = clockMonotonic
    60  	clock.precision = 1e3
    61  }
    62  
    63  func netpollIsPollDescriptor(fd uintptr) bool {
    64  	return false
    65  }
    66  
    67  func netpollopen(fd uintptr, pd *pollDesc) int32 {
    68  	lock(&mtx)
    69  
    70  	// We don't worry about pd.fdseq here,
    71  	// as mtx protects us from stale pollDescs.
    72  
    73  	pds = append(pds, pd)
    74  
    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)
    80  
    81  	unlock(&mtx)
    82  	return 0
    83  }
    84  
    85  const disarmed = 0xFFFF
    86  
    87  func netpollarm(pd *pollDesc, mode int) {
    88  	lock(&mtx)
    89  
    90  	var s subscription
    91  
    92  	s.userdata = userdata(uintptr(unsafe.Pointer(pd)))
    93  
    94  	fdReadwrite := s.u.subscriptionFdReadwrite()
    95  	fdReadwrite.fd = int32(pd.fd)
    96  
    97  	ridx := int(pd.user >> 16)
    98  	widx := int(pd.user & 0xFFFF)
    99  
   100  	if (mode == 'r' && ridx != disarmed) || (mode == 'w' && widx != disarmed) {
   101  		unlock(&mtx)
   102  		return
   103  	}
   104  
   105  	eventtype := s.u.eventtype()
   106  	switch mode {
   107  	case 'r':
   108  		*eventtype = eventtypeFdRead
   109  		ridx = len(subs)
   110  	case 'w':
   111  		*eventtype = eventtypeFdWrite
   112  		widx = len(subs)
   113  	}
   114  
   115  	if len(subs) == disarmed {
   116  		throw("overflow")
   117  	}
   118  
   119  	pd.user = uint32(ridx)<<16 | uint32(widx)
   120  
   121  	subs = append(subs, s)
   122  	evts = append(evts, event{})
   123  
   124  	unlock(&mtx)
   125  }
   126  
   127  func netpolldisarm(pd *pollDesc, mode int32) {
   128  	switch mode {
   129  	case 'r':
   130  		removesub(int(pd.user >> 16))
   131  	case 'w':
   132  		removesub(int(pd.user & 0xFFFF))
   133  	case 'r' + 'w':
   134  		removesub(int(pd.user >> 16))
   135  		removesub(int(pd.user & 0xFFFF))
   136  	}
   137  }
   138  
   139  func removesub(i int) {
   140  	if i == disarmed {
   141  		return
   142  	}
   143  	j := len(subs) - 1
   144  
   145  	pdi := (*pollDesc)(unsafe.Pointer(uintptr(subs[i].userdata)))
   146  	pdj := (*pollDesc)(unsafe.Pointer(uintptr(subs[j].userdata)))
   147  
   148  	swapsub(pdi, i, disarmed)
   149  	swapsub(pdj, j, i)
   150  
   151  	subs = subs[:j]
   152  }
   153  
   154  func swapsub(pd *pollDesc, from, to int) {
   155  	if from == to {
   156  		return
   157  	}
   158  	ridx := int(pd.user >> 16)
   159  	widx := int(pd.user & 0xFFFF)
   160  	if ridx == from {
   161  		ridx = to
   162  	} else if widx == from {
   163  		widx = to
   164  	}
   165  	pd.user = uint32(ridx)<<16 | uint32(widx)
   166  	if to != disarmed {
   167  		subs[to], subs[from] = subs[from], subs[to]
   168  	}
   169  }
   170  
   171  func netpollclose(fd uintptr) int32 {
   172  	lock(&mtx)
   173  	for i := 0; i < len(pds); i++ {
   174  		if pds[i].fd == fd {
   175  			netpolldisarm(pds[i], 'r'+'w')
   176  			pds[i] = pds[len(pds)-1]
   177  			pds = pds[:len(pds)-1]
   178  			break
   179  		}
   180  	}
   181  	unlock(&mtx)
   182  	return 0
   183  }
   184  
   185  func netpollBreak() {}
   186  
   187  func netpoll(delay int64) (gList, int32) {
   188  	lock(&mtx)
   189  
   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.
   193  	pollsubs := subs
   194  	if delay >= 0 {
   195  		timeout := &subs[0]
   196  		clock := timeout.u.subscriptionClock()
   197  		clock.timeout = uint64(delay)
   198  	} else {
   199  		pollsubs = subs[1:]
   200  	}
   201  
   202  	if len(pollsubs) == 0 {
   203  		unlock(&mtx)
   204  		return gList{}, 0
   205  	}
   206  
   207  	evts = evts[:len(pollsubs)]
   208  	for i := range evts {
   209  		evts[i] = event{}
   210  	}
   211  
   212  retry:
   213  	var nevents size
   214  	errno := poll_oneoff(unsafe.Pointer(&pollsubs[0]), unsafe.Pointer(&evts[0]), uint32(len(pollsubs)), unsafe.Pointer(&nevents))
   215  	if errno != 0 {
   216  		if errno != _EINTR {
   217  			println("errno=", errno, " len(pollsubs)=", len(pollsubs))
   218  			throw("poll_oneoff failed")
   219  		}
   220  		// If a timed sleep was interrupted, just return to
   221  		// recalculate how long we should sleep now.
   222  		if delay > 0 {
   223  			unlock(&mtx)
   224  			return gList{}, 0
   225  		}
   226  		goto retry
   227  	}
   228  
   229  	var toRun gList
   230  	delta := int32(0)
   231  	for i := 0; i < int(nevents); i++ {
   232  		e := &evts[i]
   233  		if e.typ == eventtypeClock {
   234  			continue
   235  		}
   236  
   237  		hangup := e.fdReadwrite.flags&fdReadwriteHangup != 0
   238  		var mode int32
   239  		if e.typ == eventtypeFdRead || e.error != 0 || hangup {
   240  			mode += 'r'
   241  		}
   242  		if e.typ == eventtypeFdWrite || e.error != 0 || hangup {
   243  			mode += 'w'
   244  		}
   245  		if mode != 0 {
   246  			pd := (*pollDesc)(unsafe.Pointer(uintptr(e.userdata)))
   247  			netpolldisarm(pd, mode)
   248  			pd.setEventErr(e.error != 0, 0)
   249  			delta += netpollready(&toRun, pd, mode)
   250  		}
   251  	}
   252  
   253  	unlock(&mtx)
   254  	return toRun, delta
   255  }
   256  

View as plain text