]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: fix spurious deadlock in netpoll
authorDmitry Vyukov <dvyukov@google.com>
Tue, 13 Jan 2015 17:12:50 +0000 (20:12 +0300)
committerDmitry Vyukov <dvyukov@google.com>
Wed, 14 Jan 2015 16:41:17 +0000 (16:41 +0000)
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>
src/runtime/crash_test.go
src/runtime/netpoll.go
src/runtime/proc1.go

index 24fe338b91ac18a890bf4b7f41a6f42840d2a39a..43cea9008a33788a298181ab4e40c5b8157cf531 100644 (file)
@@ -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")
+}
+`
index 0bd372319a17ea888822040cd3f672fa0cea366c..3ef45064914c5ccd4bc0e81efeac0c513e3d2103 100644 (file)
@@ -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
index 22ea7a9d279666a2fd671a7672e592c332e2a32e..6fa407f0ce2f29e01e150994955a611218a9a10f 100644 (file)
@@ -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")
                }