Skip to content

Commit

Permalink
proc,proc/native: avoid context switches by executing Continue in ptr…
Browse files Browse the repository at this point in the history
…ace thread

Avoids a lot of context switches while executing Continue by running
inside the ptrace thread.

Benchmark before:

BenchmarkConditionalBreakpoints-4   	       1	3554266510 ns/op

After:

BenchmarkConditionalBreakpoints-4   	       1	1807164143 ns/op

Fixes go-delve#1549
  • Loading branch information
aarzilli committed Jun 2, 2020
1 parent 4be75be commit 462048f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 1 deletion.
5 changes: 5 additions & 0 deletions pkg/proc/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,3 +454,8 @@ func (p *process) FindThread(threadID int) (proc.Thread, bool) {
func (p *process) SetCurrentThread(th proc.Thread) {
p.currentThread = th.(*thread)
}

// ExecOnMagicThread calls fn.
func (p *process) ExecOnMagicThread(fn func()) {
fn()
}
5 changes: 5 additions & 0 deletions pkg/proc/gdbserial/gdbserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,11 @@ func (p *gdbProcess) SetCurrentThread(th proc.Thread) {
p.currentThread = th.(*gdbThread)
}

// ExecOnMagicThread calls fn.
func (p *gdbProcess) ExecOnMagicThread(fn func()) {
fn()
}

const (
interruptSignal = 0x2
breakpointSignal = 0x5
Expand Down
6 changes: 6 additions & 0 deletions pkg/proc/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ type ProcessInternal interface {
Restart(pos string) error
Detach(bool) error
ContinueOnce() (trapthread Thread, stopReason StopReason, err error)

// ExecOnMagicThread executes a function on a thread where calls to
// other methods of Process will execute faster (maybe).
// See the description of this method inside pkg/proc/native for an
// explanation.
ExecOnMagicThread(func())
}

// RecordingManipulation is an interface for manipulating process recordings.
Expand Down
32 changes: 31 additions & 1 deletion pkg/proc/native/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type nativeProcess struct {
// this process.
ctty *os.File

insidePtraceThread bool // currently executing inside the ptrace thread

exited, detached bool
}

Expand Down Expand Up @@ -333,20 +335,48 @@ func (dbp *nativeProcess) handlePtraceFuncs() {
runtime.LockOSThread()

for fn := range dbp.ptraceChan {
dbp.insidePtraceThread = true
fn()
dbp.insidePtraceThread = false
dbp.ptraceDoneChan <- nil
}
close(dbp.ptraceDoneChan)
}

func (dbp *nativeProcess) execPtraceFunc(fn func()) {
if dbp.insidePtraceThread {
fn()
return
}
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}

// ExecOnMagicThread executes fn on the thread that can make ptrace
// calls (or the equivalent of ptrace on windows). If fn does a lot of calls
// to other methods of Process that need to do ptrace calls this will speed
// it up considerably by avoiding a large number of context switches.
// It is very important that fn does not panic, you must defer a recover
// call here.
//
// This function works by calling fn in the ptrace goroutine and then
// disabling the code that sends ptrace calls to that goroutine, executing
// them directly instead.
// It is very important that only the goroutine that calls this function
// will attempt to execute ptrace calls, otherwise the ptrace calls will be
// executed by a goroutine that can't execute them and return the ESRCH / no
// such process (or access denied on windows).
// For Delve this property is ensured by the debugger package which takes
// care to serialize every call to pkg/proc.
// The only risky thing is function call injection which happens inside a
// different goroutine.
func (dbp *nativeProcess) ExecOnMagicThread(fn func()) {
dbp.execPtraceFunc(fn)
}

func (dbp *nativeProcess) postExit() {
dbp.exited = true
close(dbp.ptraceChan)
close(dbp.ptraceDoneChan)
dbp.bi.Close()
if dbp.ctty != nil {
dbp.ctty.Close()
Expand Down
60 changes: 60 additions & 0 deletions pkg/proc/target_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go/ast"
"go/token"
"path/filepath"
"runtime"

"github.com/go-delve/delve/pkg/astutil"
"github.com/go-delve/delve/pkg/dwarf/reader"
Expand Down Expand Up @@ -41,13 +42,72 @@ func (dbp *Target) Next() (err error) {
return dbp.Continue()
}

type internalError struct {
Err interface{}
Stack []internalErrorFrame
}

type internalErrorFrame struct {
Pc uintptr
Func string
File string
Line int
}

func (err *internalError) Error() string {
var out bytes.Buffer
fmt.Fprintf(&out, "Error during continue: %v\n", err.Err)
for _, frame := range err.Stack {
fmt.Fprintf(&out, "%s (%#x)\n\t%s:%d\n", frame.Func, frame.Pc, frame.File, frame.Line)
}
return out.String()
}

func newInternalError(ierr interface{}) *internalError {
r := &internalError{ierr, nil}
for i := 1; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
fname := "<unknown>"
fn := runtime.FuncForPC(pc)
if fn != nil {
fname = fn.Name()
}
r.Stack = append(r.Stack, internalErrorFrame{pc, fname, file, line})
}
return r
}

// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// or is otherwise stopped.
func (dbp *Target) Continue() error {
if _, err := dbp.Valid(); err != nil {
return err
}
if len(dbp.fncallForG) == 0 {
// If there are no function call injections in progress we can use
// ExecOnMagicThread here. Any function call injection introduces
// a goroutine that might call ptrace functions while continueInternal is
// being executed, which won't work (see the comment describing
// InternalExecOnMagicThread inside pkg/proc/native).
var err error
dbp.proc.ExecOnMagicThread(func() {
defer func() {
if ierr := recover(); ierr != nil {
err = newInternalError(ierr)
}
}()
err = dbp.continueInternal()
})
return err
}
return dbp.continueInternal()
}

func (dbp *Target) continueInternal() error {
for _, thread := range dbp.ThreadList() {
thread.Common().returnValues = nil
}
Expand Down

0 comments on commit 462048f

Please sign in to comment.