Repository: go-delve/delve
Pull request #2671 (2021-08-24 00:19:13)

service/dap: set non-build launch/attach errors as visible
In general, we deal with 2 types of launch/attach errors:
1. Errors printed to Debug Console via OutputEvent that DO NOT need visible reporting from client (`showUser=false`)
2. Errors NOT printed to Debug Console that DO need visible reporting from client (`showUser=true`).

However, vscode hasn't been honoring the protocol: it triggered a modal alert for launch/attach errors regardless of the `showUser` value and showed a duplicate visible error with `showUser=true`. Therefore, we have been setting `showUser=false` for all launch/attach errors, which is omitted during json conversion as per types generated via [go-dap](https://github.com/google/go-dap).

microsoft/vscode#128484 fixes this behavior. New editor version has been released and now has:
* no `showUser` => visible error (for backward compatibility)
* `showUser=false` => no visible error
* `showUser=true` => visible error

After this change we will have:
* non-build errors: `showUser=true` - 1 pop-ups with new vscode version, 2 pop-ups with old vscode version
* build errors (details in Console): `showUser=false` - 1 pop-up with any vscode version

After https://github.com/google/go-dap/pull/60 is pulled in, we will have:
* build errors (details in Console): `showUser=false` - 1 pop-up with old vscode version, no pop-up with new vscode version (our goal)

Updates golang/vscode-go#1375


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f040abb..4e591889 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,31 @@
 All notable changes to this project will be documented in this file.
 This project adheres to Semantic Versioning.
 
+## [1.7.2] 2021-09-21
+
+### Added
+
+* Documentation: Add notes on porting Delve to other architectures (@aarzilli)
+* Add internal checks to ensure we're synched with Go runtime internals (@aarzilli)
+* eBPF backend can parse goroutine info (@derekparker)
+* Add support for debuginfo-find (@derekparker)
+* Add MAKE arguments for GOOS / GOARCH (@cmol)
+
+### Fixed
+
+* Correctly check for 1.17 and regabi (@aarzilli)
+* Print config output strings quouted (@aarzilli, @krobelus)
+* Update check for system goroutines (@suzmue)
+* DAP: Halt before detach in Stop (@polinasok)
+* DAP: Do not send halt request if debuggee is not running (@suzmue)
+
+### Changed
+
+* Include selected goroutine in threads request (@suzmue)
+* Remove individual OS install instructions (@gabriel-vasile)
+* DAP: Show decimal / hex values for uint (@suzmue)
+* Avoid bright colors in default syntax highlighting (@krobelus)
+
 ## [1.7.1] 2021-08-18
 
 ### Added
diff --git a/Documentation/installation/README.md b/Documentation/installation/README.md
index a57fb790..cbbf1f2c 100644
--- a/Documentation/installation/README.md
+++ b/Documentation/installation/README.md
@@ -1,4 +1,5 @@
 # Installation
+The following instructions are known to work on Linux, macOS, Windows and FreeBSD.
 
 Clone the git repository and build:
 
@@ -14,7 +15,7 @@ On Go version 1.16 or later, this command will also work:
 $ go install github.com/go-delve/delve/cmd/dlv@latest
 ```
 
-See `go help install` for details on where the `dlv` executable is saved. 
+See `go help install` for details on where the `dlv` executable is saved.
 
 If during the install step you receive an error similar to this:
 
diff --git a/README.md b/README.md
index 73fb876b..a607a13a 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,6 @@ The GitHub issue tracker is for **bugs** only. Please use the [developer mailing
 ### About Delve
 
 - [Installation](Documentation/installation)
-  - [Linux](Documentation/installation/linux/install.md)
-  - [macOS](Documentation/installation/osx/install.md)
-  - [Windows](Documentation/installation/windows/install.md)
-  - [FreeBSD](Documentation/installation/freebsd/install.md)
 - [Getting Started](Documentation/cli/getting_started.md)
 - [Documentation](Documentation)
   - [Command line options](Documentation/usage/dlv.md)
diff --git a/_scripts/make.go b/_scripts/make.go
index 9e3ef3ef..5ad9bbeb 100644
--- a/_scripts/make.go
+++ b/_scripts/make.go
@@ -22,6 +22,8 @@ var NOTimeout bool
 var TestIncludePIE bool
 var TestSet, TestRegex, TestBackend, TestBuildMode string
 var Tags *[]string
+var Architecture string
+var OS string
 
 func NewMakeCommands() *cobra.Command {
 	RootCommand := &cobra.Command{
@@ -48,13 +50,26 @@ func NewMakeCommands() *cobra.Command {
 				}
 				tagFlag += strings.Join(*Tags, ",")
 			}
-			execute("go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath)
+			envflags := []string{}
+			if len(Architecture) > 0 {
+				envflags = append(envflags, "GOARCH="+Architecture)
+			}
+			if len(OS) > 0 {
+				envflags = append(envflags, "GOOS="+OS)
+			}
+			if len(envflags) > 0 {
+				executeEnv(envflags, "go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath)
+			} else {
+				execute("go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath)
+			}
 			if runtime.GOOS == "darwin" && os.Getenv("CERT") != "" && canMacnative() {
 				codesign("./dlv")
 			}
 		},
 	}
 	Tags = buildCmd.PersistentFlags().StringArray("tags", []string{}, "Build tags")
+	buildCmd.PersistentFlags().StringVar(&Architecture, "GOARCH", "", "Architecture to build for")
+	buildCmd.PersistentFlags().StringVar(&OS, "GOOS", "", "OS to build for")
 	RootCommand.AddCommand(buildCmd)
 
 	RootCommand.AddCommand(&cobra.Command{
@@ -165,11 +180,14 @@ func strflatten(v []interface{}) []string {
 	return r
 }
 
-func executeq(cmd string, args ...interface{}) {
+func executeq(env []string, cmd string, args ...interface{}) {
 	x := exec.Command(cmd, strflatten(args)...)
 	x.Stdout = os.Stdout
 	x.Stderr = os.Stderr
 	x.Env = os.Environ()
+	for _, e := range env {
+		x.Env = append(x.Env, e)
+	}
 	err := x.Run()
 	if x.ProcessState != nil && !x.ProcessState.Success() {
 		os.Exit(1)
@@ -181,7 +199,14 @@ func executeq(cmd string, args ...interface{}) {
 
 func execute(cmd string, args ...interface{}) {
 	fmt.Printf("%s %s\n", cmd, strings.Join(quotemaybe(strflatten(args)), " "))
-	executeq(cmd, args...)
+	env := []string{}
+	executeq(env, cmd, args...)
+}
+
+func executeEnv(env []string, cmd string, args ...interface{}) {
+	fmt.Printf("%s %s %s\n", strings.Join(env, " "),
+		cmd, strings.Join(quotemaybe(strflatten(args)), " "))
+	executeq(env, cmd, args...)
 }
 
 func quotemaybe(args []string) []string {
@@ -313,7 +338,8 @@ func testCmd(cmd *cobra.Command, args []string) {
 
 		fmt.Println("\nTesting")
 		os.Setenv("PROCTEST", "lldb")
-		executeq("sudo", "-E", "go", "test", testFlags(), allPackages())
+		env := []string{}
+		executeq(env, "sudo", "-E", "go", "test", testFlags(), allPackages())
 		return
 	}
 
@@ -415,7 +441,8 @@ func testCmdIntl(testSet, testRegex, testBackend, testBuildMode string) {
 	}
 
 	if len(testPackages) > 3 {
-		executeq("go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag)
+		env := []string{}
+		executeq(env, "go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag)
 	} else if testRegex != "" {
 		execute("go", "test", testFlags(), buildFlags(), testPackages, "-run="+testRegex, backendFlag, buildModeFlag)
 	} else {
diff --git a/pkg/config/config.go b/pkg/config/config.go
index f1a24e46..83ef688d 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -53,9 +53,8 @@ type Config struct {
 	// expression for its argument.
 	ShowLocationExpr bool `yaml:"show-location-expr"`
 
-	// Source list line-number color (3/4 bit color codes as defined
-	// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors),
-	// or a string containing a terminal escape sequence.
+	// Source list line-number color, as a terminal escape sequence.
+	// For historic reasons, this can also be an integer color code.
 	SourceListLineColor interface{} `yaml:"source-list-line-color"`
 
 	// Source list arrow color, as a terminal escape sequence.
@@ -228,11 +227,10 @@ func writeDefaultConfig(f *os.File) error {
 # This is the default configuration file. Available options are provided, but disabled.
 # Delete the leading hash mark to enable an item.
 
-# Uncomment the following line and set your preferred ANSI foreground color
-# for source line numbers in the (list) command (if unset, default is 34,
-# dark blue) See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
-# Alternatively a string containing an escape sequence can also be used.
-# source-list-line-color: 34
+# Uncomment the following line and set your preferred ANSI color for source
+# line numbers in the (list) command. The default is 34 (dark blue). See
+# https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
+# source-list-line-color: "\x1b[34m"
 
 # Uncomment the following lines to change the colors used by syntax highlighting.
 # source-list-keyword-color: "\x1b[0m"
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index cb96659e..2bb0a069 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -538,7 +538,7 @@ func (g *G) System(tgt *Target) bool {
 		return false
 	}
 	switch loc.Fn.Name {
-	case "runtime.main", "runtime.handleAsyncEvent", "runtime.runfinq":
+	case "runtime.main", "runtime.handleAsyncEvent":
 		return false
 	}
 	return strings.HasPrefix(loc.Fn.Name, "runtime.")
diff --git a/pkg/terminal/config.go b/pkg/terminal/config.go
index 3bcda96e..2d1ef51e 100644
--- a/pkg/terminal/config.go
+++ b/pkg/terminal/config.go
@@ -82,6 +82,13 @@ func configureList(t *Term) error {
 		}
 
 		switch field.Kind() {
+		case reflect.Interface:
+			switch field := field.Interface().(type) {
+			case string:
+				fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
+			default:
+				fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
+			}
 		case reflect.Ptr:
 			if !field.IsNil() {
 				fmt.Fprintf(w, "%s\t%v\n", fieldName, field.Elem())
diff --git a/pkg/version/version.go b/pkg/version/version.go
index 3bde2354..5b6f24e7 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -17,7 +17,7 @@ type Version struct {
 var (
 	// DelveVersion is the current version of Delve.
 	DelveVersion = Version{
-		Major: "1", Minor: "7", Patch: "1", Metadata: "",
+		Major: "1", Minor: "7", Patch: "2", Metadata: "",
 		Build: "$Id$",
 	}
 )
diff --git a/service/dap/server.go b/service/dap/server.go
index 3e90c430..53caba4b 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -25,6 +25,7 @@ import (
 	"regexp"
 	"runtime"
 	"runtime/debug"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -193,7 +194,11 @@ const (
 	// what is presented. A common use case of a call injection is to
 	// stringify complex data conveniently.
 	maxStringLenInCallRetVars = 1 << 10 // 1024
+)
+
+var (
 	// Max number of goroutines that we will return.
+	// This is a var for testing
 	maxGoroutines = 1 << 10
 )
 
@@ -745,7 +750,7 @@ func cleanExeName(name string) string {
 func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 	var args = defaultLaunchConfig // narrow copy for initializing non-zero default values
 	if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
-		s.sendErrorResponse(request.Request,
+		s.sendShowUserErrorResponse(request.Request,
 			FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - %v", err))
 		return
 	}
@@ -755,14 +760,14 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		mode = "debug"
 	}
 	if !isValidLaunchMode(mode) {
-		s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", mode))
+		s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
+			fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", mode))
 		return
 	}
 	// TODO(polina): Respond with an error if debug session is in progress?
 	program := args.Program
 	if program == "" && mode != "replay" { // Only fail on modes requiring a program
-		s.sendErrorResponse(request.Request,
-			FailedToLaunch, "Failed to launch",
+		s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
 			"The program attribute is missing in debug configuration.")
 		return
 	}
@@ -777,8 +782,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		traceDirPath := args.TraceDirPath
 		// Validate trace directory
 		if traceDirPath == "" {
-			s.sendErrorResponse(request.Request,
-				FailedToLaunch, "Failed to launch",
+			s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
 				"The 'traceDirPath' attribute is missing in debug configuration.")
 			return
 		}
@@ -792,8 +796,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		coreFilePath := args.CoreFilePath
 		// Validate core dump path
 		if coreFilePath == "" {
-			s.sendErrorResponse(request.Request,
-				FailedToLaunch, "Failed to launch",
+			s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
 				"The 'coreFilePath' attribute is missing in debug configuration.")
 			return
 		}
@@ -814,10 +817,9 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		output = cleanExeName(output)
 		debugbinary, err := filepath.Abs(output)
 		if err != nil {
-			s.sendInternalErrorResponse(request.Seq, err.Error())
+			s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 			return
 		}
-
 		buildFlags := args.BuildFlags
 
 		var cmd string
@@ -837,8 +839,9 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 					Output:   fmt.Sprintf("Build Error: %s\n%s (%s)\n", cmd, strings.TrimSpace(string(out)), err.Error()),
 					Category: "stderr",
 				}})
-			s.sendErrorResponse(request.Request,
-				FailedToLaunch, "Failed to launch",
+			// Users are used to checking the Debug Console for build errors.
+			// No need to bother them with a visible pop-up.
+			s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
 				"Build error: Check the debug console for details.")
 			return
 		}
@@ -849,7 +852,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 	}
 
 	if err := s.setLaunchAttachArgs(args.LaunchAttachCommonConfig); err != nil {
-		s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
+		s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 		return
 	}
 
@@ -865,7 +868,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		cmd, err := s.newNoDebugProcess(program, args.Args, s.config.Debugger.WorkingDir)
 		s.mu.Unlock()
 		if err != nil {
-			s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
+			s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 			return
 		}
 		// Skip 'initialized' event, which will prevent the client from sending
@@ -898,7 +901,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		s.debugger, err = debugger.New(&s.config.Debugger, s.config.ProcessArgs)
 	}()
 	if err != nil {
-		s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
+		s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 		return
 	}
 	// Enable StepBack controls on supported backends
@@ -1358,8 +1361,8 @@ func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) {
 	if s.debugger != nil {
 		gs, next, err = s.debugger.Goroutines(0, maxGoroutines)
 	}
-	threads := make([]dap.Thread, len(gs))
 
+	var threads []dap.Thread
 	if err != nil {
 		switch err.(type) {
 		case proc.ErrProcessExited:
@@ -1375,16 +1378,41 @@ func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) {
 				}})
 		}
 		threads = []dap.Thread{{Id: 1, Name: "Dummy"}}
-	} else if len(threads) == 0 {
+	} else if len(gs) == 0 {
 		threads = []dap.Thread{{Id: 1, Name: "Dummy"}}
 	} else {
-		if next >= 0 {
-			s.logToConsole(fmt.Sprintf("Too many goroutines, only loaded %d", len(gs)))
-		}
 		state, err := s.debugger.State( /*nowait*/ true)
 		if err != nil {
 			s.log.Debug("Unable to get debugger state: ", err)
 		}
+
+		if next >= 0 {
+			s.logToConsole(fmt.Sprintf("Too many goroutines, only loaded %d", len(gs)))
+
+			// Make sure the selected goroutine is included in the list of threads
+			// to return.
+			if state != nil && state.SelectedGoroutine != nil {
+				var selectedFound bool
+				for _, g := range gs {
+					if g.ID == state.SelectedGoroutine.ID {
+						selectedFound = true
+						break
+					}
+				}
+				if !selectedFound {
+					g, err := s.debugger.FindGoroutine(state.SelectedGoroutine.ID)
+					if err != nil {
+						s.log.Debug("Error getting selected goroutine: ", err)
+					} else {
+						// TODO(suzmue): Consider putting the selected goroutine at the top.
+						// To be consistent we may want to do this for all threads requests.
+						gs = append(gs, g)
+					}
+				}
+			}
+		}
+
+		threads = make([]dap.Thread, len(gs))
 		s.debugger.LockTarget()
 		defer s.debugger.UnlockTarget()
 
@@ -1417,7 +1445,7 @@ func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) {
 func (s *Server) onAttachRequest(request *dap.AttachRequest) {
 	var args AttachConfig = defaultAttachConfig // narrow copy for initializing non-zero default values
 	if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
-		s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", fmt.Sprintf("invalid debug configuration - %v", err))
+		s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", fmt.Sprintf("invalid debug configuration - %v", err))
 		return
 	}
 
@@ -1427,20 +1455,19 @@ func (s *Server) onAttachRequest(request *dap.AttachRequest) {
 	}
 
 	if !isValidAttachMode(mode) {
-		s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach",
+		s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach",
 			fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", args.Mode))
 		return
 	}
 	if mode == "local" {
 		if args.ProcessID == 0 {
-			s.sendErrorResponse(request.Request,
-				FailedToAttach, "Failed to attach",
+			s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach",
 				"The 'processId' attribute is missing in debug configuration")
 			return
 		}
 		s.config.Debugger.AttachPid = args.ProcessID
 		if err := s.setLaunchAttachArgs(args.LaunchAttachCommonConfig); err != nil {
-			s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", err.Error())
+			s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", err.Error())
 			return
 		}
 		if backend := args.Backend; backend != "" {
@@ -1455,7 +1482,7 @@ func (s *Server) onAttachRequest(request *dap.AttachRequest) {
 			s.debugger, err = debugger.New(&s.config.Debugger, nil)
 		}()
 		if err != nil {
-			s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", err.Error())
+			s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", err.Error())
 			return
 		}
 	}
@@ -1539,10 +1566,12 @@ func (s *Server) doStepCommand(command string, threadId int, asyncSetupDone chan
 // onPauseRequest handles 'pause' request.
 // This is a mandatory request to support.
 func (s *Server) onPauseRequest(request *dap.PauseRequest) {
-	_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
-	if err != nil {
-		s.sendErrorResponse(request.Request, UnableToHalt, "Unable to halt execution", err.Error())
-		return
+	if s.debugger.IsRunning() {
+		_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
+		if err != nil {
+			s.sendErrorResponse(request.Request, UnableToHalt, "Unable to halt execution", err.Error())
+			return
+		}
 	}
 	s.send(&dap.PauseResponse{Response: *newResponse(request.Request)})
 	// No need to send any event here.
@@ -2078,6 +2107,9 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 	}
 
 	switch v.Kind {
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		n, _ := strconv.ParseUint(api.ConvertVar(v).Value, 10, 64)
+		value = fmt.Sprintf("%s = %#x", value, n)
 	case reflect.UnsafePointer:
 		// Skip child reference
 	case reflect.Ptr:
@@ -2688,11 +2720,16 @@ func (s *Server) sendErrorResponseWithOpts(request dap.Request, id int, summary,
 	s.send(er)
 }
 
-// sendErrorResponse sends an error response with default visibility settings.
+// sendErrorResponse sends an error response with showUser disabled (default).
 func (s *Server) sendErrorResponse(request dap.Request, id int, summary, details string) {
 	s.sendErrorResponseWithOpts(request, id, summary, details, false /*showUser*/)
 }
 
+// sendShowUserErrorResponse sends an error response with showUser enabled.
+func (s *Server) sendShowUserErrorResponse(request dap.Request, id int, summary, details string) {
+	s.sendErrorResponseWithOpts(request, id, summary, details, true /*showUser*/)
+}
+
 // sendInternalErrorResponse sends an "internal error" response back to the client.
 // We only take a seq here because we don't want to make assumptions about the
 // kind of message received by the server that this error is a reply to.
diff --git a/service/rpccommon/server.go b/service/rpccommon/server.go
index e31bd4f1..ff96cfe4 100644
--- a/service/rpccommon/server.go
+++ b/service/rpccommon/server.go
@@ -90,10 +90,14 @@ func NewServer(config *service.Config) *ServerImpl {
 
 // Stop stops the JSON-RPC server.
 func (s *ServerImpl) Stop() error {
+	s.log.Debug("stopping")
 	close(s.stopChan)
 	if s.config.AcceptMulti {
 		s.listener.Close()
 	}
+	if s.debugger.IsRunning() {
+		s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
+	}
 	kill := s.config.Debugger.AttachPid == 0
 	return s.debugger.Detach(kill)
 }


Test patch:
diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go
index fcc81e31..64778746 100644
--- a/cmd/dlv/dlv_test.go
+++ b/cmd/dlv/dlv_test.go
@@ -254,7 +254,7 @@ func TestContinue(t *testing.T) {
 	cmd := exec.Command(dlvbin, "debug", "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr)
 	cmd.Dir = buildtestdir
 	stdout, err := cmd.StdoutPipe()
-	assertNoError(err, t, "stderr pipe")
+	assertNoError(err, t, "stdout pipe")
 	defer stdout.Close()
 
 	assertNoError(cmd.Start(), t, "start headless instance")
@@ -299,7 +299,7 @@ func TestChildProcessExitWhenNoDebugInfo(t *testing.T) {
 	// search the running process named fix.Name
 	cmd := exec.Command("ps", "-aux")
 	stdout, err := cmd.StdoutPipe()
-	assertNoError(err, t, "stderr pipe")
+	assertNoError(err, t, "stdout pipe")
 	defer stdout.Close()
 
 	assertNoError(cmd.Start(), t, "start `ps -aux`")
@@ -330,7 +330,7 @@ func TestRedirect(t *testing.T) {
 	catfixture := filepath.Join(protest.FindFixturesDir(), "cat.go")
 	cmd := exec.Command(dlvbin, "debug", "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr, "-r", catfixture, catfixture)
 	stdout, err := cmd.StdoutPipe()
-	assertNoError(err, t, "stderr pipe")
+	assertNoError(err, t, "stdout pipe")
 	defer stdout.Close()
 
 	assertNoError(cmd.Start(), t, "start headless instance")
diff --git a/pkg/proc/proc_unix_test.go b/pkg/proc/proc_unix_test.go
index 5d7e2f85..74a39cc6 100644
--- a/pkg/proc/proc_unix_test.go
+++ b/pkg/proc/proc_unix_test.go
@@ -1,3 +1,4 @@
+//go:build linux || darwin
 // +build linux darwin
 
 package proc_test
@@ -36,7 +37,6 @@ func TestIssue419(t *testing.T) {
 
 	// SIGINT directed at the inferior should be passed along not swallowed by delve
 	withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) {
-		defer close(errChan)
 		setFunctionBreakpoint(p, t, "main.main")
 		assertNoError(p.Continue(), t, "Continue()")
 		resumeChan := make(chan struct{}, 1)
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 080768a9..94c675ff 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -54,15 +54,21 @@ func runTestBuildFlags(t *testing.T, name string, test func(c *daptest.Client, f
 	fixture := protest.BuildFixture(name, buildFlags)
 
 	// Start the DAP server.
-	client := startDapServer(t)
-	// client.Close will close the client connectinon, which will cause a connection error
-	// on the server side and signal disconnect to unblock Stop() above.
+	serverStopped := make(chan struct{})
+	client := startDapServerWithClient(t, serverStopped)
 	defer client.Close()
 
 	test(client, fixture)
+	<-serverStopped
 }
 
-func startDapServer(t *testing.T) *daptest.Client {
+func startDapServerWithClient(t *testing.T, serverStopped chan struct{}) *daptest.Client {
+	listener, _ := startDapServer(t, serverStopped)
+	client := daptest.NewClient(listener.Addr().String())
+	return client
+}
+
+func startDapServer(t *testing.T, serverStopped chan struct{}) (listener net.Listener, forceStop chan struct{}) {
 	// Start the DAP server.
 	listener, err := net.Listen("tcp", ":0")
 	if err != nil {
@@ -80,13 +86,72 @@ func startDapServer(t *testing.T) *daptest.Client {
 	// Run a goroutine that stops the server when disconnectChan is signaled.
 	// This helps us test that certain events cause the server to stop as
 	// expected.
+	forceStop = make(chan struct{})
 	go func() {
-		<-disconnectChan
+		defer func() {
+			if serverStopped != nil {
+				close(serverStopped)
+			}
+		}()
+		select {
+		case <-disconnectChan: // Stop triggered internally
+		case <-forceStop: // Stop triggered externally
+		}
 		server.Stop()
 	}()
 
+	return listener, forceStop
+}
+
+func TestForceStopNoClient(t *testing.T) {
+	serverStopped := make(chan struct{})
+	_, forceStop := startDapServer(t, serverStopped)
+	close(forceStop)
+	<-serverStopped
+}
+
+func TestForceStopNoTarget(t *testing.T) {
+	serverStopped := make(chan struct{})
+	listener, forceStop := startDapServer(t, serverStopped)
 	client := daptest.NewClient(listener.Addr().String())
-	return client
+	defer client.Close() // does not trigger Stop
+
+	client.InitializeRequest()
+	client.ExpectInitializeResponseAndCapabilities(t)
+	close(forceStop)
+	<-serverStopped
+}
+
+func TestForceStopWithTarget(t *testing.T) {
+	serverStopped := make(chan struct{})
+	listener, forceStop := startDapServer(t, serverStopped)
+	client := daptest.NewClient(listener.Addr().String())
+	defer client.Close() // does not trigger Stop
+
+	client.InitializeRequest()
+	client.ExpectInitializeResponseAndCapabilities(t)
+	fixture := protest.BuildFixture("increment", protest.AllNonOptimized)
+	client.LaunchRequest("exec", fixture.Path, stopOnEntry)
+	client.ExpectInitializedEvent(t)
+	client.ExpectLaunchResponse(t)
+	close(forceStop)
+	<-serverStopped
+}
+
+func TestForceStopWhileStopping(t *testing.T) {
+	serverStopped := make(chan struct{})
+	listener, forceStop := startDapServer(t, serverStopped)
+	client := daptest.NewClient(listener.Addr().String())
+
+	client.InitializeRequest()
+	client.ExpectInitializeResponseAndCapabilities(t)
+	fixture := protest.BuildFixture("increment", protest.AllNonOptimized)
+	client.LaunchRequest("exec", fixture.Path, stopOnEntry)
+	client.ExpectInitializedEvent(t)
+	client.Close() // depending on timing may trigger Stop()
+	time.Sleep(time.Microsecond)
+	close(forceStop) // depending on timing may trigger Stop()
+	<-serverStopped
 }
 
 // TestLaunchStopOnEntry emulates the message exchange that can be observed with
@@ -537,8 +602,8 @@ func TestPreSetBreakpoint(t *testing.T) {
 		client.VariablesRequest(1000) // Arguments
 		args := client.ExpectVariablesResponse(t)
 		checkChildren(t, args, "Arguments", 2)
-		checkVarExact(t, args, 0, "y", "y", "0", "uint", noChildren)
-		checkVarExact(t, args, 1, "~r1", "", "0", "uint", noChildren)
+		checkVarExact(t, args, 0, "y", "y", "0 = 0x0", "uint", noChildren)
+		checkVarExact(t, args, 1, "~r1", "", "0 = 0x0", "uint", noChildren)
 
 		client.VariablesRequest(1001) // Locals
 		locals := client.ExpectVariablesResponse(t)
@@ -922,6 +987,63 @@ func TestStackTraceRequest(t *testing.T) {
 	})
 }
 
+func TestSelectedThreadsRequest(t *testing.T) {
+	runTest(t, "goroutinestackprog", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{20},
+			[]onBreakpoint{{
+				execute: func() {
+					checkStop(t, client, 1, "main.main", 20)
+
+					defaultMaxGoroutines := maxGoroutines
+					defer func() { maxGoroutines = defaultMaxGoroutines }()
+
+					maxGoroutines = 1
+					client.SetBreakpointsRequest(fixture.Source, []int{8})
+					client.ExpectSetBreakpointsResponse(t)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+
+					se := client.ExpectStoppedEvent(t)
+					if se.Body.Reason != "breakpoint" || se.Body.ThreadId == 1 {
+						t.Errorf("got %#v, want Reason=%q, ThreadId!=1", se, "breakpoint")
+					}
+
+					client.ThreadsRequest()
+					oe := client.ExpectOutputEvent(t)
+					if !strings.HasPrefix(oe.Body.Output, "Too many goroutines") {
+						t.Errorf("got %#v, expected Output=\"Too many goroutines...\"\n", oe)
+
+					}
+					tr := client.ExpectThreadsResponse(t)
+
+					if len(tr.Body.Threads) != 2 {
+						t.Errorf("got %d threads, expected 2\n", len(tr.Body.Threads))
+					}
+
+					var selectedFound bool
+					for _, thread := range tr.Body.Threads {
+						if thread.Id == se.Body.ThreadId {
+							selectedFound = true
+							break
+						}
+					}
+					if !selectedFound {
+						t.Errorf("got %#v, want ThreadId=%d\n", tr.Body.Threads, se.Body.ThreadId)
+					}
+				},
+				disconnect: true,
+			}})
+
+	})
+}
+
 // TestScopesAndVariablesRequests executes to a breakpoint and tests different
 // configurations of 'scopes' and 'variables' requests.
 func TestScopesAndVariablesRequests(t *testing.T) {
@@ -999,15 +1121,15 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 					// reflect.Kind == Int64 - see testvariables2
 					// reflect.Kind == Uint
 					// reflect.Kind == Uint8
-					checkVarExact(t, locals, -1, "u8", "u8", "255", "uint8", noChildren)
+					checkVarExact(t, locals, -1, "u8", "u8", "255 = 0xff", "uint8", noChildren)
 					// reflect.Kind == Uint16
-					checkVarExact(t, locals, -1, "u16", "u16", "65535", "uint16", noChildren)
+					checkVarExact(t, locals, -1, "u16", "u16", "65535 = 0xffff", "uint16", noChildren)
 					// reflect.Kind == Uint32
-					checkVarExact(t, locals, -1, "u32", "u32", "4294967295", "uint32", noChildren)
+					checkVarExact(t, locals, -1, "u32", "u32", "4294967295 = 0xffffffff", "uint32", noChildren)
 					// reflect.Kind == Uint64
-					checkVarExact(t, locals, -1, "u64", "u64", "18446744073709551615", "uint64", noChildren)
+					checkVarExact(t, locals, -1, "u64", "u64", "18446744073709551615 = 0xffffffffffffffff", "uint64", noChildren)
 					// reflect.Kind == Uintptr
-					checkVarExact(t, locals, -1, "up", "up", "5", "uintptr", noChildren)
+					checkVarExact(t, locals, -1, "up", "up", "5 = 0x5", "uintptr", noChildren)
 					// reflect.Kind == Float32
 					checkVarExact(t, locals, -1, "f32", "f32", "1.2", "float32", noChildren)
 					// reflect.Kind == Float64
@@ -1280,7 +1402,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 						client.VariablesRequest(ref)
 						ch1 := client.ExpectVariablesResponse(t)
 						checkChildren(t, ch1, "ch1", 11)
-						checkVarExact(t, ch1, 0, "qcount", "ch1.qcount", "4", "uint", noChildren)
+						checkVarExact(t, ch1, 0, "qcount", "ch1.qcount", "4 = 0x4", "uint", noChildren)
 						checkVarRegex(t, ch1, 10, "lock", "ch1.lock", `runtime\.mutex {.*key: 0.*}`, `runtime\.mutex`, hasChildren)
 						validateEvaluateName(t, client, ch1, 0)
 						validateEvaluateName(t, client, ch1, 10)
@@ -1905,41 +2027,44 @@ func TestVariablesMetadata(t *testing.T) {
 						}
 					}
 
+					bytes := []string{"116 = 0x74", "195 = 0xc3", "168 = 0xa8", "115 = 0x73", "116 = 0x74"}
+					runes := []string{"116", "232", "115", "116"}
+
 					// byteslice
 					ref := checkVarExactIndexed(t, locals, -1, "byteslice", "byteslice", "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", "[]uint8", true, 5, 1)
-					checkNamedChildren(ref, "byteslice", "uint8", []string{"116", "195", "168", "115", "116"}, false)
+					checkNamedChildren(ref, "byteslice", "uint8", bytes, false)
 
 					client.EvaluateRequest("byteslice", 0, "")
 					got := client.ExpectEvaluateResponse(t)
 					ref = checkEvalIndexed(t, got, "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", hasChildren, 5, 1)
-					checkNamedChildren(ref, "byteslice", "uint8", []string{"116", "195", "168", "115", "116"}, true)
+					checkNamedChildren(ref, "byteslice", "uint8", bytes, true)
 
 					// runeslice
 					ref = checkVarExactIndexed(t, locals, -1, "runeslice", "runeslice", "[]int32 len: 4, cap: 4, [116,232,115,116]", "[]int32", true, 4, 1)
-					checkNamedChildren(ref, "runeslice", "int32", []string{"116", "232", "115", "116"}, false)
+					checkNamedChildren(ref, "runeslice", "int32", runes, false)
 
 					client.EvaluateRequest("runeslice", 0, "repl")
 					got = client.ExpectEvaluateResponse(t)
 					ref = checkEvalIndexed(t, got, "[]int32 len: 4, cap: 4, [116,232,115,116]", hasChildren, 4, 1)
-					checkNamedChildren(ref, "runeslice", "int32", []string{"116", "232", "115", "116"}, true)
+					checkNamedChildren(ref, "runeslice", "int32", runes, true)
 
 					// bytearray
 					ref = checkVarExactIndexed(t, locals, -1, "bytearray", "bytearray", "[5]uint8 [116,195,168,115,116]", "[5]uint8", true, 5, 1)
-					checkNamedChildren(ref, "bytearray", "uint8", []string{"116", "195", "168", "115", "116"}, false)
+					checkNamedChildren(ref, "bytearray", "uint8", bytes, false)
 
 					client.EvaluateRequest("bytearray", 0, "hover")
 					got = client.ExpectEvaluateResponse(t)
 					ref = checkEvalIndexed(t, got, "[5]uint8 [116,195,168,115,116]", hasChildren, 5, 1)
-					checkNamedChildren(ref, "bytearray", "uint8", []string{"116", "195", "168", "115", "116"}, true)
+					checkNamedChildren(ref, "bytearray", "uint8", bytes, true)
 
 					// runearray
 					ref = checkVarExactIndexed(t, locals, -1, "runearray", "runearray", "[4]int32 [116,232,115,116]", "[4]int32", true, 4, 1)
-					checkNamedChildren(ref, "runearray", "int32", []string{"116", "232", "115", "116"}, false)
+					checkNamedChildren(ref, "runearray", "int32", runes, false)
 
 					client.EvaluateRequest("runearray", 0, "watch")
 					got = client.ExpectEvaluateResponse(t)
 					ref = checkEvalIndexed(t, got, "[4]int32 [116,232,115,116]", hasChildren, 4, 1)
-					checkNamedChildren(ref, "runearray", "int32", []string{"116", "232", "115", "116"}, true)
+					checkNamedChildren(ref, "runearray", "int32", runes, true)
 				},
 				disconnect: true,
 			}})
@@ -2194,6 +2319,64 @@ func TestSetBreakpoint(t *testing.T) {
 	})
 }
 
+func TestPauseAtStop(t *testing.T) {
+	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{16},
+			[]onBreakpoint{{
+				execute: func() {
+					checkStop(t, client, 1, "main.main", 16)
+
+					client.SetBreakpointsRequest(fixture.Source, []int{6, 8})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{6, fixture.Source, true, ""}, {8, fixture.Source, true, ""}})
+
+					// Send a pause request while stopped on a cleared breakpoint.
+					client.PauseRequest(1)
+					client.ExpectPauseResponse(t)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.ExpectStoppedEvent(t)
+					checkStop(t, client, 1, "main.loop", 6)
+
+					// Send a pause request while stopped on a breakpoint.
+					client.PauseRequest(1)
+					client.ExpectPauseResponse(t)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se := client.ExpectStoppedEvent(t)
+					if se.Body.Reason != "breakpoint" {
+						t.Errorf("got %#v, expected breakpoint", se)
+					}
+					checkStop(t, client, 1, "main.loop", 8)
+
+					// Send a pause request while stopped after stepping.
+					client.NextRequest(1)
+					client.ExpectNextResponse(t)
+					client.ExpectStoppedEvent(t)
+					checkStop(t, client, 1, "main.loop", 9)
+
+					client.PauseRequest(1)
+					client.ExpectPauseResponse(t)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+
+					client.ExpectStoppedEvent(t)
+					checkStop(t, client, 1, "main.loop", 8)
+				},
+				// The program has an infinite loop, so we must kill it by disconnecting.
+				disconnect: true,
+			}})
+	})
+}
+
 func checkHitBreakpointIds(t *testing.T, se *dap.StoppedEvent, reason string, id int) {
 	if se.Body.ThreadId != 1 || se.Body.Reason != reason || len(se.Body.HitBreakpointIds) != 1 || se.Body.HitBreakpointIds[0] != id {
 		t.Errorf("got %#v, want Reason=%q, ThreadId=1, HitBreakpointIds=[]int{%d}", se, reason, id)
@@ -4215,8 +4398,9 @@ func TestNoDebug_AcceptNoRequestsButDisconnect(t *testing.T) {
 }
 
 func TestLaunchRequestWithRelativeBuildPath(t *testing.T) {
-	client := startDapServer(t)
-	defer client.Close() // will trigger Stop()
+	serverStopped := make(chan struct{})
+	client := startDapServerWithClient(t, serverStopped)
+	defer client.Close()
 
 	fixdir := protest.FindFixturesDir()
 	if filepath.IsAbs(fixdir) {
@@ -4231,6 +4415,7 @@ func TestLaunchRequestWithRelativeBuildPath(t *testing.T) {
 		client.LaunchRequestWithArgs(map[string]interface{}{
 			"mode": "debug", "program": program, "cwd": filepath.Dir(dlvwd)})
 	})
+	<-serverStopped
 }
 
 func TestLaunchRequestWithRelativeExecPath(t *testing.T) {
@@ -4249,14 +4434,16 @@ func TestLaunchRequestWithRelativeExecPath(t *testing.T) {
 }
 
 func TestLaunchTestRequest(t *testing.T) {
-	client := startDapServer(t)
-	defer client.Close() // will trigger Stop()
+	serverStopped := make(chan struct{})
+	client := startDapServerWithClient(t, serverStopped)
+	defer client.Close()
 	runDebugSession(t, client, "launch", func() {
 		fixtures := protest.FindFixturesDir()
 		testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest"))
 		client.LaunchRequestWithArgs(map[string]interface{}{
 			"mode": "test", "program": testdir, "output": "__mytestdir"})
 	})
+	<-serverStopped
 }
 
 // Tests that 'args' from LaunchRequest are parsed and passed to the target
@@ -4412,6 +4599,9 @@ func TestUnupportedCommandResponses(t *testing.T) {
 
 		client.ModulesRequest()
 		expectUnsupportedCommand("modules")
+
+		client.DisconnectRequest()
+		client.ExpectDisconnectResponse(t)
 	})
 }
 
@@ -4816,6 +5006,9 @@ func TestOptionalNotYetImplementedResponses(t *testing.T) {
 
 		client.CancelRequest()
 		expectNotYetImplemented("cancel")
+
+		client.DisconnectRequest()
+		client.ExpectDisconnectResponse(t)
 	})
 }
 
@@ -4849,84 +5042,85 @@ func TestBadLaunchRequests(t *testing.T) {
 
 		// Test for the DAP-specific detailed error message.
 		client.LaunchRequest("exec", "", stopOnEntry)
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
 		// Bad "program"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": 12345})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"program\" of type string")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": nil})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug"})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
 		// Bad "mode"
 		client.LaunchRequest("remote", fixture.Path, stopOnEntry)
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - unsupported 'mode' attribute \"remote\"")
 
 		client.LaunchRequest("notamode", fixture.Path, stopOnEntry)
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - unsupported 'mode' attribute \"notamode\"")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": 12345, "program": fixture.Path})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"mode\" of type string")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": ""}) // empty mode defaults to "debug" (not an error)
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{}) // missing mode defaults to "debug" (not an error)
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
 		// Bad "args"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": "foobar"})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal string into \"args\" of type []string")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": 12345})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"args\" of type []string")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": []int{1, 2}})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"args\" of type string")
 
 		// Bad "buildFlags"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"buildFlags\" of type string")
 
 		// Bad "backend"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": 123})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"backend\" of type string")
+
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": "foo"})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: could not launch process: unknown backend \"foo\"")
 
 		// Bad "substitutePath"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": 123})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"substitutePath\" of type {\"from\":string, \"to\":string}")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{123}})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot use 123 as 'substitutePath' of type {\"from\":string, \"to\":string}")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"to": "path2"}}})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - 'substitutePath' requires both 'from' and 'to' entries")
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"from": "path1", "to": 123}}})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: invalid debug configuration - cannot use {\"from\":\"path1\",\"to\":123} as 'substitutePath' of type {\"from\":string, \"to\":string}")
 		// Bad "cwd"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "cwd": 123})
@@ -4935,7 +5129,7 @@ func TestBadLaunchRequests(t *testing.T) {
 
 		// Skip detailed message checks for potentially different OS-specific errors.
 		client.LaunchRequest("exec", fixture.Path+"_does_not_exist", stopOnEntry)
-		checkFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) // No such file or directory
+		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // No such file or directory
 
 		client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry)
 		oe := client.ExpectOutputEvent(t)
@@ -4957,7 +5151,7 @@ func TestBadLaunchRequests(t *testing.T) {
 		checkFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
 
 		client.LaunchRequest("exec", fixture.Source, stopOnEntry)
-		checkFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) // Not an executable
+		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // Not an executable
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "-bad -flags"})
 		oe = client.ExpectOutputEvent(t)
@@ -4974,39 +5168,40 @@ func TestBadLaunchRequests(t *testing.T) {
 
 		// Bad "cwd"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": false, "cwd": "dir/invalid"})
-		checkFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent.
+		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // invalid directory, the error message is system-dependent.
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "cwd": "dir/invalid"})
-		checkFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent.
+		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // invalid directory, the error message is system-dependent.
 
 		// Bad "noDebug"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": "true"})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: invalid debug configuration - cannot unmarshal string into \"noDebug\" of type bool")
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t), "Failed to launch: invalid debug configuration - cannot unmarshal string into \"noDebug\" of type bool")
+
 		// Bad "replay" parameters
 		// These errors come from dap layer
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "traceDirPath": ""})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "program": fixture.Source, "traceDirPath": ""})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
 		// These errors come from debugger layer
 		if _, err := exec.LookPath("rr"); err != nil {
 			client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "backend": "ignored", "traceDirPath": ".."})
-			checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 				"Failed to launch: backend unavailable")
 		}
 
 		// Bad "core" parameters
 		// These errors come from dap layer
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "coreFilePath": ""})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "program": fixture.Source, "coreFilePath": ""})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: The 'coreFilePath' attribute is missing in debug configuration.")
 		// These errors come from debugger layer
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "backend": "ignored", "program": fixture.Source, "coreFilePath": fixture.Source})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to launch: unrecognized core format")
 
 		// We failed to launch the program. Make sure shutdown still works.
@@ -5048,45 +5243,45 @@ func TestBadAttachRequest(t *testing.T) {
 
 		// Bad "mode"
 		client.AttachRequest(map[string]interface{}{"mode": "remote"})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: invalid debug configuration - unsupported 'mode' attribute \"remote\"")
 
 		client.AttachRequest(map[string]interface{}{"mode": "blah blah blah"})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: invalid debug configuration - unsupported 'mode' attribute \"blah blah blah\"")
 
 		client.AttachRequest(map[string]interface{}{"mode": 123})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: invalid debug configuration - cannot unmarshal number into \"mode\" of type string")
 
 		client.AttachRequest(map[string]interface{}{"mode": ""}) // empty mode defaults to "local" (not an error)
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
 		client.AttachRequest(map[string]interface{}{}) // no mode defaults to "local" (not an error)
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
 		// Bad "processId"
 		client.AttachRequest(map[string]interface{}{"mode": "local"})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": nil})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 0})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": "1"})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: invalid debug configuration - cannot unmarshal string into \"processId\" of type int")
 
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1})
 		// The exact message varies on different systems, so skip that check
-		checkFailedToAttach(client.ExpectInvisibleErrorResponse(t)) // could not attach to pid 1
+		checkFailedToAttach(client.ExpectVisibleErrorResponse(t)) // could not attach to pid 1
 
 		// This will make debugger.(*Debugger) panic, which we will catch as an internal error.
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": -1})
@@ -5107,10 +5302,11 @@ func TestBadAttachRequest(t *testing.T) {
 
 		// Bad "backend"
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": 123})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: invalid debug configuration - cannot unmarshal number into \"backend\" of type string")
+
 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": "foo"})
-		checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
+		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 			"Failed to attach: could not attach to pid 1: unknown backend \"foo\"")
 
 		// We failed to attach to the program. Make sure shutdown still works.
@@ -5127,9 +5323,8 @@ func TestBadInitializeRequest(t *testing.T) {
 		t.Helper()
 		// Only one initialize request is allowed, so use a new server
 		// for each test.
-		client := startDapServer(t)
-		// client.Close will close the client connectinon, which will cause a connection error
-		// on the server side and signal disconnect to unblock Stop() above.
+		serverStopped := make(chan struct{})
+		client := startDapServerWithClient(t, serverStopped)
 		defer client.Close()
 
 		client.InitializeRequestWithArgs(args)
@@ -5146,6 +5341,10 @@ func TestBadInitializeRequest(t *testing.T) {
 		if response.Body.Error.Format != err {
 			t.Errorf("\ngot  %q\nwant %q", response.Body.Error.Format, err)
 		}
+
+		client.DisconnectRequest()
+		client.ExpectDisconnectResponse(t)
+		<-serverStopped
 	}
 
 	// Bad path format.
@@ -5207,5 +5406,8 @@ func TestBadlyFormattedMessageToServer(t *testing.T) {
 		// Make sure that the unknown request did not kill the server.
 		client.InitializeRequest()
 		client.ExpectInitializeResponse(t)
+
+		client.DisconnectRequest()
+		client.ExpectDisconnectResponse(t)
 	})
 }
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index e3bad857..f332b9b4 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1892,6 +1892,39 @@ func TestAcceptMulticlient(t *testing.T) {
 	<-serverDone
 }
 
+func TestForceStopWhileContinue(t *testing.T) {
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("couldn't start listener: %s\n", err)
+	}
+	serverStopped := make(chan struct{})
+	disconnectChan := make(chan struct{})
+	go func() {
+		defer close(serverStopped)
+		defer listener.Close()
+		server := rpccommon.NewServer(&service.Config{
+			Listener:       listener,
+			ProcessArgs:    []string{protest.BuildFixture("http_server", protest.AllNonOptimized).Path},
+			AcceptMulti:    true,
+			DisconnectChan: disconnectChan,
+			Debugger: debugger.Config{
+				Backend: "default",
+			},
+		})
+		if err := server.Run(); err != nil {
+			panic(err)
+		}
+		<-disconnectChan
+		server.Stop()
+	}()
+
+	client := rpc2.NewClient(listener.Addr().String())
+	client.Disconnect(true /*continue*/)
+	time.Sleep(10 * time.Millisecond) // give server time to start running
+	close(disconnectChan)             // stop the server
+	<-serverStopped                   // Stop() didn't block on detach because we halted first
+}
+
 func TestClientServerFunctionCall(t *testing.T) {
 	protest.MustSupportFunctionCalls(t, testBackend)
 	withTestClient2("fncall", t, func(c service.Client) {