Skip to content

Commit

Permalink
runtime: change netpoll to take an amount of time to block
Browse files Browse the repository at this point in the history
This new facility will be used by future CLs in this series.

Change the only blocking call to netpoll to do the right thing when
netpoll returns an empty list.

Updates #6239
Updates #27707

Change-Id: I58b3c2903eda61a3698b1a4729ed0e81382bb1ed
Reviewed-on: https://go-review.googlesource.com/c/go/+/171821
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
  • Loading branch information
ianlancetaylor committed Oct 15, 2019
1 parent 6da300b commit 831e3cf
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 57 deletions.
1 change: 1 addition & 0 deletions src/runtime/defs1_solaris_amd64.go
Expand Up @@ -8,6 +8,7 @@ const (
_EBADF = 0x9
_EFAULT = 0xe
_EAGAIN = 0xb
_ETIME = 0x3e
_ETIMEDOUT = 0x91
_EWOULDBLOCK = 0xb
_EINPROGRESS = 0x96
Expand Down
1 change: 1 addition & 0 deletions src/runtime/defs_solaris.go
Expand Up @@ -38,6 +38,7 @@ const (
EBADF = C.EBADF
EFAULT = C.EFAULT
EAGAIN = C.EAGAIN
ETIME = C.ETIME
ETIMEDOUT = C.ETIMEDOUT
EWOULDBLOCK = C.EWOULDBLOCK
EINPROGRESS = C.EINPROGRESS
Expand Down
31 changes: 24 additions & 7 deletions src/runtime/netpoll_aix.go
Expand Up @@ -148,12 +148,27 @@ func netpollarm(pd *pollDesc, mode int) {
unlock(&mtxset)
}

// 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(block bool) gList {
timeout := ^uintptr(0)
if !block {
timeout = 0
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)
Expand All @@ -168,6 +183,11 @@ retry:
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
Expand Down Expand Up @@ -203,8 +223,5 @@ retry:
}
}
unlock(&mtxset)
if block && toRun.empty() {
goto retry
}
return toRun
}
31 changes: 23 additions & 8 deletions src/runtime/netpoll_epoll.go
Expand Up @@ -56,15 +56,28 @@ func netpollarm(pd *pollDesc, mode int) {
throw("runtime: unused")
}

// polls for ready network connections
// returns list of goroutines that become runnable
func netpoll(block bool) gList {
// 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
func netpoll(delay int64) gList {
if epfd == -1 {
return gList{}
}
waitms := int32(-1)
if !block {
var waitms int32
if delay < 0 {
waitms = -1
} else if delay == 0 {
waitms = 0
} else if delay < 1e6 {
waitms = 1
} else if delay < 1e15 {
waitms = int32(delay / 1e6)
} else {
// An arbitrary cap on how long to wait for a timer.
// 1e9 ms == ~11.5 days.
waitms = 1e9
}
var events [128]epollevent
retry:
Expand All @@ -74,6 +87,11 @@ retry:
println("runtime: epollwait on fd", epfd, "failed with", -n)
throw("runtime: netpoll failed")
}
// If a timed sleep was interrupted, just return to
// recalculate how long we should sleep now.
if waitms > 0 {
return gList{}
}
goto retry
}
var toRun gList
Expand All @@ -98,8 +116,5 @@ retry:
netpollready(&toRun, pd, mode)
}
}
if block && toRun.empty() {
goto retry
}
return toRun
}
2 changes: 1 addition & 1 deletion src/runtime/netpoll_fake.go
Expand Up @@ -27,6 +27,6 @@ func netpollclose(fd uintptr) int32 {
func netpollarm(pd *pollDesc, mode int) {
}

func netpoll(block bool) gList {
func netpoll(delay int64) gList {
return gList{}
}
26 changes: 20 additions & 6 deletions src/runtime/netpoll_kqueue.go
Expand Up @@ -57,15 +57,27 @@ func netpollarm(pd *pollDesc, mode int) {
throw("runtime: unused")
}

// Polls for ready network connections.
// netpoll checks for ready network connections.
// Returns list of goroutines that become runnable.
func netpoll(block bool) gList {
// delay < 0: blocks indefinitely
// delay == 0: does not block, just polls
// delay > 0: block for up to that many nanoseconds
func netpoll(delay int64) gList {
if kq == -1 {
return gList{}
}
var tp *timespec
var ts timespec
if !block {
if delay < 0 {
tp = nil
} else if delay == 0 {
tp = &ts
} else {
ts.setNsec(delay)
if ts.tv_sec > 1e6 {
// Darwin returns EINVAL if the sleep time is too long.
ts.tv_sec = 1e6
}
tp = &ts
}
var events [64]keventt
Expand All @@ -76,6 +88,11 @@ retry:
println("runtime: kevent on fd", kq, "failed with", -n)
throw("runtime: netpoll failed")
}
// If a timed sleep was interrupted, just return to
// recalculate how long we should sleep now.
if delay > 0 {
return gList{}
}
goto retry
}
var toRun gList
Expand Down Expand Up @@ -110,8 +127,5 @@ retry:
netpollready(&toRun, pd, mode)
}
}
if block && toRun.empty() {
goto retry
}
return toRun
}
35 changes: 25 additions & 10 deletions src/runtime/netpoll_solaris.go
Expand Up @@ -178,27 +178,45 @@ func netpollarm(pd *pollDesc, mode int) {
unlock(&pd.lock)
}

// polls for ready network connections
// returns list of goroutines that become runnable
func netpoll(block bool) gList {
// 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
func netpoll(delay int64) gList {
if portfd == -1 {
return gList{}
}

var wait *timespec
var zero timespec
if !block {
wait = &zero
var ts timespec
if delay < 0 {
wait = nil
} else if delay == 0 {
wait = &ts
} else {
ts.setNsec(delay)
if ts.tv_sec > 1e6 {
// An arbitrary cap on how long to wait for a timer.
// 1e6 s == ~11.5 days.
ts.tv_sec = 1e6
}
wait = &ts
}

var events [128]portevent
retry:
var n uint32 = 1
if port_getn(portfd, &events[0], uint32(len(events)), &n, wait) < 0 {
if e := errno(); e != _EINTR {
if e := errno(); e != _EINTR && e != _ETIME {
print("runtime: port_getn on fd ", portfd, " failed (errno=", e, ")\n")
throw("runtime: netpoll failed")
}
// If a timed sleep was interrupted, just return to
// recalculate how long we should sleep now.
if delay > 0 {
return gList{}
}
goto retry
}

Expand Down Expand Up @@ -242,8 +260,5 @@ retry:
}
}

if block && toRun.empty() {
goto retry
}
return toRun
}
2 changes: 1 addition & 1 deletion src/runtime/netpoll_stub.go
Expand Up @@ -10,7 +10,7 @@ var netpollWaiters uint32

// Polls for ready network connections.
// Returns list of goroutines that become runnable.
func netpoll(block bool) gList {
func netpoll(delay int64) gList {
// Implementation for platforms that do not support
// integrated network poller.
return gList{}
Expand Down
33 changes: 21 additions & 12 deletions src/runtime/netpoll_windows.go
Expand Up @@ -61,9 +61,12 @@ func netpollarm(pd *pollDesc, mode int) {
throw("runtime: unused")
}

// Polls for completed network IO.
// netpoll checks for ready network connections.
// Returns list of goroutines that become runnable.
func netpoll(block bool) gList {
// delay < 0: blocks indefinitely
// delay == 0: does not block, just polls
// delay > 0: block for up to that many nanoseconds
func netpoll(delay int64) gList {
var entries [64]overlappedEntry
var wait, qty, key, flags, n, i uint32
var errno int32
Expand All @@ -75,23 +78,32 @@ func netpoll(block bool) gList {
if iocphandle == _INVALID_HANDLE_VALUE {
return gList{}
}
wait = 0
if block {
if delay < 0 {
wait = _INFINITE
} else if delay == 0 {
wait = 0
} else if delay < 1e6 {
wait = 1
} else if delay < 1e15 {
wait = uint32(delay / 1e6)
} else {
// An arbitrary cap on how long to wait for a timer.
// 1e9 ms == ~11.5 days.
wait = 1e9
}
retry:

if _GetQueuedCompletionStatusEx != nil {
n = uint32(len(entries) / int(gomaxprocs))
if n < 8 {
n = 8
}
if block {
if delay != 0 {
mp.blocked = true
}
if stdcall6(_GetQueuedCompletionStatusEx, iocphandle, uintptr(unsafe.Pointer(&entries[0])), uintptr(n), uintptr(unsafe.Pointer(&n)), uintptr(wait), 0) == 0 {
mp.blocked = false
errno = int32(getlasterror())
if !block && errno == _WAIT_TIMEOUT {
if errno == _WAIT_TIMEOUT {
return gList{}
}
println("runtime: GetQueuedCompletionStatusEx failed (errno=", errno, ")")
Expand All @@ -111,13 +123,13 @@ retry:
op = nil
errno = 0
qty = 0
if block {
if delay != 0 {
mp.blocked = true
}
if stdcall5(_GetQueuedCompletionStatus, iocphandle, uintptr(unsafe.Pointer(&qty)), uintptr(unsafe.Pointer(&key)), uintptr(unsafe.Pointer(&op)), uintptr(wait)) == 0 {
mp.blocked = false
errno = int32(getlasterror())
if !block && errno == _WAIT_TIMEOUT {
if errno == _WAIT_TIMEOUT {
return gList{}
}
if op == nil {
Expand All @@ -129,9 +141,6 @@ retry:
mp.blocked = false
handlecompletion(&toRun, op, errno, qty)
}
if block && toRun.empty() {
goto retry
}
return toRun
}

Expand Down

0 comments on commit 831e3cf

Please sign in to comment.