diff --git a/_fixtures/exit.init b/_fixtures/exit.init new file mode 100644 index 00000000..a3abe509 --- /dev/null +++ b/_fixtures/exit.init @@ -0,0 +1 @@ +exit diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 92c56d35..16d9617a 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -500,6 +500,20 @@ func connect(addr string, clientConn net.Conn, conf *config.Config, kind execute } else { client = rpc2.NewClient(addr) } + if client.IsMulticlient() { + state, _ := client.GetStateNonBlocking() + // The error return of GetState will usually be the ErrProcessExited, + // which we don't care about. If there are other errors they will show up + // later, here we are only concerned about stopping a running target so + // that we can initialize our connection. + if state != nil && state.Running { + _, err := client.Halt() + if err != nil { + fmt.Fprintf(os.Stderr, "could not halt: %v", err) + return 1 + } + } + } if client.Recorded() && (kind == executingGeneratedFile || kind == executingGeneratedTest) { // When using the rr backend remove the trace directory if we built the // executable diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 45ecdb2d..a992b7f8 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -256,3 +256,20 @@ func TestGeneratedDoc(t *testing.T) { checkAutogenDoc(t, docFilename, "scripts/gen-usage-docs.go", slurpFile(t, tempDir+"/"+doc.Name())) } } + +func TestExitInInit(t *testing.T) { + dlvbin, tmpdir := getDlvBin(t) + defer os.RemoveAll(tmpdir) + + buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") + exitInit := filepath.Join(protest.FindFixturesDir(), "exit.init") + cmd := exec.Command(dlvbin, "--init", exitInit, "debug") + cmd.Dir = buildtestdir + out, err := cmd.CombinedOutput() + t.Logf("%q %v\n", string(out), err) + // dlv will exit anyway because stdin is not a tty but it will print the + // prompt once if the init file didn't call exit successfully. + if strings.Contains(string(out), "(dlv)") { + t.Fatal("init did not cause dlv to exit") + } +} diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index be1ce43d..9a03e51a 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -1933,6 +1933,9 @@ func (c *Commands) executeFile(t *Term, name string) error { } if err := c.Call(line, t); err != nil { + if _, isExitRequest := err.(ExitRequestError); isExitRequest { + return err + } fmt.Printf("%s:%d: %v\n", name, lineno, err) } } diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index f0e9b1c0..2747c06b 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -64,20 +64,6 @@ type Term struct { // New returns a new Term. func New(client service.Client, conf *config.Config) *Term { - if client != nil && client.IsMulticlient() { - state, _ := client.GetStateNonBlocking() - // The error return of GetState will usually be the ErrProcessExited, - // which we don't care about. If there are other errors they will show up - // later, here we are only concerned about stopping a running target so - // that we can initialize our connection. - if state != nil && state.Running { - _, err := client.Halt() - if err != nil { - fmt.Fprintf(os.Stderr, "could not halt: %v", err) - return nil - } - } - } cmds := DebugCommands(client) if conf != nil && conf.Aliases != nil { cmds.Merge(conf.Aliases) @@ -204,6 +190,9 @@ func (t *Term) Run() (int, error) { if t.InitFile != "" { err := t.cmds.executeFile(t, t.InitFile) if err != nil { + if _, ok := err.(ExitRequestError); ok { + return t.handleExit() + } fmt.Fprintf(os.Stderr, "Error executing init file: %s\n", err) } }