src/goDebug: check if delve exited before attempting to get state

There is an initial ThreadsRequest after initialization from
vscode. If the program exits too quickly, this request may come
after delve has already exited. There is already a check to
ignore an error from ListGoRoutines if the debugState.exited.
This change adds the same check to threadsRequest before
isDebuggeeRunning is called, which tries to get the debugState
from delve.

Updates golang/vscode-go#1113

Change-Id: I01b1f2af9d7357c2363709b94435b797aa042459
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/286492
Trust: Suzy Mueller <suzmue@golang.org>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
Reviewed-by: Polina Sokolova <polina@google.com>
diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts
index 17cdaad..5cec197 100644
--- a/src/debugAdapter/goDebug.ts
+++ b/src/debugAdapter/goDebug.ts
@@ -1363,6 +1363,11 @@
 			// Thread request to delve is synchronous and will block if a previous async continue request didn't return
 			response.body = { threads: [new Thread(1, 'Dummy')] };
 			return this.sendResponse(response);
+		} else if (this.debugState && this.debugState.exited) {
+			// If the program exits very quickly, the initial threadsRequest will complete after it has exited.
+			// A TerminatedEvent has already been sent. d
+			response.body = { threads: [] };
+			return this.sendResponse(response);
 		}
 		log('ThreadsRequest');
 		this.delve.call<DebugGoroutine[] | ListGoroutinesOut>('ListGoroutines', [], (err, out) => {
@@ -2429,6 +2434,9 @@
 	// instead of issuing a getDebugState call to Delve. Perhaps we want to
 	// do that to improve performance in the future.
 	private async isDebuggeeRunning(): Promise<boolean> {
+		if (this.debugState && this.debugState.exited) {
+			return false;
+		}
 		try {
 			this.debugState = await this.delve.getDebugState();
 			return this.debugState.Running;
diff --git a/test/integration/goDebug.test.ts b/test/integration/goDebug.test.ts
index 87f19cd..58aa1fd 100644
--- a/test/integration/goDebug.test.ts
+++ b/test/integration/goDebug.test.ts
@@ -592,6 +592,53 @@
 			]);
 		});
 
+		test('should handle threads request after initialization', async () => {
+			const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'auto',
+				program: PROGRAM,
+			};
+			const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+			return Promise.all([
+				dc.configurationSequence().then(() => {
+					dc.threadsRequest().then((response) => {
+						assert.ok(response.success);
+					});
+				}),
+				dc.launch(debugConfig),
+				dc.waitForEvent('terminated'),
+			]);
+		});
+
+		test('should handle delayed initial threads request', async () => {
+			// If the program exits very quickly, the initial threadsRequest
+			// will complete after it has exited.
+			const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'auto',
+				program: PROGRAM,
+			};
+			const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+			await Promise.all([
+				dc.configurationSequence(),
+				dc.launch(debugConfig),
+				dc.waitForEvent('terminated')
+			]);
+
+			const response = await dc.threadsRequest();
+			assert.ok(response.success);
+		});
+
 		test('user-specified --listen flag should be ignored', () => {
 			const PROGRAM = path.join(DATA_ROOT, 'baseTest');
 			const config = {