There might be some concurrent (maybe not concurrent, just sequential but in a short time window) and duplicate calls to `netpollBreak`, trying to wake up a net-poller. If one has called `netpollBreak` and that waking event hasn't been received by epollwait/kevent/..., then the subsequent calls of `netpollBreak` ought to be ignored or in other words, these calls should be converged into one.
Benchmarks go1.13.5 darwin/amd64:
benchmark-func time/op (old) time/op (new) delta
BenchmarkNetpollBreak-4 29668ns ±1% 3131ns ±2% -89.45%
mem/B (old) mem/B (new) delta
154B ±13% 0B ±0% -100%
Change-Id: I3cf757a5d6edc5a99adad7aea3baee4b7f2a8f5c
GitHub-Last-Rev:
15bcfbab8a5db51f65da01315a5880a5dbf9e028
GitHub-Pull-Request: golang/go#36294
Reviewed-on: https://go-review.googlesource.com/c/go/+/212737
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
package runtime
-import "unsafe"
+import (
+ "runtime/internal/atomic"
+ "unsafe"
+)
func epollcreate(size int32) int32
func epollcreate1(flags int32) int32
epfd int32 = -1 // epoll descriptor
netpollBreakRd, netpollBreakWr uintptr // for netpollBreak
+
+ netpollWakeSig uintptr // used to avoid duplicate calls of netpollBreak
)
func netpollinit() {
// netpollBreak interrupts an epollwait.
func netpollBreak() {
- for {
- var b byte
- n := write(netpollBreakWr, unsafe.Pointer(&b), 1)
- if n == 1 {
- break
- }
- if n == -_EINTR {
- continue
- }
- if n == -_EAGAIN {
- return
+ if atomic.Casuintptr(&netpollWakeSig, 0, 1) {
+ for {
+ var b byte
+ n := write(netpollBreakWr, unsafe.Pointer(&b), 1)
+ if n == 1 {
+ break
+ }
+ if n == -_EINTR {
+ continue
+ }
+ if n == -_EAGAIN {
+ return
+ }
+ println("runtime: netpollBreak write failed with", -n)
+ throw("runtime: netpollBreak write failed")
}
- println("runtime: netpollBreak write failed with", -n)
- throw("runtime: netpollBreak write failed")
}
}
// if blocking.
var tmp [16]byte
read(int32(netpollBreakRd), noescape(unsafe.Pointer(&tmp[0])), int32(len(tmp)))
+ atomic.Storeuintptr(&netpollWakeSig, 0)
}
continue
}
// Integrated network poller (kqueue-based implementation).
-import "unsafe"
+import (
+ "runtime/internal/atomic"
+ "unsafe"
+)
var (
kq int32 = -1
netpollBreakRd, netpollBreakWr uintptr // for netpollBreak
+
+ netpollWakeSig uintptr // used to avoid duplicate calls of netpollBreak
)
func netpollinit() {
// netpollBreak interrupts a kevent.
func netpollBreak() {
- for {
- var b byte
- n := write(netpollBreakWr, unsafe.Pointer(&b), 1)
- if n == 1 || n == -_EAGAIN {
- break
- }
- if n == -_EINTR {
- continue
+ if atomic.Casuintptr(&netpollWakeSig, 0, 1) {
+ for {
+ var b byte
+ n := write(netpollBreakWr, unsafe.Pointer(&b), 1)
+ if n == 1 || n == -_EAGAIN {
+ break
+ }
+ if n == -_EINTR {
+ continue
+ }
+ println("runtime: netpollBreak write failed with", -n)
+ throw("runtime: netpollBreak write failed")
}
- println("runtime: netpollBreak write failed with", -n)
- throw("runtime: netpollBreak write failed")
}
}
// if blocking.
var tmp [16]byte
read(int32(netpollBreakRd), noescape(unsafe.Pointer(&tmp[0])), int32(len(tmp)))
+ atomic.Storeuintptr(&netpollWakeSig, 0)
}
continue
}
--- /dev/null
+package runtime_test
+
+import (
+ "runtime"
+ "sync"
+ "testing"
+)
+
+var wg sync.WaitGroup
+
+func init() {
+ runtime.NetpollGenericInit()
+}
+
+func BenchmarkNetpollBreak(b *testing.B) {
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < 10; j++ {
+ wg.Add(1)
+ go func() {
+ runtime.NetpollBreak()
+ wg.Done()
+ }()
+ }
+ }
+ wg.Wait()
+ b.StopTimer()
+}
package runtime
-import "unsafe"
+import (
+ "runtime/internal/atomic"
+ "unsafe"
+)
// Solaris runtime-integrated network poller.
//
libc_port_dissociate,
libc_port_getn,
libc_port_alert libcFunc
+ netpollWakeSig uintptr // used to avoid duplicate calls of netpollBreak
)
func errno() int32 {
// netpollBreak interrupts a port_getn wait.
func netpollBreak() {
- // Use port_alert to put portfd into alert mode.
- // This will wake up all threads sleeping in port_getn on portfd,
- // and cause their calls to port_getn to return immediately.
- // Further, until portfd is taken out of alert mode,
- // all calls to port_getn will return immediately.
- if port_alert(portfd, _PORT_ALERT_UPDATE, _POLLHUP, uintptr(unsafe.Pointer(&portfd))) < 0 {
- if e := errno(); e != _EBUSY {
- println("runtime: port_alert failed with", e)
- throw("runtime: netpoll: port_alert failed")
+ if atomic.Casuintptr(&netpollWakeSig, 0, 1) {
+ // Use port_alert to put portfd into alert mode.
+ // This will wake up all threads sleeping in port_getn on portfd,
+ // and cause their calls to port_getn to return immediately.
+ // Further, until portfd is taken out of alert mode,
+ // all calls to port_getn will return immediately.
+ if port_alert(portfd, _PORT_ALERT_UPDATE, _POLLHUP, uintptr(unsafe.Pointer(&portfd))) < 0 {
+ if e := errno(); e != _EBUSY {
+ println("runtime: port_alert failed with", e)
+ throw("runtime: netpoll: port_alert failed")
+ }
}
}
}
println("runtime: port_alert failed with", e)
throw("runtime: netpoll: port_alert failed")
}
+ atomic.Storeuintptr(&netpollWakeSig, 0)
}
continue
}
package runtime
import (
+ "runtime/internal/atomic"
"unsafe"
)
qty uint32
}
-var iocphandle uintptr = _INVALID_HANDLE_VALUE // completion port io handle
+var (
+ iocphandle uintptr = _INVALID_HANDLE_VALUE // completion port io handle
+
+ netpollWakeSig uintptr // used to avoid duplicate calls of netpollBreak
+)
func netpollinit() {
iocphandle = stdcall4(_CreateIoCompletionPort, _INVALID_HANDLE_VALUE, 0, 0, _DWORD_MAX)
}
func netpollBreak() {
- if stdcall4(_PostQueuedCompletionStatus, iocphandle, 0, 0, 0) == 0 {
- println("runtime: netpoll: PostQueuedCompletionStatus failed (errno=", getlasterror(), ")")
- throw("runtime: netpoll: PostQueuedCompletionStatus failed")
+ if atomic.Casuintptr(&netpollWakeSig, 0, 1) {
+ if stdcall4(_PostQueuedCompletionStatus, iocphandle, 0, 0, 0) == 0 {
+ println("runtime: netpoll: PostQueuedCompletionStatus failed (errno=", getlasterror(), ")")
+ throw("runtime: netpoll: PostQueuedCompletionStatus failed")
+ }
}
}
}
handlecompletion(&toRun, op, errno, qty)
} else {
+ atomic.Storeuintptr(&netpollWakeSig, 0)
if delay == 0 {
// Forward the notification to the
// blocked poller.