]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: crash when func main calls Goexit and all other goroutines exit
authorRuss Cox <rsc@golang.org>
Wed, 16 Apr 2014 17:12:18 +0000 (13:12 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 16 Apr 2014 17:12:18 +0000 (13:12 -0400)
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).

Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711

There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.

The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.

Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.

Fixes #7711.

LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044

doc/go1.3.html
src/pkg/runtime/crash_test.go
src/pkg/runtime/extern.go
src/pkg/runtime/proc.c

index 916ed04d25ae6fd8c4124bec0d702f5cb3e4b52c..466eeb1a16e4751fd463cca2fc9bc2b1ba8ed2bc 100644 (file)
@@ -379,6 +379,15 @@ In particular, it only calls <a href="/pkg/os/exec/#LookPath"><code>LookPath</co
 when the binary's file name contains no path separators.
 </li>
 
+<li>
+If the main goroutine calls 
+<a href="/pkg/runtime/#Goexit"><code>runtime.Goexit</code>
+and all other goroutines finish execution, the program now always crashes,
+reporting a detected deadlock.
+Earlier versions of Go handled this situation inconsistently: most instances
+were reported as deadlocks, but some trivial cases exited cleanly instead.
+</li>
+
 <li>
 The <a href="/pkg/strconv/#CanBackquote"><code>CanBackquote</code></a>
 function in the <a href="/pkg/strconv/"><code>strconv</code></a> package
index cd9520b165e3f56764270ecb4fcbd0f1fb796365..dbcd9486de43712ca0d6b981526b964b264f15d3 100644 (file)
@@ -111,8 +111,9 @@ func TestLockedDeadlock2(t *testing.T) {
 
 func TestGoexitDeadlock(t *testing.T) {
        output := executeTest(t, goexitDeadlockSource, nil)
-       if output != "" {
-               t.Fatalf("expected no output, got:\n%s", output)
+       want := "no goroutines (main called runtime.Goexit) - deadlock!"
+       if !strings.Contains(output, want) {
+               t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
        }
 }
 
@@ -144,13 +145,12 @@ panic: again
 
 }
 
-func TestGoexitExit(t *testing.T) {
+func TestGoexitCrash(t *testing.T) {
        output := executeTest(t, goexitExitSource, nil)
-       want := ""
-       if output != want {
-               t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
+       want := "no goroutines (main called runtime.Goexit) - deadlock!"
+       if !strings.Contains(output, want) {
+               t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
        }
-
 }
 
 const crashSource = `
index 0c5041d38b169ffd33813823ec4a93bd1f1a6bde..2466911dd695d9129009b2a0b5dab79770738715 100644 (file)
@@ -79,6 +79,11 @@ func Gosched()
 
 // Goexit terminates the goroutine that calls it.  No other goroutine is affected.
 // Goexit runs all deferred calls before terminating the goroutine.
+//
+// Calling Goexit from the main goroutine terminates that goroutine
+// without func main returning. Since func main has not returned,
+// the program continues execution of other goroutines.
+// If all other goroutines exit, the program crashes.
 func Goexit()
 
 // Caller reports file and line number information about function invocations on
index 6b5c031c8748c596e37b4ed49a47450c35d90fe7..52b02d94bb4b2741d92edf046e2ffc93b0446fea 100644 (file)
@@ -2501,7 +2501,7 @@ checkdead(void)
        }
        runtime·unlock(&allglock);
        if(grunning == 0)  // possible if main goroutine calls runtime·Goexit()
-               runtime·exit(0);
+               runtime·throw("no goroutines (main called runtime.Goexit) - deadlock!");
        m->throwing = -1;  // do not dump full stacks
        runtime·throw("all goroutines are asleep - deadlock!");
 }