src/language: create go extension context

The GoExtensionContext object will hold the shared state
related to the language client and server.

Change-Id: I1795f854f360af6809ca73637ac59cdd9b24c57e
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/404075
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/context.ts b/src/context.ts
new file mode 100644
index 0000000..a1bff86
--- /dev/null
+++ b/src/context.ts
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { LanguageClient } from 'vscode-languageclient/node';
+
+import { LanguageServerConfig, Restart, ServerInfo } from './language/goLanguageServer';
+import { LegacyLanguageService } from './language/registerDefaultProviders';
+import { Mutex } from './utils/mutex';
+
+// Global variables used for management of the language client.
+// They are global so that the server can be easily restarted with
+// new configurations.
+export interface GoExtensionContext {
+	languageClient?: LanguageClient;
+	legacyLanguageService?: LegacyLanguageService;
+	languageServerDisposable?: vscode.Disposable;
+	latestConfig?: LanguageServerConfig;
+	serverOutputChannel?: vscode.OutputChannel;
+	languageServerIsRunning?: boolean;
+	// serverInfo is the information from the server received during initialization.
+	serverInfo?: ServerInfo;
+	// lastUserAction is the time of the last user-triggered change.
+	// A user-triggered change is a didOpen, didChange, didSave, or didClose event.
+	lastUserAction: Date;
+	serverTraceChannel?: vscode.OutputChannel;
+	crashCount: number;
+	// Some metrics for automated issue reports:
+	restartHistory: Restart[];
+	languageServerStartMutex: Mutex;
+}
diff --git a/src/goDeveloperSurvey.ts b/src/goDeveloperSurvey.ts
index ec942e8..4235606 100644
--- a/src/goDeveloperSurvey.ts
+++ b/src/goDeveloperSurvey.ts
@@ -8,8 +8,8 @@
 
 import vscode = require('vscode');
 import { getGoConfig } from './config';
-import { lastUserAction } from './language/goLanguageServer';
 import { daysBetween, flushSurveyConfig, getStateConfig, minutesBetween, timeMinute } from './goSurvey';
+import { GoExtensionContext } from './context';
 
 // Start and end dates of the survey.
 export const startDate = new Date('2021-10-27');
@@ -35,7 +35,7 @@
 	lastDateAccepted?: Date;
 }
 
-export function maybePromptForDeveloperSurvey() {
+export function maybePromptForDeveloperSurvey(goCtx: GoExtensionContext) {
 	// First, check the value of the 'go.survey.prompt' setting to see
 	// if the user has opted out of all survey prompts.
 	const goConfig = getGoConfig();
@@ -54,7 +54,7 @@
 		const currentTime = new Date();
 
 		// Make sure the user has been idle for at least a minute.
-		if (minutesBetween(lastUserAction, currentTime) < 1) {
+		if (minutesBetween(goCtx.lastUserAction, currentTime) < 1) {
 			setTimeout(callback, 5 * timeMinute);
 			return;
 		}
diff --git a/src/goImport.ts b/src/goImport.ts
index 43d728e..e6d4520 100644
--- a/src/goImport.ts
+++ b/src/goImport.ts
@@ -12,11 +12,11 @@
 import { ExecuteCommandRequest, ExecuteCommandParams } from 'vscode-languageserver-protocol';
 import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { languageClient, serverInfo } from './language/goLanguageServer';
 import { documentSymbols, GoOutlineImportsOptions } from './language/legacy/goOutline';
 import { getImportablePackages } from './goPackages';
 import { getBinPath, getImportPath, parseFilePrelude } from './util';
 import { envPath, getCurrentGoRoot } from './utils/pathUtils';
+import { GoExtensionContext } from './context';
 
 const missingToolMsg = 'Missing tool: ';
 
@@ -44,7 +44,8 @@
 	return [...stdLibs.sort(), ...nonStdLibs.sort()];
 }
 
-async function golist(): Promise<string[]> {
+async function golist(goCtx: GoExtensionContext): Promise<string[]> {
+	const { languageClient, serverInfo } = goCtx;
 	const COMMAND = 'gopls.list_known_packages';
 	if (languageClient && serverInfo?.Commands?.includes(COMMAND)) {
 		try {
@@ -96,9 +97,9 @@
 	return imports;
 }
 
-async function askUserForImport(): Promise<string | undefined> {
+async function askUserForImport(goCtx: GoExtensionContext): Promise<string | undefined> {
 	try {
-		const packages = await golist();
+		const packages = await golist(goCtx);
 		return vscode.window.showQuickPick(packages);
 	} catch (err) {
 		if (typeof err === 'string' && err.startsWith(missingToolMsg)) {
@@ -162,13 +163,14 @@
 	}
 }
 
-export function addImport(arg: { importPath: string }) {
+export function addImport(goCtx: GoExtensionContext, arg: { importPath: string }) {
+	const { languageClient, serverInfo } = goCtx;
 	const editor = vscode.window.activeTextEditor;
 	if (!editor) {
 		vscode.window.showErrorMessage('No active editor found to add imports.');
 		return;
 	}
-	const p = arg && arg.importPath ? Promise.resolve(arg.importPath) : askUserForImport();
+	const p = arg && arg.importPath ? Promise.resolve(arg.importPath) : askUserForImport(goCtx);
 	p.then(async (imp) => {
 		if (!imp) {
 			return;
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index b8b5853..f6485ea 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -17,6 +17,7 @@
 import { toolExecutionEnvironment, toolInstallationEnvironment } from './goEnv';
 import { addGoRuntimeBaseToPATH, clearGoRuntimeBaseFromPATH } from './goEnvironmentStatus';
 import { logVerbose } from './goLogging';
+import { GoExtensionContext } from './context';
 import { restartLanguageServer } from './goMain';
 import { addGoStatus, initGoStatusBar, outputChannel, removeGoStatus } from './goStatus';
 import {
@@ -512,7 +513,7 @@
 	}
 }
 
-export function updateGoVarsFromConfig(): Promise<void> {
+export function updateGoVarsFromConfig(goCtx: GoExtensionContext): Promise<void> {
 	const { binPath, why } = getBinPathWithExplanation('go', false);
 	const goRuntimePath = binPath;
 
@@ -570,7 +571,7 @@
 					// clear pre-existing terminal PATH mutation logic set up by this extension.
 					clearGoRuntimeBaseFromPATH();
 				}
-				initGoStatusBar();
+				initGoStatusBar(goCtx);
 				// TODO: restart language server or synchronize with language server update.
 
 				return resolve();
diff --git a/src/goMain.ts b/src/goMain.ts
index 22f06ec..7df094f 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -46,7 +46,6 @@
 	updateGoVarsFromConfig
 } from './goInstallTools';
 import {
-	languageServerIsRunning,
 	RestartReason,
 	showServerOutputChannel,
 	startLanguageServerWithFallback,
@@ -104,7 +103,7 @@
 import semver = require('semver');
 import vscode = require('vscode');
 import { getFormatTool } from './language/legacy/goFormat';
-import { resetSurveyConfigs, showSurveyConfig, timeMinute } from './goSurvey';
+import { resetSurveyConfigs, showSurveyConfig } from './goSurvey';
 import { ExtensionAPI } from './export';
 import extensionAPI from './extensionAPI';
 import { GoTestExplorer, isVscodeTestingAPIAvailable } from './goTest/explore';
@@ -112,6 +111,18 @@
 import { GoExplorerProvider } from './goExplorer';
 import { VulncheckProvider } from './goVulncheck';
 
+import { Mutex } from './utils/mutex';
+import { GoExtensionContext } from './context';
+
+// TODO: Remove this export. Temporarily exporting the context for import into the
+// legacy DocumentSymbolProvider.
+export const goCtx: GoExtensionContext = {
+	lastUserAction: new Date(),
+	crashCount: 0,
+	restartHistory: [],
+	languageServerStartMutex: new Mutex()
+};
+
 export let buildDiagnosticCollection: vscode.DiagnosticCollection;
 export let lintDiagnosticCollection: vscode.DiagnosticCollection;
 export let vetDiagnosticCollection: vscode.DiagnosticCollection;
@@ -196,17 +207,17 @@
 	ctx: vscode.ExtensionContext,
 	cfg: vscode.WorkspaceConfiguration
 ): Promise<ExtensionAPI> {
-	await updateGoVarsFromConfig();
+	await updateGoVarsFromConfig(goCtx);
 
 	suggestUpdates(ctx);
 	offerToInstallLatestGoVersion();
 	offerToInstallTools();
 
 	// TODO: let configureLanguageServer to return its status.
-	await configureLanguageServer(ctx);
+	await configureLanguageServer(ctx, goCtx);
 
 	const activeDoc = vscode.window.activeTextEditor?.document;
-	if (!languageServerIsRunning && activeDoc?.languageId === 'go' && isGoPathSet()) {
+	if (!goCtx.languageServerIsRunning && activeDoc?.languageId === 'go' && isGoPathSet()) {
 		// Check mod status so that cache is updated and then run build/lint/vet
 		isModSupported(activeDoc.uri).then(() => {
 			runBuilds(activeDoc, getGoConfig());
@@ -217,7 +228,7 @@
 
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.environment.status', async () => {
-			expandGoStatusBar();
+			expandGoStatusBar(goCtx);
 		})
 	);
 	const testCodeLensProvider = new GoRunTestCodeLensProvider();
@@ -342,7 +353,7 @@
 	}
 
 	GoExplorerProvider.setup(ctx);
-	VulncheckProvider.setup(ctx);
+	VulncheckProvider.setup(ctx, goCtx);
 
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.subtest.cursor', (args) => {
@@ -436,7 +447,7 @@
 
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.import.add', (arg) => {
-			return addImport(arg);
+			return addImport(goCtx, arg);
 		})
 	);
 
@@ -483,7 +494,7 @@
 				e.affectsConfiguration('go.toolsEnvVars') ||
 				e.affectsConfiguration('go.testEnvFile')
 			) {
-				updateGoVarsFromConfig();
+				updateGoVarsFromConfig(goCtx);
 			}
 			if (e.affectsConfiguration('go.logging')) {
 				setLogConfig(updatedGoConfig['logging']);
@@ -630,7 +641,7 @@
 
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.extractServerChannel', () => {
-			showServerOutputChannel();
+			showServerOutputChannel(goCtx);
 		})
 	);
 
@@ -648,7 +659,7 @@
 
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.toggle.gc_details', () => {
-			if (!languageServerIsRunning) {
+			if (!goCtx.languageServerIsRunning) {
 				vscode.window.showErrorMessage(
 					'"Go: Toggle gc details" command is available only when the language server is running'
 				);
@@ -709,7 +720,7 @@
 	);
 
 	// Survey related commands
-	ctx.subscriptions.push(vscode.commands.registerCommand('go.survey.showConfig', () => showSurveyConfig()));
+	ctx.subscriptions.push(vscode.commands.registerCommand('go.survey.showConfig', () => showSurveyConfig(goCtx)));
 	ctx.subscriptions.push(vscode.commands.registerCommand('go.survey.resetConfig', () => resetSurveyConfigs()));
 
 	vscode.languages.setLanguageConfiguration(GO_MODE.language, {
@@ -933,20 +944,22 @@
 	}
 }
 
-function configureLanguageServer(ctx: vscode.ExtensionContext) {
+function configureLanguageServer(ctx: vscode.ExtensionContext, goCtx: GoExtensionContext) {
 	// Subscribe to notifications for changes to the configuration
 	// of the language server, even if it's not currently in use.
-	ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => watchLanguageServerConfiguration(e)));
+	ctx.subscriptions.push(
+		vscode.workspace.onDidChangeConfiguration((e) => watchLanguageServerConfiguration(goCtx, e))
+	);
 
 	// Set the function that is used to restart the language server.
 	// This is necessary, even if the language server is not currently
 	// in use.
 	restartLanguageServer = async (reason: RestartReason) => {
-		startLanguageServerWithFallback(ctx, reason);
+		startLanguageServerWithFallback(ctx, goCtx, reason);
 	};
 
 	// Start the language server, or fallback to the default language providers.
-	return startLanguageServerWithFallback(ctx, 'activation');
+	return startLanguageServerWithFallback(ctx, goCtx, 'activation');
 }
 
 function getCurrentGoPathCommand() {
diff --git a/src/goStatus.ts b/src/goStatus.ts
index 56360e5..87807ab 100644
--- a/src/goStatus.ts
+++ b/src/goStatus.ts
@@ -11,16 +11,12 @@
 import vscodeUri = require('vscode-uri');
 import { getGoConfig } from './config';
 import { formatGoVersion, GoEnvironmentOption, terminalCreationListener } from './goEnvironmentStatus';
-import {
-	buildLanguageServerConfig,
-	getLocalGoplsVersion,
-	languageServerIsRunning,
-	serverOutputChannel
-} from './language/goLanguageServer';
+import { buildLanguageServerConfig, getLocalGoplsVersion } from './language/goLanguageServer';
 import { isGoFile } from './goMode';
 import { isModSupported, runGoEnv } from './goModules';
 import { allToolsInformation } from './goToolsInformation';
 import { getGoVersion } from './util';
+import { GoExtensionContext } from './context';
 
 export const outputChannel = vscode.window.createOutputChannel('Go');
 
@@ -57,7 +53,8 @@
 	}
 }
 
-export async function expandGoStatusBar() {
+export async function expandGoStatusBar(goCtx: GoExtensionContext) {
+	const { languageServerIsRunning, serverOutputChannel } = goCtx;
 	const options = [
 		{ label: 'Locate Configured Go Tools', description: 'display go env' },
 		{ label: 'Choose Go Environment' }
@@ -120,7 +117,8 @@
 /**
  * Initialize the status bar item with current Go binary
  */
-export async function initGoStatusBar() {
+export async function initGoStatusBar(goCtx: GoExtensionContext) {
+	const { languageServerIsRunning } = goCtx;
 	if (!goEnvStatusbarItem) {
 		const STATUS_BAR_ITEM_NAME = 'Go';
 		goEnvStatusbarItem = vscode.window.createStatusBarItem(
@@ -141,7 +139,7 @@
 	// Assume if it is configured it is already running, since the
 	// icon will be updated on an attempt to start.
 	const goConfig = getGoConfig();
-	updateLanguageServerIconGoStatusBar(languageServerIsRunning, goConfig['useLanguageServer'] === true);
+	updateLanguageServerIconGoStatusBar(!!languageServerIsRunning, goConfig['useLanguageServer'] === true);
 
 	showGoStatusBar();
 }
diff --git a/src/goSurvey.ts b/src/goSurvey.ts
index b55c2d4..cf76bb0 100644
--- a/src/goSurvey.ts
+++ b/src/goSurvey.ts
@@ -7,7 +7,7 @@
 'use strict';
 
 import vscode = require('vscode');
-import { getLocalGoplsVersion, lastUserAction, latestConfig } from './language/goLanguageServer';
+import { getLocalGoplsVersion } from './language/goLanguageServer';
 import { outputChannel } from './goStatus';
 import { extensionId } from './const';
 import { getFromGlobalState, getFromWorkspaceState, updateGlobalState } from './stateUtils';
@@ -19,6 +19,7 @@
 } from './goDeveloperSurvey';
 import { getGoConfig } from './config';
 import { getGoVersion } from './util';
+import { GoExtensionContext } from './context';
 
 // GoplsSurveyConfig is the set of global properties used to determine if
 // we should prompt a user to take the gopls survey.
@@ -48,7 +49,7 @@
 	lastDateAccepted?: Date;
 }
 
-export function maybePromptForGoplsSurvey() {
+export function maybePromptForGoplsSurvey(goCtx: GoExtensionContext) {
 	// First, check the value of the 'go.survey.prompt' setting to see
 	// if the user has opted out of all survey prompts.
 	const goConfig = getGoConfig();
@@ -67,11 +68,11 @@
 		const currentTime = new Date();
 
 		// Make sure the user has been idle for at least a minute.
-		if (minutesBetween(lastUserAction, currentTime) < 1) {
+		if (minutesBetween(goCtx.lastUserAction, currentTime) < 1) {
 			setTimeout(callback, 5 * timeMinute);
 			return;
 		}
-		cfg = await promptForGoplsSurvey(cfg, now);
+		cfg = await promptForGoplsSurvey(goCtx, cfg, now);
 		if (cfg) {
 			flushSurveyConfig(goplsSurveyConfig, cfg);
 		}
@@ -145,7 +146,11 @@
 	return Math.floor(Math.random() * (high - low + 1)) + low;
 }
 
-async function promptForGoplsSurvey(cfg: GoplsSurveyConfig = {}, now: Date): Promise<GoplsSurveyConfig> {
+async function promptForGoplsSurvey(
+	goCtx: GoExtensionContext,
+	cfg: GoplsSurveyConfig = {},
+	now: Date
+): Promise<GoplsSurveyConfig> {
 	let selected = await vscode.window.showInformationMessage(
 		`Looks like you are using the Go extension for VS Code.
 Could you help us improve this extension by filling out a 1-2 minute survey about your experience with it?`,
@@ -160,9 +165,10 @@
 	switch (selected) {
 		case 'Yes':
 			{
+				const { latestConfig } = goCtx;
 				cfg.lastDateAccepted = now;
 				cfg.prompt = true;
-				const goplsEnabled = latestConfig.enabled;
+				const goplsEnabled = latestConfig?.enabled;
 				const usersGoplsVersion = await getLocalGoplsVersion(latestConfig);
 				const goV = await getGoVersion();
 				const goVersion = goV ? (goV.isDevel ? 'devel' : goV.format(true)) : 'na';
@@ -245,7 +251,7 @@
 	}
 }
 
-export async function showSurveyConfig() {
+export async function showSurveyConfig(goCtx: GoExtensionContext) {
 	// TODO(rstambler): Add developer survey config.
 	outputChannel.appendLine('HaTs Survey Configuration');
 	outputChannel.appendLine(JSON.stringify(getGoplsSurveyConfig(), null, 2));
@@ -258,10 +264,10 @@
 	let selected = await vscode.window.showInformationMessage('Prompt for HaTS survey?', 'Yes', 'Maybe', 'No');
 	switch (selected) {
 		case 'Yes':
-			promptForGoplsSurvey(getGoplsSurveyConfig(), new Date());
+			promptForGoplsSurvey(goCtx, getGoplsSurveyConfig(), new Date());
 			break;
 		case 'Maybe':
-			maybePromptForGoplsSurvey();
+			maybePromptForGoplsSurvey(goCtx);
 			break;
 		default:
 			break;
@@ -272,7 +278,7 @@
 			promptForDeveloperSurvey(getDeveloperSurveyConfig(), new Date());
 			break;
 		case 'Maybe':
-			maybePromptForDeveloperSurvey();
+			maybePromptForDeveloperSurvey(goCtx);
 			break;
 		default:
 			break;
diff --git a/src/goVulncheck.ts b/src/goVulncheck.ts
index fe173b4..29136b6 100644
--- a/src/goVulncheck.ts
+++ b/src/goVulncheck.ts
@@ -7,17 +7,16 @@
 import { pathToFileURL } from 'url';
 import * as vscode from 'vscode';
 import { ExecuteCommandRequest } from 'vscode-languageserver-protocol';
-
-import { languageClient, serverInfo } from './language/goLanguageServer';
+import { GoExtensionContext } from './context';
 
 export class VulncheckProvider {
 	static scheme = 'govulncheck';
-	static setup({ subscriptions }: vscode.ExtensionContext) {
+	static setup({ subscriptions }: vscode.ExtensionContext, goCtx: GoExtensionContext) {
 		const channel = vscode.window.createOutputChannel('govulncheck');
 		const instance = new this(channel);
 		subscriptions.push(
 			vscode.commands.registerCommand('go.vulncheck.run', async () => {
-				instance.run();
+				instance.run(goCtx);
 			})
 		);
 		return instance;
@@ -27,20 +26,20 @@
 
 	private running = false;
 
-	async run() {
+	async run(goCtx: GoExtensionContext) {
 		if (this.running) {
 			vscode.window.showWarningMessage('another vulncheck is in progress');
 			return;
 		}
 		try {
 			this.running = true;
-			await this.runInternal();
+			await this.runInternal(goCtx);
 		} finally {
 			this.running = false;
 		}
 	}
 
-	private async runInternal() {
+	private async runInternal(goCtx: GoExtensionContext) {
 		const pick = await vscode.window.showQuickPick(['Current Package', 'Workspace']);
 		let dir, pattern: string;
 		const document = vscode.window.activeTextEditor?.document;
@@ -72,7 +71,7 @@
 
 		let result = '\nNo known vulnerabilities found.';
 		try {
-			const vuln = await vulncheck(dir, pattern);
+			const vuln = await vulncheck(goCtx, dir, pattern);
 			if (vuln?.Vuln) {
 				result = vuln.Vuln.map(renderVuln).join('----------------------\n');
 			}
@@ -105,7 +104,12 @@
 	}
 }
 
-async function vulncheck(dir: string, pattern = './...'): Promise<VulncheckReponse | undefined> {
+async function vulncheck(
+	goCtx: GoExtensionContext,
+	dir: string,
+	pattern = './...'
+): Promise<VulncheckReponse | undefined> {
+	const { languageClient, serverInfo } = goCtx;
 	const COMMAND = 'gopls.run_vulncheck_exp';
 	if (languageClient && serverInfo?.Commands?.includes(COMMAND)) {
 		const request = {
diff --git a/src/language/goLanguageServer.ts b/src/language/goLanguageServer.ts
index 3c8007c..e399fe6 100644
--- a/src/language/goLanguageServer.ts
+++ b/src/language/goLanguageServer.ts
@@ -38,6 +38,7 @@
 import { toolExecutionEnvironment } from '../goEnv';
 import { GoDocumentFormattingEditProvider, usingCustomFormatTool } from './legacy/goFormat';
 import { installTools, latestToolVersion, promptForMissingTool, promptForUpdatingTool } from '../goInstallTools';
+import { GoExtensionContext } from '../context';
 import {
 	buildDiagnosticCollection,
 	lintDiagnosticCollection,
@@ -55,7 +56,6 @@
 	getWorkspaceFolderPath,
 	removeDuplicateDiagnostics
 } from '../util';
-import { Mutex } from '../utils/mutex';
 import { getToolFromToolPath } from '../utils/pathUtils';
 import WebRequest = require('web-request');
 import { FoldingContext } from 'vscode';
@@ -64,7 +64,7 @@
 import { maybePromptForDeveloperSurvey } from '../goDeveloperSurvey';
 import { LegacyLanguageService } from './registerDefaultProviders';
 
-interface LanguageServerConfig {
+export interface LanguageServerConfig {
 	serverName: string;
 	path: string;
 	version?: { version: string; goVersion?: string };
@@ -79,48 +79,24 @@
 	checkForUpdates: string;
 }
 
-// Global variables used for management of the language client.
-// They are global so that the server can be easily restarted with
-// new configurations.
-// TODO: refactor it. These can be encapsulated in a single LanguageServer class
-// that keeps track of the state of the active language server instance.
-export let languageClient: LanguageClient | undefined;
-let languageServerDisposable: vscode.Disposable | undefined;
-export let latestConfig: LanguageServerConfig;
-export let serverOutputChannel: vscode.OutputChannel | undefined;
-export let languageServerIsRunning = false;
-
-// serverInfo is the information from the server received during initialization.
-export let serverInfo: ServerInfo | undefined;
-
-interface ServerInfo {
+export interface ServerInfo {
 	Name: string;
 	Version?: string;
 	GoVersion?: string;
 	Commands?: string[];
 }
 
-let legacyLanguageService: LegacyLanguageService | undefined;
-
-const languageServerStartMutex = new Mutex();
-
-let serverTraceChannel: vscode.OutputChannel;
-let crashCount = 0;
-
-// Some metrics for automated issue reports:
-let restartHistory: Restart[] = [];
-
-export function updateRestartHistory(reason: RestartReason, enabled: boolean) {
+export function updateRestartHistory(goCtx: GoExtensionContext, reason: RestartReason, enabled: boolean) {
 	// Keep the history limited to 10 elements.
-	while (restartHistory.length > 10) {
-		restartHistory = restartHistory.slice(1);
+	while (goCtx.restartHistory.length > 10) {
+		goCtx.restartHistory = goCtx.restartHistory.slice(1);
 	}
-	restartHistory.push(new Restart(reason, new Date(), enabled));
+	goCtx.restartHistory.push(new Restart(reason, new Date(), enabled));
 }
 
-function formatRestartHistory(): string {
+function formatRestartHistory(goCtx: GoExtensionContext): string {
 	const result: string[] = [];
-	for (const restart of restartHistory) {
+	for (const restart of goCtx.restartHistory) {
 		result.push(`${restart.timestamp.toUTCString()}: ${restart.reason} (enabled: ${restart.enabled})`);
 	}
 	return result.join('\n');
@@ -128,7 +104,7 @@
 
 export type RestartReason = 'activation' | 'manual' | 'config change' | 'installation';
 
-class Restart {
+export class Restart {
 	reason: RestartReason;
 	timestamp: Date;
 	enabled: boolean;
@@ -144,13 +120,13 @@
 // server.
 let restartCommand: vscode.Disposable;
 
-// lastUserAction is the time of the last user-triggered change.
-// A user-triggered change is a didOpen, didChange, didSave, or didClose event.
-export let lastUserAction: Date = new Date();
-
 // startLanguageServerWithFallback starts the language server, if enabled,
 // or falls back to the default language providers.
-export async function startLanguageServerWithFallback(ctx: vscode.ExtensionContext, reason: RestartReason) {
+export async function startLanguageServerWithFallback(
+	ctx: vscode.ExtensionContext,
+	goCtx: GoExtensionContext,
+	reason: RestartReason
+) {
 	for (const folder of vscode.workspace.workspaceFolders || []) {
 		switch (folder.uri.scheme) {
 			case 'vsls':
@@ -177,12 +153,12 @@
 	const goConfig = getGoConfig();
 	const cfg = buildLanguageServerConfig(goConfig);
 
-	updateRestartHistory(reason, cfg.enabled);
+	updateRestartHistory(goCtx, reason, cfg.enabled);
 
 	// We have some extra prompts for gopls users and for people who have opted
 	// out of gopls.
 	if (reason === 'activation') {
-		scheduleGoplsSuggestions();
+		scheduleGoplsSuggestions(goCtx);
 	}
 
 	// If the language server is gopls, we enable a few additional features.
@@ -197,18 +173,18 @@
 			}
 		}
 	}
-	const unlock = await languageServerStartMutex.lock();
+	const unlock = await goCtx.languageServerStartMutex.lock();
 	try {
-		const started = await startLanguageServer(ctx, cfg);
+		const started = await startLanguageServer(ctx, goCtx, cfg);
 
 		// If the server has been disabled, or failed to start,
 		// fall back to the default providers, while making sure not to
 		// re-register any providers.
-		if (!started && !legacyLanguageService) {
-			legacyLanguageService = new LegacyLanguageService(ctx);
-			ctx.subscriptions.push(legacyLanguageService);
+		if (!started && !goCtx.legacyLanguageService) {
+			goCtx.legacyLanguageService = new LegacyLanguageService(ctx);
+			ctx.subscriptions.push(goCtx.legacyLanguageService);
 		}
-		languageServerIsRunning = started;
+		goCtx.languageServerIsRunning = started;
 		vscode.commands.executeCommand('setContext', 'go.goplsIsRunning', started);
 		updateLanguageServerIconGoStatusBar(started, goConfig['useLanguageServer'] === true);
 	} finally {
@@ -220,7 +196,7 @@
 // suggestions. We check user's gopls versions once per day to prompt users to
 // update to the latest version. We also check if we should prompt users to
 // fill out the survey.
-function scheduleGoplsSuggestions() {
+function scheduleGoplsSuggestions(goCtx: GoExtensionContext) {
 	if (extensionInfo.isInCloudIDE) {
 		return;
 	}
@@ -277,15 +253,15 @@
 		if (!foundGo) {
 			return;
 		}
-		maybePromptForGoplsSurvey();
-		maybePromptForDeveloperSurvey();
+		maybePromptForGoplsSurvey(goCtx);
+		maybePromptForDeveloperSurvey(goCtx);
 	};
 	setTimeout(update, 10 * timeMinute);
 	setTimeout(survey, 30 * timeMinute);
 }
 
 // Ask users to fill out opt-out survey.
-export async function promptAboutGoplsOptOut() {
+export async function promptAboutGoplsOptOut(goCtx: GoExtensionContext) {
 	// Check if the configuration is set in the workspace.
 	const useLanguageServer = getGoConfig().inspect('useLanguageServer');
 	const workspace = useLanguageServer?.workspaceFolderValue === false || useLanguageServer?.workspaceValue === false;
@@ -301,6 +277,7 @@
 		}
 		cfg.lastDatePrompted = new Date();
 		await promptForGoplsOptOutSurvey(
+			goCtx,
 			cfg,
 			"It looks like you've disabled the Go language server. Would you be willing to tell us why you've disabled it, so that we can improve it?"
 		);
@@ -310,12 +287,16 @@
 	flushGoplsOptOutConfig(cfg, workspace);
 }
 
-async function promptForGoplsOptOutSurvey(cfg: GoplsOptOutConfig, msg: string): Promise<GoplsOptOutConfig> {
+async function promptForGoplsOptOutSurvey(
+	goCtx: GoExtensionContext,
+	cfg: GoplsOptOutConfig,
+	msg: string
+): Promise<GoplsOptOutConfig> {
 	const s = await vscode.window.showInformationMessage(msg, { title: 'Yes' }, { title: 'No' });
 	if (!s) {
 		return cfg;
 	}
-	const localGoplsVersion = await getLocalGoplsVersion(latestConfig);
+	const localGoplsVersion = await getLocalGoplsVersion(goCtx.latestConfig);
 	const goplsVersion = localGoplsVersion?.version || 'na';
 	const goV = await getGoVersion();
 	let goVersion = 'na';
@@ -355,12 +336,17 @@
 	updateGlobalState(goplsOptOutConfigKey, JSON.stringify(cfg));
 };
 
-async function startLanguageServer(ctx: vscode.ExtensionContext, config: LanguageServerConfig): Promise<boolean> {
+async function startLanguageServer(
+	ctx: vscode.ExtensionContext,
+	goCtx: GoExtensionContext,
+	config: LanguageServerConfig
+): Promise<boolean> {
 	// If the client has already been started, make sure to clear existing
 	// diagnostics and stop it.
+	const { languageClient: client, languageServerDisposable } = goCtx;
 	let cleanStop = true;
-	if (languageClient) {
-		cleanStop = await stopLanguageClient(languageClient);
+	if (client) {
+		cleanStop = await stopLanguageClient(goCtx);
 		if (languageServerDisposable) {
 			languageServerDisposable.dispose();
 		}
@@ -369,12 +355,12 @@
 	// Check if we should recreate the language client.
 	// This may be necessary if the user has changed settings
 	// in their config, or previous session wasn't stopped cleanly.
-	if (!cleanStop || !deepEqual(latestConfig, config)) {
+	if (!cleanStop || !deepEqual(goCtx.latestConfig, config)) {
 		// Track the latest config used to start the language server,
 		// and rebuild the language client.
-		latestConfig = config;
-		languageClient = await buildLanguageClient(buildLanguageClientOption(config));
-		crashCount = 0;
+		goCtx.latestConfig = config;
+		goCtx.languageClient = await buildLanguageClient(goCtx, buildLanguageClientOption(goCtx, config));
+		goCtx.crashCount = 0;
 	}
 
 	// If the user has not enabled the language server, return early.
@@ -387,6 +373,7 @@
 	if (!restartCommand) {
 		restartCommand = vscode.commands.registerCommand('go.languageserver.restart', async () => {
 			await suggestGoplsIssueReport(
+				goCtx,
 				"Looks like you're about to manually restart the language server.",
 				errorKind.manualRestart
 			);
@@ -398,15 +385,15 @@
 	// Before starting the language server, make sure to deregister any
 	// currently registered language providers.
 
-	legacyLanguageService?.dispose();
-	legacyLanguageService = undefined;
+	goCtx.legacyLanguageService?.dispose();
+	goCtx.legacyLanguageService = undefined;
 
-	languageServerDisposable = languageClient?.start();
+	goCtx.languageServerDisposable = goCtx.languageClient?.start();
 	languageServerDisposable && ctx.subscriptions.push(languageServerDisposable);
-	await languageClient?.onReady();
-	serverInfo = toServerInfo(languageClient?.initializeResult);
+	await goCtx.languageClient?.onReady();
+	goCtx.serverInfo = toServerInfo(goCtx.languageClient?.initializeResult);
 
-	console.log(`Server: ${JSON.stringify(serverInfo, null, 2)}`);
+	console.log(`Server: ${JSON.stringify(goCtx.serverInfo, null, 2)}`);
 	return true;
 }
 
@@ -419,7 +406,8 @@
 };
 
 // exported for testing.
-export async function stopLanguageClient(c: LanguageClient): Promise<boolean> {
+export async function stopLanguageClient(goCtx: GoExtensionContext): Promise<boolean> {
+	const c = goCtx.languageClient;
 	if (!c) return false;
 
 	if (c.diagnostics) {
@@ -471,20 +459,20 @@
 // buildLanguageClientOption returns the default, extra configuration
 // used in building a new LanguageClient instance. Options specified
 // in LanguageServerConfig
-function buildLanguageClientOption(cfg: LanguageServerConfig): BuildLanguageClientOption {
+function buildLanguageClientOption(goCtx: GoExtensionContext, cfg: LanguageServerConfig): BuildLanguageClientOption {
 	// Reuse the same output channel for each instance of the server.
 	if (cfg.enabled) {
-		if (!serverOutputChannel) {
-			serverOutputChannel = vscode.window.createOutputChannel(cfg.serverName + ' (server)');
+		if (!goCtx.serverOutputChannel) {
+			goCtx.serverOutputChannel = vscode.window.createOutputChannel(cfg.serverName + ' (server)');
 		}
-		if (!serverTraceChannel) {
-			serverTraceChannel = vscode.window.createOutputChannel(cfg.serverName);
+		if (!goCtx.serverTraceChannel) {
+			goCtx.serverTraceChannel = vscode.window.createOutputChannel(cfg.serverName);
 		}
 	}
 	return Object.assign(
 		{
-			outputChannel: serverOutputChannel,
-			traceOutputChannel: serverTraceChannel
+			outputChannel: goCtx.serverOutputChannel,
+			traceOutputChannel: goCtx.serverTraceChannel
 		},
 		cfg
 	);
@@ -492,7 +480,10 @@
 
 // buildLanguageClient returns a language client built using the given language server config.
 // The returned language client need to be started before use.
-export async function buildLanguageClient(cfg: BuildLanguageClientOption): Promise<LanguageClient | undefined> {
+export async function buildLanguageClient(
+	goCtx: GoExtensionContext,
+	cfg: BuildLanguageClientOption
+): Promise<LanguageClient | undefined> {
 	if (!cfg.enabled) {
 		return Promise.resolve(undefined);
 	}
@@ -532,6 +523,7 @@
 					`The language server is not able to serve any features. Initialization failed: ${error}. `
 				);
 				suggestGoplsIssueReport(
+					goCtx,
 					'The gopls server failed to initialize',
 					errorKind.initializationFailure,
 					error
@@ -551,11 +543,12 @@
 				},
 				closed: (): CloseAction => {
 					// Allow 5 crashes before shutdown.
-					crashCount++;
-					if (crashCount < 5) {
+					goCtx.crashCount++;
+					if (goCtx.crashCount < 5) {
 						return CloseAction.Restart;
 					}
 					suggestGoplsIssueReport(
+						goCtx,
 						'The connection to gopls has been closed. The gopls server may have crashed.',
 						errorKind.crash
 					);
@@ -572,7 +565,7 @@
 							'Show Trace'
 						);
 						if (answer === 'Show Trace') {
-							serverOutputChannel?.show();
+							goCtx.serverOutputChannel?.show();
 						}
 						return null;
 					}
@@ -699,19 +692,19 @@
 				// Keep track of the last file change in order to not prompt
 				// user if they are actively working.
 				didOpen: (e, next) => {
-					lastUserAction = new Date();
+					goCtx.lastUserAction = new Date();
 					next(e);
 				},
 				didChange: (e, next) => {
-					lastUserAction = new Date();
+					goCtx.lastUserAction = new Date();
 					next(e);
 				},
 				didClose: (e, next) => {
-					lastUserAction = new Date();
+					goCtx.lastUserAction = new Date();
 					next(e);
 				},
 				didSave: (e, next) => {
-					lastUserAction = new Date();
+					goCtx.lastUserAction = new Date();
 					next(e);
 				},
 				workspace: {
@@ -883,7 +876,7 @@
 	];
 }
 
-export async function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) {
+export async function watchLanguageServerConfiguration(goCtx: GoExtensionContext, e: vscode.ConfigurationChangeEvent) {
 	if (!e.affectsConfiguration('go')) {
 		return;
 	}
@@ -901,7 +894,7 @@
 	}
 
 	if (e.affectsConfiguration('go.useLanguageServer') && getGoConfig()['useLanguageServer'] === false) {
-		promptAboutGoplsOptOut();
+		promptAboutGoplsOptOut(goCtx);
 	}
 }
 
@@ -1009,9 +1002,12 @@
 
 export async function shouldUpdateLanguageServer(
 	tool: Tool,
-	cfg: LanguageServerConfig,
+	cfg?: LanguageServerConfig,
 	mustCheck?: boolean
 ): Promise<semver.SemVer | null | undefined> {
+	if (!cfg) {
+		return null;
+	}
 	// Only support updating gopls for now.
 	if (tool.name !== 'gopls' || (!mustCheck && (cfg.checkForUpdates === 'off' || extensionInfo.isInCloudIDE))) {
 		return null;
@@ -1171,7 +1167,7 @@
 // `gopls version` command.
 //
 // If this command has already been executed, it returns the saved result.
-export const getLocalGoplsVersion = async (cfg: LanguageServerConfig) => {
+export const getLocalGoplsVersion = async (cfg?: LanguageServerConfig) => {
 	if (!cfg) {
 		return null;
 	}
@@ -1290,6 +1286,7 @@
 
 // suggestGoplsIssueReport prompts users to file an issue with gopls.
 async function suggestGoplsIssueReport(
+	goCtx: GoExtensionContext,
 	msg: string,
 	reason: errorKind,
 	initializationError?: WebRequest.ResponseError<InitializeError>
@@ -1303,7 +1300,7 @@
 	// just prompt them to update, not file an issue.
 	const tool = getTool('gopls');
 	if (tool) {
-		const versionToUpdate = await shouldUpdateLanguageServer(tool, latestConfig, true);
+		const versionToUpdate = await shouldUpdateLanguageServer(tool, goCtx.latestConfig, true);
 		if (versionToUpdate) {
 			promptForUpdatingTool(tool.name, versionToUpdate, true);
 			return;
@@ -1311,9 +1308,9 @@
 	}
 
 	// Show the user the output channel content to alert them to the issue.
-	serverOutputChannel?.show();
+	goCtx.serverOutputChannel?.show();
 
-	if (latestConfig.serverName !== 'gopls') {
+	if (goCtx.latestConfig?.serverName !== 'gopls') {
 		return;
 	}
 	const promptForIssueOnGoplsRestartKey = 'promptForIssueOnGoplsRestart';
@@ -1334,7 +1331,7 @@
 		}
 	}
 
-	const { sanitizedLog, failureReason } = await collectGoplsLog();
+	const { sanitizedLog, failureReason } = await collectGoplsLog(goCtx);
 
 	// If the user has invalid values for "go.languageServerFlags", we may get
 	// this error. Prompt them to double check their flags.
@@ -1380,9 +1377,9 @@
 						break;
 				}
 				// Get the user's version in case the update prompt above failed.
-				const usersGoplsVersion = await getLocalGoplsVersion(latestConfig);
+				const usersGoplsVersion = await getLocalGoplsVersion(goCtx.latestConfig);
 				const goVersion = await getGoVersion();
-				const settings = latestConfig.flags.join(' ');
+				const settings = goCtx.latestConfig.flags.join(' ');
 				const title = `gopls: automated issue report (${errKind})`;
 				const goplsLog = sanitizedLog
 					? `<pre>${sanitizedLog}</pre>`
@@ -1399,14 +1396,14 @@
 				const body = `
 gopls version: ${usersGoplsVersion?.version} (${usersGoplsVersion?.goVersion})
 gopls flags: ${settings}
-update flags: ${latestConfig.checkForUpdates}
+update flags: ${goCtx.latestConfig.checkForUpdates}
 extension version: ${extensionInfo.version}
 go version: ${goVersion?.format(true)}
 environment: ${extensionInfo.appName} ${process.platform}
 initialization error: ${initializationError}
 issue timestamp: ${now.toUTCString()}
 restart history:
-${formatRestartHistory()}
+${formatRestartHistory(goCtx)}
 
 ATTENTION: PLEASE PROVIDE THE DETAILS REQUESTED BELOW.
 
@@ -1441,13 +1438,13 @@
 	}
 }
 
-export function showServerOutputChannel() {
-	if (!languageServerIsRunning) {
+export function showServerOutputChannel(goCtx: GoExtensionContext) {
+	if (!goCtx.languageServerIsRunning) {
 		vscode.window.showInformationMessage('gopls is not running');
 		return;
 	}
 	// likely show() is asynchronous, despite the documentation
-	serverOutputChannel?.show();
+	goCtx.serverOutputChannel?.show();
 	let found: vscode.TextDocument | undefined;
 	for (const doc of vscode.workspace.textDocuments) {
 		if (doc.fileName.indexOf('extension-output-') !== -1) {
@@ -1473,8 +1470,8 @@
 	return new Promise((resolve) => setTimeout(resolve, ms));
 }
 
-async function collectGoplsLog(): Promise<{ sanitizedLog?: string; failureReason?: string }> {
-	serverOutputChannel?.show();
+async function collectGoplsLog(goCtx: GoExtensionContext): Promise<{ sanitizedLog?: string; failureReason?: string }> {
+	goCtx.serverOutputChannel?.show();
 	// Find the logs in the output channel. There is no way to read
 	// an output channel directly, but we can find the open text
 	// document, since we just surfaced the output channel to the user.
diff --git a/src/language/legacy/goOutline.ts b/src/language/legacy/goOutline.ts
index 6c7e162..6552955 100644
--- a/src/language/legacy/goOutline.ts
+++ b/src/language/legacy/goOutline.ts
@@ -12,7 +12,7 @@
 import { getGoConfig } from '../../config';
 import { toolExecutionEnvironment } from '../../goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
-import { languageClient, serverInfo } from '../goLanguageServer';
+import { goCtx } from '../../goMain';
 import { getBinPath, getFileArchive, makeMemoizedByteOffsetConverter } from '../../util';
 import { killProcess } from '../../utils/processUtils';
 
@@ -207,6 +207,7 @@
 			this.includeImports = gotoSymbolConfig ? gotoSymbolConfig['includeImports'] : false;
 		}
 
+		const { languageClient, serverInfo } = goCtx;
 		// TODO(suzmue): Check the commands available instead of the version.
 		if (languageClient && serverInfo?.Commands?.includes(GOPLS_LIST_IMPORTS)) {
 			const symbols: vscode.DocumentSymbol[] | undefined = await vscode.commands.executeCommand(
@@ -278,6 +279,7 @@
 }
 
 async function listImports(document: vscode.TextDocument): Promise<{ Path: string; Name: string }[]> {
+	const { languageClient } = goCtx;
 	const uri = languageClient?.code2ProtocolConverter.asTextDocumentIdentifier(document).uri;
 	const params: ExecuteCommandParams = {
 		command: GOPLS_LIST_IMPORTS,
diff --git a/src/util.ts b/src/util.ts
index 91d6707..af4548b 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -15,8 +15,7 @@
 import { getGoConfig } from './config';
 import { extensionId } from './const';
 import { toolExecutionEnvironment } from './goEnv';
-import { languageClient } from './language/goLanguageServer';
-import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
+import { buildDiagnosticCollection, goCtx, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
 import { getCurrentPackage } from './goModules';
 import { outputChannel } from './goStatus';
 import { getFromWorkspaceState } from './stateUtils';
@@ -885,6 +884,7 @@
 			// If there are build errors on current file, ignore the new lint/vet warnings co-inciding with them
 			newDiagnostics = deDupeDiagnostics(buildDiagnosticCollection.get(fileUri)!.slice(), newDiagnostics);
 		}
+		const { languageClient } = goCtx;
 		// If there are errors from the language client that are on the current file, ignore the warnings co-inciding
 		// with them.
 		if (languageClient && languageClient.diagnostics?.has(fileUri)) {
diff --git a/test/gopls/extension.test.ts b/test/gopls/extension.test.ts
index 246672c..a111fcd 100644
--- a/test/gopls/extension.test.ts
+++ b/test/gopls/extension.test.ts
@@ -16,6 +16,8 @@
 } from '../../src/language/goLanguageServer';
 import sinon = require('sinon');
 import { getGoVersion, GoVersion } from '../../src/util';
+import { Mutex } from '../../src/utils/mutex';
+import { GoExtensionContext } from '../../src/context';
 
 // FakeOutputChannel is a fake output channel used to buffer
 // the output of the tested language client in an in-memory
@@ -92,6 +94,12 @@
 	}
 
 	public async setup(filePath: string) {
+		const goCtx: GoExtensionContext = {
+			lastUserAction: new Date(),
+			crashCount: 0,
+			restartHistory: [],
+			languageServerStartMutex: new Mutex()
+		};
 		// file path to open.
 		this.fakeOutputChannel = new FakeOutputChannel();
 		const pkgLoadingDone = this.onMessageInTrace('Finished loading packages.', 60_000);
@@ -104,7 +112,7 @@
 		});
 		const cfg: BuildLanguageClientOption = buildLanguageServerConfig(goConfig);
 		cfg.outputChannel = this.fakeOutputChannel; // inject our fake output channel.
-		this.languageClient = await buildLanguageClient(cfg);
+		this.languageClient = await buildLanguageClient(goCtx, cfg);
 		if (!this.languageClient) {
 			throw new Error('Language client not initialized.');
 		}
diff --git a/test/gopls/survey.test.ts b/test/gopls/survey.test.ts
index 0d9c169..128e616 100644
--- a/test/gopls/survey.test.ts
+++ b/test/gopls/survey.test.ts
@@ -9,6 +9,8 @@
 import goLanguageServer = require('../../src/language/goLanguageServer');
 import goSurvey = require('../../src/goSurvey');
 import goDeveloperSurvey = require('../../src/goDeveloperSurvey');
+import { Mutex } from '../../src/utils/mutex';
+import { GoExtensionContext } from '../../src/context';
 
 suite('gopls survey tests', () => {
 	test('prompt for survey', () => {
@@ -218,12 +220,18 @@
 
 	testCases.map(async ([testConfig, choice, wantCount], i) => {
 		test(`opt out: ${i}`, async () => {
+			const goCtx: GoExtensionContext = {
+				lastUserAction: new Date(),
+				crashCount: 0,
+				restartHistory: [],
+				languageServerStartMutex: new Mutex()
+			};
 			const stub = sandbox.stub(vscode.window, 'showInformationMessage').resolves({ title: choice });
 			const getGoplsOptOutConfigStub = sandbox.stub(goLanguageServer, 'getGoplsOptOutConfig').returns(testConfig);
 			const flushGoplsOptOutConfigStub = sandbox.stub(goLanguageServer, 'flushGoplsOptOutConfig');
 			sandbox.stub(vscode.env, 'openExternal').resolves(true);
 
-			await goLanguageServer.promptAboutGoplsOptOut();
+			await goLanguageServer.promptAboutGoplsOptOut(goCtx);
 			assert.strictEqual(stub.callCount, wantCount, 'unexpected call count');
 			sandbox.assert.called(getGoplsOptOutConfigStub);
 			sandbox.assert.calledOnce(flushGoplsOptOutConfigStub);
diff --git a/test/integration/codelens.test.ts b/test/integration/codelens.test.ts
index 10ecaab..31500ef 100644
--- a/test/integration/codelens.test.ts
+++ b/test/integration/codelens.test.ts
@@ -12,9 +12,11 @@
 import vscode = require('vscode');
 import { getGoConfig } from '../../src/config';
 import { updateGoVarsFromConfig } from '../../src/goInstallTools';
+import { GoExtensionContext } from '../../src/context';
 import { GoRunTestCodeLensProvider } from '../../src/goRunTestCodelens';
 import { subTestAtCursor } from '../../src/goTest';
 import { getCurrentGoPath, getGoVersion } from '../../src/util';
+import { Mutex } from '../../src/utils/mutex';
 
 suite('Code lenses for testing and benchmarking', function () {
 	this.timeout(20000);
@@ -31,7 +33,13 @@
 	const codeLensProvider = new GoRunTestCodeLensProvider();
 
 	suiteSetup(async () => {
-		await updateGoVarsFromConfig();
+		const goCtx: GoExtensionContext = {
+			lastUserAction: new Date(),
+			crashCount: 0,
+			restartHistory: [],
+			languageServerStartMutex: new Mutex()
+		};
+		await updateGoVarsFromConfig(goCtx);
 
 		gopath = getCurrentGoPath();
 		if (!gopath) {
diff --git a/test/integration/coverage.test.ts b/test/integration/coverage.test.ts
index 42c598f..fbbaf65 100644
--- a/test/integration/coverage.test.ts
+++ b/test/integration/coverage.test.ts
@@ -13,6 +13,8 @@
 import path = require('path');
 import sinon = require('sinon');
 import vscode = require('vscode');
+import { Mutex } from '../../src/utils/mutex';
+import { GoExtensionContext } from '../../src/context';
 
 // The ideal test would check that each open editor containing a file with coverage
 // information is displayed correctly. We cannot see the applied decorations, so the
@@ -25,7 +27,13 @@
 	let coverFilePath: string;
 
 	suiteSetup(async () => {
-		await updateGoVarsFromConfig();
+		const goCtx: GoExtensionContext = {
+			lastUserAction: new Date(),
+			crashCount: 0,
+			restartHistory: [],
+			languageServerStartMutex: new Mutex()
+		};
+		await updateGoVarsFromConfig(goCtx);
 
 		// Set up the test fixtures.
 		fixtureSourcePath = path.join(__dirname, '..', '..', '..', 'test', 'testdata', 'coverage');
diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts
index 00c72e5..574e911 100644
--- a/test/integration/extension.test.ts
+++ b/test/integration/extension.test.ts
@@ -49,6 +49,8 @@
 } from '../../src/util';
 import cp = require('child_process');
 import os = require('os');
+import { GoExtensionContext } from '../../src/context';
+import { Mutex } from '../../src/utils/mutex';
 
 const testAll = (isModuleMode: boolean) => {
 	const dummyCancellationSource = new vscode.CancellationTokenSource();
@@ -68,7 +70,13 @@
 		previousEnv = Object.assign({}, process.env);
 		process.env.GO111MODULE = isModuleMode ? 'on' : 'off';
 
-		await updateGoVarsFromConfig();
+		const goCtx: GoExtensionContext = {
+			lastUserAction: new Date(),
+			crashCount: 0,
+			restartHistory: [],
+			languageServerStartMutex: new Mutex()
+		};
+		await updateGoVarsFromConfig(goCtx);
 
 		gopath = getCurrentGoPath();
 		if (!gopath) {
diff --git a/test/integration/goDebugConfiguration.test.ts b/test/integration/goDebugConfiguration.test.ts
index 97aa335..98b5a24 100644
--- a/test/integration/goDebugConfiguration.test.ts
+++ b/test/integration/goDebugConfiguration.test.ts
@@ -14,6 +14,8 @@
 import goEnv = require('../../src/goEnv');
 import { MockCfg } from '../mocks/MockCfg';
 import { extensionId } from '../../src/const';
+import { GoExtensionContext } from '../../src/context';
+import { Mutex } from '../../src/utils/mutex';
 
 suite('Debug Environment Variable Merge Test', () => {
 	const debugConfigProvider = new GoDebugConfigurationProvider();
@@ -23,7 +25,13 @@
 	const filePath = path.join(fixtureSourcePath, 'baseTest', 'test.go');
 
 	suiteSetup(async () => {
-		await updateGoVarsFromConfig();
+		const goCtx: GoExtensionContext = {
+			lastUserAction: new Date(),
+			crashCount: 0,
+			restartHistory: [],
+			languageServerStartMutex: new Mutex()
+		};
+		await updateGoVarsFromConfig(goCtx);
 		await vscode.workspace.openTextDocument(vscode.Uri.file(filePath));
 	});
 
diff --git a/test/integration/statusbar.test.ts b/test/integration/statusbar.test.ts
index e8e8804..bf97017 100644
--- a/test/integration/statusbar.test.ts
+++ b/test/integration/statusbar.test.ts
@@ -26,11 +26,19 @@
 import { MockMemento } from '../mocks/MockMemento';
 
 import ourutil = require('../../src/util');
+import { GoExtensionContext } from '../../src/context';
 import { setGOROOTEnvVar } from '../../src/goMain';
+import { Mutex } from '../../src/utils/mutex';
 
 describe('#initGoStatusBar()', function () {
 	this.beforeAll(async () => {
-		await updateGoVarsFromConfig(); // should initialize the status bar.
+		const goCtx: GoExtensionContext = {
+			lastUserAction: new Date(),
+			crashCount: 0,
+			restartHistory: [],
+			languageServerStartMutex: new Mutex()
+		};
+		await updateGoVarsFromConfig(goCtx); // should initialize the status bar.
 	});
 
 	this.afterAll(() => {