Commit bc31bccc authored by Dmitriy Vyukov's avatar Dmitriy Vyukov

runtime: preempt long-running goroutines

If a goroutine runs for more than 10ms, preempt it.
Update #543.

R=rsc
CC=golang-dev
https://golang.org/cl/10796043
parent 58f12ffd
...@@ -94,7 +94,7 @@ static void wakep(void); ...@@ -94,7 +94,7 @@ static void wakep(void);
static void stoplockedm(void); static void stoplockedm(void);
static void startlockedm(G*); static void startlockedm(G*);
static void sysmon(void); static void sysmon(void);
static uint32 retake(uint32*); static uint32 retake(int64);
static void inclocked(int32); static void inclocked(int32);
static void checkdead(void); static void checkdead(void);
static void exitsyscall0(G*); static void exitsyscall0(G*);
...@@ -2071,7 +2071,6 @@ sysmon(void) ...@@ -2071,7 +2071,6 @@ sysmon(void)
uint32 idle, delay; uint32 idle, delay;
int64 now, lastpoll; int64 now, lastpoll;
G *gp; G *gp;
uint32 ticks[MaxGomaxprocs];
idle = 0; // how many cycles in succession we had not wokeup somebody idle = 0; // how many cycles in succession we had not wokeup somebody
delay = 0; delay = 0;
...@@ -2103,19 +2102,29 @@ sysmon(void) ...@@ -2103,19 +2102,29 @@ sysmon(void)
injectglist(gp); injectglist(gp);
} }
// retake P's blocked in syscalls // retake P's blocked in syscalls
if(retake(ticks)) // and preempt long running G's
if(retake(now))
idle = 0; idle = 0;
else else
idle++; idle++;
} }
} }
typedef struct Pdesc Pdesc;
struct Pdesc
{
uint32 tick;
int64 when;
};
static Pdesc pdesc[MaxGomaxprocs];
static uint32 static uint32
retake(uint32 *ticks) retake(int64 now)
{ {
uint32 i, s, n; uint32 i, s, n;
int64 t; int64 t;
P *p; P *p;
Pdesc *pd;
n = 0; n = 0;
for(i = 0; i < runtime·gomaxprocs; i++) { for(i = 0; i < runtime·gomaxprocs; i++) {
...@@ -2123,24 +2132,34 @@ retake(uint32 *ticks) ...@@ -2123,24 +2132,34 @@ retake(uint32 *ticks)
if(p==nil) if(p==nil)
continue; continue;
t = p->tick; t = p->tick;
if(ticks[i] != t) { pd = &pdesc[i];
ticks[i] = t; if(pd->tick != t) {
pd->tick = t;
pd->when = now;
continue; continue;
} }
s = p->status; s = p->status;
if(s != Psyscall) if(s == Psyscall) {
continue; // Retake P from syscall if it's there for more than 1 sysmon tick (20us).
if(p->runqhead == p->runqtail && runtime·atomicload(&runtime·sched.nmspinning) + runtime·atomicload(&runtime·sched.npidle) > 0) // TODO: fast atomic // But only if there is other work to do.
continue; if(p->runqhead == p->runqtail &&
// Need to increment number of locked M's before the CAS. runtime·atomicload(&runtime·sched.nmspinning) + runtime·atomicload(&runtime·sched.npidle) > 0)
// Otherwise the M from which we retake can exit the syscall, continue;
// increment nmidle and report deadlock. // Need to increment number of locked M's before the CAS.
inclocked(-1); // Otherwise the M from which we retake can exit the syscall,
if(runtime·cas(&p->status, s, Pidle)) { // increment nmidle and report deadlock.
n++; inclocked(-1);
handoffp(p); if(runtime·cas(&p->status, s, Pidle)) {
n++;
handoffp(p);
}
inclocked(1);
} else if(s == Prunning) {
// Preempt G if it's running for more than 10ms.
if(pd->when + 10*1000*1000 > now)
continue;
preemptone(p);
} }
inclocked(1);
} }
return n; return n;
} }
......
...@@ -192,6 +192,27 @@ var preempt = func() int { ...@@ -192,6 +192,27 @@ var preempt = func() int {
return sum return sum
} }
func TestPreemption(t *testing.T) {
t.Skip("preemption is disabled")
// Test that goroutines are preempted at function calls.
const N = 5
c := make(chan bool)
var x uint32
for g := 0; g < 2; g++ {
go func(g int) {
for i := 0; i < N; i++ {
for atomic.LoadUint32(&x) != uint32(g) {
preempt()
}
atomic.StoreUint32(&x, uint32(1-g))
}
c <- true
}(g)
}
<-c
<-c
}
func TestPreemptionGC(t *testing.T) { func TestPreemptionGC(t *testing.T) {
t.Skip("preemption is disabled") t.Skip("preemption is disabled")
// Test that pending GC preempts running goroutines. // Test that pending GC preempts running goroutines.
......
...@@ -244,11 +244,11 @@ runtime·newstack(void) ...@@ -244,11 +244,11 @@ runtime·newstack(void)
if(gp->stackguard0 == (uintptr)StackPreempt) { if(gp->stackguard0 == (uintptr)StackPreempt) {
if(gp == m->g0) if(gp == m->g0)
runtime·throw("runtime: preempt g0"); runtime·throw("runtime: preempt g0");
if(oldstatus == Grunning && (m->p == nil || m->p->status != Prunning)) if(oldstatus == Grunning && m->p == nil)
runtime·throw("runtime: g is running but p is not"); runtime·throw("runtime: g is running but p is not");
// Be conservative about where we preempt. // Be conservative about where we preempt.
// We are interested in preempting user Go code, not runtime code. // We are interested in preempting user Go code, not runtime code.
if(oldstatus != Grunning || m->locks || m->mallocing || m->gcing) { if(oldstatus != Grunning || m->locks || m->mallocing || m->gcing || m->p->status != Prunning) {
// Let the goroutine keep running for now. // Let the goroutine keep running for now.
// gp->preempt is set, so it will be preempted next time. // gp->preempt is set, so it will be preempted next time.
gp->stackguard0 = gp->stackguard; gp->stackguard0 = gp->stackguard;
......
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