Skip to content

Commit 4e7067c

Browse files
committed
runtime: mark extra M's G as dead when not in use
Currently the extra Ms created for cgo callbacks have a corresponding G that's kept in syscall state with only a call to goexit on its stack. This leads to confusing output from runtime.NumGoroutines and in tracebacks: goroutine 17 [syscall, locked to thread]: runtime.goexit() .../src/runtime/asm_amd64.s:2197 +0x1 Fix this by putting this goroutine into state _Gdead when it's not in use instead of _Gsyscall. To keep the goroutine counts correct, we also add one to sched.ngsys while the goroutine is in _Gdead. The effect of this is as if the goroutine simply doesn't exist when it's not in use. Fixes #16631. Fixes #16714. Change-Id: Ieae08a2febd4b3d00bef5c23fd6ca88fb2bb0087 Reviewed-on: https://go-review.googlesource.com/45030 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
1 parent b5a0f71 commit 4e7067c

File tree

3 files changed

+125
-3
lines changed

3 files changed

+125
-3
lines changed

src/runtime/crash_cgo_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,12 @@ func TestRaceSignal(t *testing.T) {
395395
t.Errorf("expected %q got %s", want, got)
396396
}
397397
}
398+
399+
func TestCgoNumGoroutine(t *testing.T) {
400+
t.Parallel()
401+
got := runTestProg(t, "testprogcgo", "NumGoroutine")
402+
want := "OK\n"
403+
if got != want {
404+
t.Errorf("expected %q got %v", want, got)
405+
}
406+
}

src/runtime/proc.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,10 @@ func needm(x byte) {
14351435
// Initialize this thread to use the m.
14361436
asminit()
14371437
minit()
1438+
1439+
// mp.curg is now a real goroutine.
1440+
casgstatus(mp.curg, _Gdead, _Gsyscall)
1441+
atomic.Xadd(&sched.ngsys, -1)
14381442
}
14391443

14401444
var earlycgocallback = []byte("fatal error: cgo callback before cgo call\n")
@@ -1477,9 +1481,11 @@ func oneNewExtraM() {
14771481
gp.stktopsp = gp.sched.sp
14781482
gp.gcscanvalid = true
14791483
gp.gcscandone = true
1480-
// malg returns status as Gidle, change to Gsyscall before adding to allg
1481-
// where GC will see it.
1482-
casgstatus(gp, _Gidle, _Gsyscall)
1484+
// malg returns status as _Gidle. Change to _Gdead before
1485+
// adding to allg where GC can see it. We use _Gdead to hide
1486+
// this from tracebacks and stack scans since it isn't a
1487+
// "real" goroutine until needm grabs it.
1488+
casgstatus(gp, _Gidle, _Gdead)
14831489
gp.m = mp
14841490
mp.curg = gp
14851491
mp.locked = _LockInternal
@@ -1492,6 +1498,12 @@ func oneNewExtraM() {
14921498
// put on allg for garbage collector
14931499
allgadd(gp)
14941500

1501+
// gp is now on the allg list, but we don't want it to be
1502+
// counted by gcount. It would be more "proper" to increment
1503+
// sched.ngfree, but that requires locking. Incrementing ngsys
1504+
// has the same effect.
1505+
atomic.Xadd(&sched.ngsys, +1)
1506+
14951507
// Add m to the extra list.
14961508
mnext := lockextra(true)
14971509
mp.schedlink.set(mnext)
@@ -1528,6 +1540,10 @@ func dropm() {
15281540
// with no pointer manipulation.
15291541
mp := getg().m
15301542

1543+
// Return mp.curg to dead state.
1544+
casgstatus(mp.curg, _Gsyscall, _Gdead)
1545+
atomic.Xadd(&sched.ngsys, +1)
1546+
15311547
// Block signals before unminit.
15321548
// Unminit unregisters the signal handling stack (but needs g on some systems).
15331549
// Setg(nil) clears g, which is the signal handler's cue not to run Go handlers.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
/*
8+
#include <stddef.h>
9+
#include <pthread.h>
10+
11+
extern void CallbackNumGoroutine();
12+
13+
static void* thread2(void* arg __attribute__ ((unused))) {
14+
CallbackNumGoroutine();
15+
return NULL;
16+
}
17+
18+
static void CheckNumGoroutine() {
19+
pthread_t tid;
20+
pthread_create(&tid, NULL, thread2, NULL);
21+
pthread_join(tid, NULL);
22+
}
23+
*/
24+
import "C"
25+
26+
import (
27+
"fmt"
28+
"runtime"
29+
"strings"
30+
)
31+
32+
var baseGoroutines int
33+
34+
func init() {
35+
register("NumGoroutine", NumGoroutine)
36+
}
37+
38+
func NumGoroutine() {
39+
// Test that there are just the expected number of goroutines
40+
// running. Specifically, test that the spare M's goroutine
41+
// doesn't show up.
42+
//
43+
// On non-Windows platforms there's a signal handling thread
44+
// started by os/signal.init in addition to the main
45+
// goroutine.
46+
if runtime.GOOS != "windows" {
47+
baseGoroutines = 1
48+
}
49+
if _, ok := checkNumGoroutine("first", 1+baseGoroutines); !ok {
50+
return
51+
}
52+
53+
// Test that the goroutine for a callback from C appears.
54+
if C.CheckNumGoroutine(); !callbackok {
55+
return
56+
}
57+
58+
// Make sure we're back to the initial goroutines.
59+
if _, ok := checkNumGoroutine("third", 1+baseGoroutines); !ok {
60+
return
61+
}
62+
63+
fmt.Println("OK")
64+
}
65+
66+
func checkNumGoroutine(label string, want int) (string, bool) {
67+
n := runtime.NumGoroutine()
68+
if n != want {
69+
fmt.Printf("%s NumGoroutine: want %d; got %d\n", label, want, n)
70+
return "", false
71+
}
72+
73+
sbuf := make([]byte, 32<<10)
74+
sbuf = sbuf[:runtime.Stack(sbuf, true)]
75+
n = strings.Count(string(sbuf), "goroutine ")
76+
if n != want {
77+
fmt.Printf("%s Stack: want %d; got %d:\n%s\n", label, want, n, string(sbuf))
78+
return "", false
79+
}
80+
return string(sbuf), true
81+
}
82+
83+
var callbackok bool
84+
85+
//export CallbackNumGoroutine
86+
func CallbackNumGoroutine() {
87+
stk, ok := checkNumGoroutine("second", 2+baseGoroutines)
88+
if !ok {
89+
return
90+
}
91+
if !strings.Contains(stk, "CallbackNumGoroutine") {
92+
fmt.Printf("missing CallbackNumGoroutine from stack:\n%s\n", stk)
93+
return
94+
}
95+
96+
callbackok = true
97+
}

0 commit comments

Comments
 (0)