Commit da8cf543 authored by Keith Randall's avatar Keith Randall

runtime: always run semacquire on the G stack

semacquire might need to park the currently running G.  It can
only park if called from the G stack (because it has no way of
saving the M stack state).  So all calls to semacquire must come
from the G stack.

The three violators are GOMAXPROCS, ReadMemStats, and WriteHeapDump.
This change moves the semacquire call earlier, out of their C code
and into their Go code.

This seldom caused bugs because semacquire seldom actually had
to park the caller.  But it did happen intermittently.

Fixes #8749

LGTM=dvyukov
R=golang-codereviews, dvyukov, bradfitz
CC=golang-codereviews
https://golang.org/cl/144940043
parent e28746c4
...@@ -24,15 +24,29 @@ func UnlockOSThread() ...@@ -24,15 +24,29 @@ func UnlockOSThread()
// The number of logical CPUs on the local machine can be queried with NumCPU. // The number of logical CPUs on the local machine can be queried with NumCPU.
// This call will go away when the scheduler improves. // This call will go away when the scheduler improves.
func GOMAXPROCS(n int) int { func GOMAXPROCS(n int) int {
g := getg() if n > _MaxGomaxprocs {
g.m.scalararg[0] = uintptr(n) n = _MaxGomaxprocs
onM(gomaxprocs_m) }
n = int(g.m.scalararg[0]) lock(&sched.lock)
g.m.scalararg[0] = 0 ret := int(gomaxprocs)
return n unlock(&sched.lock)
} if n <= 0 || n == ret {
return ret
}
func gomaxprocs_m() // proc.c semacquire(&worldsema, false)
gp := getg()
gp.m.gcing = 1
onM(stoptheworld)
// newprocs will be processed by starttheworld
newprocs = int32(n)
gp.m.gcing = 0
semrelease(&worldsema)
onM(starttheworld)
return ret
}
// NumCPU returns the number of logical CPUs on the local machine. // NumCPU returns the number of logical CPUs on the local machine.
func NumCPU() int { func NumCPU() int {
......
...@@ -737,33 +737,16 @@ mdump(void) ...@@ -737,33 +737,16 @@ mdump(void)
flush(); flush();
} }
static void writeheapdump_m(void);
#pragma textflag NOSPLIT
void void
runtimedebug·WriteHeapDump(uintptr fd) runtime·writeheapdump_m(void)
{
void (*fn)(void);
g->m->scalararg[0] = fd;
fn = writeheapdump_m;
runtime·onM(&fn);
}
static void
writeheapdump_m(void)
{ {
uintptr fd; uintptr fd;
fd = g->m->scalararg[0]; fd = g->m->scalararg[0];
g->m->scalararg[0] = 0; g->m->scalararg[0] = 0;
// Stop the world.
runtime·casgstatus(g->m->curg, Grunning, Gwaiting); runtime·casgstatus(g->m->curg, Grunning, Gwaiting);
g->waitreason = runtime·gostringnocopy((byte*)"dumping heap"); g->waitreason = runtime·gostringnocopy((byte*)"dumping heap");
runtime·semacquire(&runtime·worldsema, false);
g->m->gcing = 1;
runtime·stoptheworld();
// Update stats so we can dump them. // Update stats so we can dump them.
// As a side effect, flushes all the MCaches so the MSpan.freelist // As a side effect, flushes all the MCaches so the MSpan.freelist
...@@ -784,13 +767,7 @@ writeheapdump_m(void) ...@@ -784,13 +767,7 @@ writeheapdump_m(void)
tmpbufsize = 0; tmpbufsize = 0;
} }
// Start up the world again.
g->m->gcing = 0;
g->m->locks++;
runtime·semrelease(&runtime·worldsema);
runtime·starttheworld();
runtime·casgstatus(g->m->curg, Gwaiting, Grunning); runtime·casgstatus(g->m->curg, Gwaiting, Grunning);
g->m->locks--;
} }
// dumpint() the kind & offset of each field in an object. // dumpint() the kind & offset of each field in an object.
......
...@@ -69,4 +69,39 @@ func init() { ...@@ -69,4 +69,39 @@ func init() {
} }
// ReadMemStats populates m with memory allocator statistics. // ReadMemStats populates m with memory allocator statistics.
func ReadMemStats(m *MemStats) func ReadMemStats(m *MemStats) {
// Have to acquire worldsema to stop the world,
// because stoptheworld can only be used by
// one goroutine at a time, and there might be
// a pending garbage collection already calling it.
semacquire(&worldsema, false)
gp := getg()
gp.m.gcing = 1
onM(stoptheworld)
gp.m.ptrarg[0] = noescape(unsafe.Pointer(m))
onM(readmemstats_m)
gp.m.gcing = 0
gp.m.locks++
semrelease(&worldsema)
onM(starttheworld)
gp.m.locks--
}
// Implementation of runtime/debug.WriteHeapDump
func writeHeapDump(fd uintptr) {
semacquire(&worldsema, false)
gp := getg()
gp.m.gcing = 1
onM(stoptheworld)
gp.m.scalararg[0] = fd
onM(writeheapdump_m)
gp.m.gcing = 0
gp.m.locks++
semrelease(&worldsema)
onM(starttheworld)
gp.m.locks--
}
...@@ -1451,32 +1451,14 @@ extern uintptr runtime·sizeof_C_MStats; ...@@ -1451,32 +1451,14 @@ extern uintptr runtime·sizeof_C_MStats;
static void readmemstats_m(void); static void readmemstats_m(void);
#pragma textflag NOSPLIT
void void
runtime·ReadMemStats(MStats *stats) runtime·readmemstats_m(void)
{
void (*fn)(void);
g->m->ptrarg[0] = stats;
fn = readmemstats_m;
runtime·onM(&fn);
}
static void
readmemstats_m(void)
{ {
MStats *stats; MStats *stats;
stats = g->m->ptrarg[0]; stats = g->m->ptrarg[0];
g->m->ptrarg[0] = nil; g->m->ptrarg[0] = nil;
// Have to acquire worldsema to stop the world,
// because stoptheworld can only be used by
// one goroutine at a time, and there might be
// a pending garbage collection already calling it.
runtime·semacquire(&runtime·worldsema, false);
g->m->gcing = 1;
runtime·stoptheworld();
runtime·updatememstats(nil); runtime·updatememstats(nil);
// Size of the trailing by_size array differs between Go and C, // Size of the trailing by_size array differs between Go and C,
// NumSizeClasses was changed, but we can not change Go struct because of backward compatibility. // NumSizeClasses was changed, but we can not change Go struct because of backward compatibility.
...@@ -1486,12 +1468,6 @@ readmemstats_m(void) ...@@ -1486,12 +1468,6 @@ readmemstats_m(void)
stats->stacks_sys = stats->stacks_inuse; stats->stacks_sys = stats->stacks_inuse;
stats->heap_inuse -= stats->stacks_inuse; stats->heap_inuse -= stats->stacks_inuse;
stats->heap_sys -= stats->stacks_inuse; stats->heap_sys -= stats->stacks_inuse;
g->m->gcing = 0;
g->m->locks++;
runtime·semrelease(&runtime·worldsema);
runtime·starttheworld();
g->m->locks--;
} }
static void readgcstats_m(void); static void readgcstats_m(void);
......
...@@ -24,42 +24,6 @@ ...@@ -24,42 +24,6 @@
// //
// Design doc at http://golang.org/s/go11sched. // Design doc at http://golang.org/s/go11sched.
typedef struct Sched Sched;
struct Sched {
Mutex lock;
uint64 goidgen;
M* midle; // idle m's waiting for work
int32 nmidle; // number of idle m's waiting for work
int32 nmidlelocked; // number of locked m's waiting for work
int32 mcount; // number of m's that have been created
int32 maxmcount; // maximum number of m's allowed (or die)
P* pidle; // idle P's
uint32 npidle;
uint32 nmspinning;
// Global runnable queue.
G* runqhead;
G* runqtail;
int32 runqsize;
// Global cache of dead G's.
Mutex gflock;
G* gfree;
int32 ngfree;
uint32 gcwaiting; // gc is waiting to run
int32 stopwait;
Note stopnote;
uint32 sysmonwait;
Note sysmonnote;
uint64 lastpoll;
int32 profilehz; // cpu profiling rate
};
enum enum
{ {
// Number of goroutine ids to grab from runtime·sched.goidgen to local per-P cache at once. // Number of goroutine ids to grab from runtime·sched.goidgen to local per-P cache at once.
...@@ -67,7 +31,7 @@ enum ...@@ -67,7 +31,7 @@ enum
GoidCacheBatch = 16, GoidCacheBatch = 16,
}; };
Sched runtime·sched; SchedType runtime·sched;
int32 runtime·gomaxprocs; int32 runtime·gomaxprocs;
uint32 runtime·needextram; uint32 runtime·needextram;
bool runtime·iscgo; bool runtime·iscgo;
...@@ -79,7 +43,7 @@ M* runtime·extram; ...@@ -79,7 +43,7 @@ M* runtime·extram;
P* runtime·allp[MaxGomaxprocs+1]; P* runtime·allp[MaxGomaxprocs+1];
int8* runtime·goos; int8* runtime·goos;
int32 runtime·ncpu; int32 runtime·ncpu;
static int32 newprocs; int32 runtime·newprocs;
Mutex runtime·allglock; // the following vars are protected by this lock or by stoptheworld Mutex runtime·allglock; // the following vars are protected by this lock or by stoptheworld
G** runtime·allg; G** runtime·allg;
...@@ -763,9 +727,9 @@ runtime·starttheworld(void) ...@@ -763,9 +727,9 @@ runtime·starttheworld(void)
injectglist(gp); injectglist(gp);
add = needaddgcproc(); add = needaddgcproc();
runtime·lock(&runtime·sched.lock); runtime·lock(&runtime·sched.lock);
if(newprocs) { if(runtime·newprocs) {
procresize(newprocs); procresize(runtime·newprocs);
newprocs = 0; runtime·newprocs = 0;
} else } else
procresize(runtime·gomaxprocs); procresize(runtime·gomaxprocs);
runtime·sched.gcwaiting = 0; runtime·sched.gcwaiting = 0;
...@@ -2364,39 +2328,6 @@ runtime·Breakpoint(void) ...@@ -2364,39 +2328,6 @@ runtime·Breakpoint(void)
runtime·breakpoint(); runtime·breakpoint();
} }
// Implementation of runtime.GOMAXPROCS.
// delete when scheduler is even stronger
void
runtime·gomaxprocs_m(void)
{
int32 n, ret;
n = g->m->scalararg[0];
g->m->scalararg[0] = 0;
if(n > MaxGomaxprocs)
n = MaxGomaxprocs;
runtime·lock(&runtime·sched.lock);
ret = runtime·gomaxprocs;
if(n <= 0 || n == ret) {
runtime·unlock(&runtime·sched.lock);
g->m->scalararg[0] = ret;
return;
}
runtime·unlock(&runtime·sched.lock);
runtime·semacquire(&runtime·worldsema, false);
g->m->gcing = 1;
runtime·stoptheworld();
newprocs = n;
g->m->gcing = 0;
runtime·semrelease(&runtime·worldsema);
runtime·starttheworld();
g->m->scalararg[0] = ret;
return;
}
// lockOSThread is called by runtime.LockOSThread and runtime.lockOSThread below // lockOSThread is called by runtime.LockOSThread and runtime.lockOSThread below
// after they modify m->locked. Do not allow preemption during this call, // after they modify m->locked. Do not allow preemption during this call,
// or else the m might be different in this function than in the caller. // or else the m might be different in this function than in the caller.
......
...@@ -60,6 +60,7 @@ typedef struct SudoG SudoG; ...@@ -60,6 +60,7 @@ typedef struct SudoG SudoG;
typedef struct Mutex Mutex; typedef struct Mutex Mutex;
typedef struct M M; typedef struct M M;
typedef struct P P; typedef struct P P;
typedef struct SchedType SchedType;
typedef struct Note Note; typedef struct Note Note;
typedef struct Slice Slice; typedef struct Slice Slice;
typedef struct String String; typedef struct String String;
...@@ -433,6 +434,42 @@ enum { ...@@ -433,6 +434,42 @@ enum {
MaxGomaxprocs = 1<<8, MaxGomaxprocs = 1<<8,
}; };
struct SchedType
{
Mutex lock;
uint64 goidgen;
M* midle; // idle m's waiting for work
int32 nmidle; // number of idle m's waiting for work
int32 nmidlelocked; // number of locked m's waiting for work
int32 mcount; // number of m's that have been created
int32 maxmcount; // maximum number of m's allowed (or die)
P* pidle; // idle P's
uint32 npidle;
uint32 nmspinning;
// Global runnable queue.
G* runqhead;
G* runqtail;
int32 runqsize;
// Global cache of dead G's.
Mutex gflock;
G* gfree;
int32 ngfree;
uint32 gcwaiting; // gc is waiting to run
int32 stopwait;
Note stopnote;
uint32 sysmonwait;
Note sysmonnote;
uint64 lastpoll;
int32 profilehz; // cpu profiling rate
};
// The m->locked word holds two pieces of state counting active calls to LockOSThread/lockOSThread. // The m->locked word holds two pieces of state counting active calls to LockOSThread/lockOSThread.
// The low bit (LockExternal) is a boolean reporting whether any LockOSThread call is active. // The low bit (LockExternal) is a boolean reporting whether any LockOSThread call is active.
// External locks are not recursive; a second lock is silently ignored. // External locks are not recursive; a second lock is silently ignored.
...@@ -716,6 +753,8 @@ extern DebugVars runtime·debug; ...@@ -716,6 +753,8 @@ extern DebugVars runtime·debug;
extern uintptr runtime·maxstacksize; extern uintptr runtime·maxstacksize;
extern Note runtime·signote; extern Note runtime·signote;
extern ForceGCState runtime·forcegc; extern ForceGCState runtime·forcegc;
extern SchedType runtime·sched;
extern int32 runtime·newprocs;
/* /*
* common functions and data * common functions and data
......
...@@ -49,6 +49,11 @@ func asyncsemrelease(addr *uint32) { ...@@ -49,6 +49,11 @@ func asyncsemrelease(addr *uint32) {
// Called from runtime. // Called from runtime.
func semacquire(addr *uint32, profile bool) { func semacquire(addr *uint32, profile bool) {
gp := getg()
if gp != gp.m.curg {
gothrow("semacquire not on the G stack")
}
// Easy case. // Easy case.
if cansemacquire(addr) { if cansemacquire(addr) {
return return
......
...@@ -119,6 +119,8 @@ func deferproc_m() ...@@ -119,6 +119,8 @@ func deferproc_m()
func goexit_m() func goexit_m()
func startpanic_m() func startpanic_m()
func dopanic_m() func dopanic_m()
func readmemstats_m()
func writeheapdump_m()
// memclr clears n bytes starting at ptr. // memclr clears n bytes starting at ptr.
// in memclr_*.s // in memclr_*.s
......
...@@ -80,6 +80,9 @@ TEXT reflect·memmove(SB), NOSPLIT, $0-0 ...@@ -80,6 +80,9 @@ TEXT reflect·memmove(SB), NOSPLIT, $0-0
TEXT runtimedebug·freeOSMemory(SB), NOSPLIT, $0-0 TEXT runtimedebug·freeOSMemory(SB), NOSPLIT, $0-0
JMP runtime·freeOSMemory(SB) JMP runtime·freeOSMemory(SB)
TEXT runtimedebug·WriteHeapDump(SB), NOSPLIT, $0-0
JMP runtime·writeHeapDump(SB)
TEXT net·runtime_pollServerInit(SB),NOSPLIT,$0-0 TEXT net·runtime_pollServerInit(SB),NOSPLIT,$0-0
JMP runtime·netpollServerInit(SB) JMP runtime·netpollServerInit(SB)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment