blob: 5184aad421d3b392907ca202cc0bd62bd0e74a0d [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import (
"runtime/internal/atomic"
"unsafe"
)
// This is based on the former libgo/runtime/netpoll_select.c implementation
// except that it uses poll instead of select and is written in Go.
// It's also based on Solaris implementation for the arming mechanisms
//go:cgo_import_dynamic libc_poll poll "libc.a/shr_64.o"
//go:linkname libc_poll libc_poll
var libc_poll libFunc
//go:nosplit
func poll(pfds *pollfd, npfds uintptr, timeout uintptr) (int32, int32) {
r, err := syscall3(&libc_poll, uintptr(unsafe.Pointer(pfds)), npfds, timeout)
return int32(r), int32(err)
}
// pollfd represents the poll structure for AIX operating system.
type pollfd struct {
fd int32
events int16
revents int16
}
const _POLLIN = 0x0001
const _POLLOUT = 0x0002
const _POLLHUP = 0x2000
const _POLLERR = 0x4000
var (
pfds []pollfd
pds []*pollDesc
mtxpoll mutex
mtxset mutex
rdwake int32
wrwake int32
pendingUpdates int32
netpollWakeSig atomic.Uint32 // used to avoid duplicate calls of netpollBreak
)
func netpollinit() {
// Create the pipe we use to wakeup poll.
r, w, errno := nonblockingPipe()
if errno != 0 {
throw("netpollinit: failed to create pipe")
}
rdwake = r
wrwake = w
// Pre-allocate array of pollfd structures for poll.
pfds = make([]pollfd, 1, 128)
// Poll the read side of the pipe.
pfds[0].fd = rdwake
pfds[0].events = _POLLIN
pds = make([]*pollDesc, 1, 128)
pds[0] = nil
}
func netpollIsPollDescriptor(fd uintptr) bool {
return fd == uintptr(rdwake) || fd == uintptr(wrwake)
}
// netpollwakeup writes on wrwake to wakeup poll before any changes.
func netpollwakeup() {
if pendingUpdates == 0 {
pendingUpdates = 1
b := [1]byte{0}
write(uintptr(wrwake), unsafe.Pointer(&b[0]), 1)
}
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
lock(&mtxpoll)
netpollwakeup()
lock(&mtxset)
unlock(&mtxpoll)
pd.user = uint32(len(pfds))
pfds = append(pfds, pollfd{fd: int32(fd)})
pds = append(pds, pd)
unlock(&mtxset)
return 0
}
func netpollclose(fd uintptr) int32 {
lock(&mtxpoll)
netpollwakeup()
lock(&mtxset)
unlock(&mtxpoll)
for i := 0; i < len(pfds); i++ {
if pfds[i].fd == int32(fd) {
pfds[i] = pfds[len(pfds)-1]
pfds = pfds[:len(pfds)-1]
pds[i] = pds[len(pds)-1]
pds[i].user = uint32(i)
pds = pds[:len(pds)-1]
break
}
}
unlock(&mtxset)
return 0
}
func netpollarm(pd *pollDesc, mode int) {
lock(&mtxpoll)
netpollwakeup()
lock(&mtxset)
unlock(&mtxpoll)
switch mode {
case 'r':
pfds[pd.user].events |= _POLLIN
case 'w':
pfds[pd.user].events |= _POLLOUT
}
unlock(&mtxset)
}
// netpollBreak interrupts a poll.
func netpollBreak() {
// Failing to cas indicates there is an in-flight wakeup, so we're done here.
if !netpollWakeSig.CompareAndSwap(0, 1) {
return
}
b := [1]byte{0}
write(uintptr(wrwake), unsafe.Pointer(&b[0]), 1)
}
// netpoll checks for ready network connections.
// Returns list of goroutines that become runnable.
// delay < 0: blocks indefinitely
// delay == 0: does not block, just polls
// delay > 0: block for up to that many nanoseconds
//
//go:nowritebarrierrec
func netpoll(delay int64) gList {
var timeout uintptr
if delay < 0 {
timeout = ^uintptr(0)
} else if delay == 0 {
// TODO: call poll with timeout == 0
return gList{}
} else if delay < 1e6 {
timeout = 1
} else if delay < 1e15 {
timeout = uintptr(delay / 1e6)
} else {
// An arbitrary cap on how long to wait for a timer.
// 1e9 ms == ~11.5 days.
timeout = 1e9
}
retry:
lock(&mtxpoll)
lock(&mtxset)
pendingUpdates = 0
unlock(&mtxpoll)
n, e := poll(&pfds[0], uintptr(len(pfds)), timeout)
if n < 0 {
if e != _EINTR {
println("errno=", e, " len(pfds)=", len(pfds))
throw("poll failed")
}
unlock(&mtxset)
// If a timed sleep was interrupted, just return to
// recalculate how long we should sleep now.
if timeout > 0 {
return gList{}
}
goto retry
}
// Check if some descriptors need to be changed
if n != 0 && pfds[0].revents&(_POLLIN|_POLLHUP|_POLLERR) != 0 {
if delay != 0 {
// A netpollwakeup could be picked up by a
// non-blocking poll. Only clear the wakeup
// if blocking.
var b [1]byte
for read(rdwake, unsafe.Pointer(&b[0]), 1) == 1 {
}
netpollWakeSig.Store(0)
}
// Still look at the other fds even if the mode may have
// changed, as netpollBreak might have been called.
n--
}
var toRun gList
for i := 1; i < len(pfds) && n > 0; i++ {
pfd := &pfds[i]
var mode int32
if pfd.revents&(_POLLIN|_POLLHUP|_POLLERR) != 0 {
mode += 'r'
pfd.events &= ^_POLLIN
}
if pfd.revents&(_POLLOUT|_POLLHUP|_POLLERR) != 0 {
mode += 'w'
pfd.events &= ^_POLLOUT
}
if mode != 0 {
pds[i].setEventErr(pfd.revents == _POLLERR)
netpollready(&toRun, pds[i], mode)
n--
}
}
unlock(&mtxset)
return toRun
}