diff --git a/_fixtures/notify-v2.go b/_fixtures/notify-v2.go new file mode 100644 index 00000000..eb80526b --- /dev/null +++ b/_fixtures/notify-v2.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "sync" +) + +func main() { + http.HandleFunc("/test", func(w http.ResponseWriter, req *http.Request) { + go func() { + // I know this is wrong, it is just to simulate a deadlocked goroutine + fmt.Println("locking...") + mtx := &sync.Mutex{} + mtx.Lock() + mtx.Lock() + fmt.Println("will never print this") + }() + }) + + log.Fatalln(http.ListenAndServe("127.0.0.1:8888", nil)) +} diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 9a8dcc91..35a4b503 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -473,6 +473,13 @@ func goroutines(t *Term, ctx callContext, argstr string) error { return nil } +func selectedGID(state *api.DebuggerState) int { + if state.SelectedGoroutine == nil { + return 0 + } + return state.SelectedGoroutine.ID +} + func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error { args := strings.SplitN(argstr, " ", 2) @@ -505,7 +512,7 @@ func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error { return err } - fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID) + fmt.Printf("Switched from %d to %d (thread %d)\n", selectedGID(oldState), gid, newState.CurrentThread.ID) return nil } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 6864b17c..e22a3893 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -5,12 +5,14 @@ import ( "fmt" "io/ioutil" "net" + "net/http" "os" "path/filepath" "regexp" "strconv" "strings" "testing" + "time" "github.com/derekparker/delve/pkg/proc/test" "github.com/derekparker/delve/service" @@ -38,6 +40,8 @@ type FakeTerminal struct { t testing.TB } +const logCommandOutput = false + func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) { outfh, err := ioutil.TempFile("", "cmdtestout") if err != nil { @@ -54,6 +58,9 @@ func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) { ft.t.Fatalf("could not read temporary output file: %v", err) } outstr = string(outbs) + if logCommandOutput { + ft.t.Logf("command %q -> %q", cmdstr, outstr) + } os.Remove(outfh.Name()) }() err = ft.cmds.Call(cmdstr, ft.Term) @@ -581,3 +588,18 @@ func TestCheckpoints(t *testing.T) { listIsAt(t, term, "restart c1", 16, -1, -1) }) } + +func TestIssue827(t *testing.T) { + // switching goroutines when the current thread isn't running any goroutine + // causes nil pointer dereference. + withTestTerminal("notify-v2", t, func(term *FakeTerminal) { + go func() { + time.Sleep(1 * time.Second) + http.Get("http://127.0.0.1:8888/test") + time.Sleep(1 * time.Second) + term.client.Halt() + }() + term.MustExec("continue") + term.MustExec("goroutine 1") + }) +}