mirror of
https://github.com/golang/go
synced 2024-09-23 23:10:13 -06:00
runtime: fix spurious deadlock in netpoll
There is a small possibility that runtime deadlocks when netpoll is just activated. Consider the following scenario: GOMAXPROCS=1 epfd=-1 (netpoll is not activated yet) A thread is in findrunnable, sets sched.lastpoll=0, calls netpoll(true), which returns nil. Now the thread is descheduled for some time. Then sysmon retakes a P from syscall and calls handoffp. The "If this is the last running P and nobody is polling network" check in handoffp fails, since the first thread set sched.lastpoll=0. So handoffp decides that there is already a thread that polls network and so it calls pidleput. Now the first thread is scheduled again, finds no work and calls stopm. There is no thread that polls network and so checkdead reports deadlock. To fix this, don't set sched.lastpoll=0 when netpoll is not activated. The deadlock can happen if cgo is disabled (-tag=netgo) and only on program startup (when netpoll is just activated). The test is from issue 5216 that lead to addition of the "If this is the last running P and nobody is polling network" check in handoffp. Update issue 9576. Change-Id: I9405f627a4d37bd6b99d5670d4328744aeebfc7a Reviewed-on: https://go-review.googlesource.com/2750 Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
ea4f14cf2b
commit
776aecaf6e
@ -526,3 +526,31 @@ func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
func TestNetpollDeadlock(t *testing.T) {
|
||||
output := executeTest(t, netpollDeadlockSource, nil)
|
||||
want := "done\n"
|
||||
if !strings.HasSuffix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
const netpollDeadlockSource = `
|
||||
package main
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
func init() {
|
||||
fmt.Println("dialing")
|
||||
c, err := net.Dial("tcp", "localhost:14356")
|
||||
if err == nil {
|
||||
c.Close()
|
||||
} else {
|
||||
fmt.Println("error: ", err)
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
fmt.Println("done")
|
||||
}
|
||||
`
|
||||
|
@ -69,11 +69,19 @@ type pollCache struct {
|
||||
// seq is incremented when deadlines are changed or descriptor is reused.
|
||||
}
|
||||
|
||||
var pollcache pollCache
|
||||
var (
|
||||
netpollInited uint32
|
||||
pollcache pollCache
|
||||
)
|
||||
|
||||
//go:linkname net_runtime_pollServerInit net.runtime_pollServerInit
|
||||
func net_runtime_pollServerInit() {
|
||||
netpollinit()
|
||||
atomicstore(&netpollInited, 1)
|
||||
}
|
||||
|
||||
func netpollinited() bool {
|
||||
return atomicload(&netpollInited) != 0
|
||||
}
|
||||
|
||||
//go:linkname net_runtime_pollOpen net.runtime_pollOpen
|
||||
|
@ -1338,7 +1338,7 @@ stop:
|
||||
}
|
||||
|
||||
// poll network
|
||||
if xchg64(&sched.lastpoll, 0) != 0 {
|
||||
if netpollinited() && xchg64(&sched.lastpoll, 0) != 0 {
|
||||
if _g_.m.p != nil {
|
||||
throw("findrunnable: netpoll with p")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user