proc.Next: Further improve handling of highly parallel programs

This patch forces Delve to be more mindful of how it handles many
threads and the goroutine context switching that occurs in such cases.
This commit is contained in:
Derek Parker 2015-08-21 22:13:54 -05:00
parent be2d9c3a84
commit eb0b4e9392
2 changed files with 34 additions and 46 deletions

@ -272,64 +272,53 @@ func (dbp *Process) next() (err error) {
return err return err
} }
var goroutineExiting bool
threadNext := func(thread *Thread) error {
if err = thread.setNextBreakpoints(); err != nil {
switch t := err.(type) {
case ThreadBlockedError, NoReturnAddr: // Noop
case GoroutineExitingError:
goroutineExiting = t.goid == g.Id
default:
return err
}
}
return thread.Continue()
}
// Make sure that we halt the process at the end of this // Make sure that we halt the process at the end of this
// function. We could get into a situation where we have // function. We could get into a situation where we have
// started some, but not all threads. // started some, but not all threads.
defer func() { err = dbp.Halt() }() defer func() { err = dbp.Halt() }()
// Set next breakpoints and then continue each thread. var goroutineExiting bool
if err = dbp.CurrentThread.setNextBreakpoints(); err != nil {
switch t := err.(type) {
case ThreadBlockedError, NoReturnAddr: // Noop
case GoroutineExitingError:
goroutineExiting = t.goid == g.Id
default:
return err
}
}
for _, th := range dbp.Threads { for _, th := range dbp.Threads {
if err := threadNext(th); err != nil { if err := th.Continue(); err != nil {
return err return err
} }
} }
for { for {
if _, err := dbp.trapWait(-1); err != nil { th, err := dbp.trapWait(-1)
if err != nil {
return err return err
} }
// We need to wait for our goroutine to execute, which may not happen tg, err := th.GetG()
// immediately. if err != nil {
// return err
// Loop through all threads, and for each stopped thread }
// see if it is the thread that we care about (thread.g == original.g). // Make sure we're on the same goroutine, unless it has exited.
// If so, we're done. Otherwise set next temp breakpoints for if tg.Id == g.Id || goroutineExiting {
// each thread and continue them. The reason we do this is because // Check to see if the goroutine has switched to another
// if our goroutine is paused, we must execute other threads in order // thread, if so make it the current thread.
// for them to get to a scheduling point, so they can pick up the if dbp.CurrentThread.Id != th.Id {
// goroutine we care about and begin executing it. if err := dbp.SwitchThread(th.Id); err != nil {
for _, thr := range dbp.Threads { return err
if !thr.Stopped() {
continue
}
tg, err := thr.GetG()
if err != nil {
return err
}
// Make sure we're on the same goroutine, unless it has exited.
if tg.Id == g.Id || goroutineExiting {
if dbp.CurrentThread != thr {
dbp.SwitchThread(thr.Id)
} }
return nil
}
if err := threadNext(thr); err != nil {
return err
} }
return nil
}
// This thread was not running our goroutine.
// We continue it since our goroutine could
// potentially be on this threads queue.
if err := th.Continue(); err != nil {
return err
} }
} }
} }

@ -145,13 +145,12 @@ mach_port_wait(mach_port_t port_set) {
return mach_port_wait(port_set); return mach_port_wait(port_set);
} }
} }
break; return thread;
case 72: // Death case 72: // Death
return msg.hdr.msgh_local_port; return msg.hdr.msgh_local_port;
} }
return 0;
return thread;
} }
kern_return_t kern_return_t