Skip to content

Commit 910ef30

Browse files
authored
fix: bug where successive python changed events trigger creating multiple servers (#557)
1 parent bf8c067 commit 910ef30

7 files changed

+196
-176
lines changed

package-lock.json

+104-143
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/constants.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ export const EXTENSION_ROOT_DIR =
1010
export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'bundled');
1111
export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 'tool', `lsp_server.py`);
1212
export const DEBUG_SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 'tool', `_debug_server.py`);
13+
export const PYTHON_MAJOR = 3;
14+
export const PYTHON_MINOR = 8;
15+
export const PYTHON_VERSION = `${PYTHON_MAJOR}.${PYTHON_MINOR}`;
16+
export const LS_SERVER_RESTART_DELAY = 1000;

src/common/python.ts

+49-9
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
import { commands, Disposable, Event, EventEmitter, Uri } from 'vscode';
66
import { traceError, traceLog } from './logging';
77
import { PythonExtension, ResolvedEnvironment } from '@vscode/python-extension';
8+
import { PYTHON_MAJOR, PYTHON_MINOR, PYTHON_VERSION } from './constants';
9+
import { getProjectRoot } from './utilities';
810

911
export interface IInterpreterDetails {
1012
path?: string[];
1113
resource?: Uri;
1214
}
1315

14-
const onDidChangePythonInterpreterEvent = new EventEmitter<IInterpreterDetails>();
15-
export const onDidChangePythonInterpreter: Event<IInterpreterDetails> = onDidChangePythonInterpreterEvent.event;
16+
const onDidChangePythonInterpreterEvent = new EventEmitter<void>();
17+
export const onDidChangePythonInterpreter: Event<void> = onDidChangePythonInterpreterEvent.event;
1618

1719
let _api: PythonExtension | undefined;
1820
async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
@@ -23,21 +25,59 @@ async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
2325
return _api;
2426
}
2527

28+
function sameInterpreter(a: string[], b: string[]): boolean {
29+
if (a.length !== b.length) {
30+
return false;
31+
}
32+
for (let i = 0; i < a.length; i++) {
33+
if (a[i] !== b[i]) {
34+
return false;
35+
}
36+
}
37+
return true;
38+
}
39+
40+
let serverPython: string[] | undefined;
41+
function checkAndFireEvent(interpreter: string[] | undefined): void {
42+
if (interpreter === undefined) {
43+
if (serverPython) {
44+
// Python was reset for this uri
45+
serverPython = undefined;
46+
onDidChangePythonInterpreterEvent.fire();
47+
return;
48+
} else {
49+
return; // No change in interpreter
50+
}
51+
}
52+
53+
if (!serverPython || !sameInterpreter(serverPython, interpreter)) {
54+
serverPython = interpreter;
55+
onDidChangePythonInterpreterEvent.fire();
56+
}
57+
}
58+
59+
async function refreshServerPython(): Promise<void> {
60+
const projectRoot = await getProjectRoot();
61+
const interpreter = await getInterpreterDetails(projectRoot?.uri);
62+
checkAndFireEvent(interpreter.path);
63+
}
64+
2665
export async function initializePython(disposables: Disposable[]): Promise<void> {
2766
try {
2867
const api = await getPythonExtensionAPI();
68+
2969
if (api) {
3070
disposables.push(
31-
api.environments.onDidChangeActiveEnvironmentPath((e) => {
32-
onDidChangePythonInterpreterEvent.fire({ path: [e.path], resource: e.resource?.uri });
71+
api.environments.onDidChangeActiveEnvironmentPath(async () => {
72+
await refreshServerPython();
3373
}),
3474
);
3575

36-
traceLog('Waiting for interpreter from python extension.');
37-
onDidChangePythonInterpreterEvent.fire(await getInterpreterDetails());
76+
traceLog('Waiting for interpreter from Python extension.');
77+
await refreshServerPython();
3878
}
3979
} catch (error) {
40-
traceError('Error initializing python: ', error);
80+
traceError('Error initializing Python: ', error);
4181
}
4282
}
4383

@@ -69,11 +109,11 @@ export async function runPythonExtensionCommand(command: string, ...rest: any[])
69109

70110
export function checkVersion(resolved: ResolvedEnvironment | undefined): boolean {
71111
const version = resolved?.version;
72-
if (version?.major === 3 && version?.minor >= 8) {
112+
if (version?.major === PYTHON_MAJOR && version?.minor >= PYTHON_MINOR) {
73113
return true;
74114
}
75115
traceError(`Python version ${version?.major}.${version?.minor} is not supported.`);
76116
traceError(`Selected python path: ${resolved?.executable.uri?.fsPath}`);
77-
traceError('Supported versions are 3.8 and above.');
117+
traceError(`Supported versions are ${PYTHON_VERSION} and above.`);
78118
return false;
79119
}

src/common/server.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ export async function restartServer(
8080
serverId: string,
8181
serverName: string,
8282
outputChannel: LogOutputChannel,
83-
lsClient?: LanguageClient,
83+
oldLsClient?: LanguageClient,
8484
): Promise<LanguageClient | undefined> {
85-
if (lsClient) {
85+
if (oldLsClient) {
8686
traceInfo(`Server: Stop requested`);
8787
try {
88-
await lsClient.stop();
88+
await oldLsClient.stop();
8989
} catch (ex) {
9090
traceError(`Server: Stop failed: ${ex}`);
9191
}

src/common/settings.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { traceError, traceInfo, traceLog, traceWarn } from './logging';
88
import { EXTENSION_ID } from './constants';
99
import { TransportKind } from 'vscode-languageclient/node';
1010
import { trace } from 'console';
11+
import { getInterpreterFromSetting } from './utilities';
1112

1213
export interface ISettings {
1314
cwd: string;
@@ -92,11 +93,6 @@ function getCwd(config: WorkspaceConfiguration, workspace: WorkspaceFolder): str
9293
return resolveVariables([cwd], 'cwd', workspace)[0];
9394
}
9495

95-
export function getInterpreterFromSetting(namespace: string, scope?: ConfigurationScope) {
96-
const config = getConfiguration(namespace, scope);
97-
return config.get<string[]>('interpreter');
98-
}
99-
10096
export async function getWorkspaceSettings(
10197
namespace: string,
10298
workspace: WorkspaceFolder,

src/common/utilities.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
import * as fs from 'fs-extra';
55
import * as path from 'path';
6-
import { env, LogLevel, Uri, WorkspaceFolder } from 'vscode';
6+
import { ConfigurationScope, env, LogLevel, Uri, WorkspaceFolder } from 'vscode';
77
import { Trace, TraceValues } from 'vscode-jsonrpc/node';
8-
import { getWorkspaceFolders, isVirtualWorkspace } from './vscodeapi';
8+
import { getConfiguration, getWorkspaceFolders, isVirtualWorkspace } from './vscodeapi';
99
import { DocumentSelector } from 'vscode-languageclient';
1010

1111
function logLevelToTrace(logLevel: LogLevel): Trace {
@@ -77,3 +77,8 @@ export function getDocumentSelector(): DocumentSelector {
7777
{ scheme: 'vscode-notebook-cell', language: 'python' },
7878
];
7979
}
80+
81+
export function getInterpreterFromSetting(namespace: string, scope?: ConfigurationScope) {
82+
const config = getConfiguration(namespace, scope);
83+
return config.get<string[]>('interpreter');
84+
}

src/extension.ts

+28-14
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import { initializePython, onDidChangePythonInterpreter } from './common/python'
99
import {
1010
checkIfConfigurationChanged,
1111
getExtensionSettings,
12-
getInterpreterFromSetting,
1312
getWorkspaceSettings,
1413
ISettings,
1514
logDefaultFormatter,
1615
logLegacySettings,
1716
} from './common/settings';
1817
import { loadServerDefaults } from './common/setup';
19-
import { getProjectRoot } from './common/utilities';
18+
import { getInterpreterFromSetting, getProjectRoot } from './common/utilities';
2019
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from './common/vscodeapi';
2120
import { registerEmptyFormatter } from './common/nullFormatter';
2221
import { registerLanguageStatusItem, updateStatus } from './common/status';
22+
import { LS_SERVER_RESTART_DELAY, PYTHON_VERSION } from './common/constants';
2323

2424
let lsClient: LanguageClient | undefined;
2525
export async function activate(context: vscode.ExtensionContext): Promise<void> {
@@ -39,19 +39,33 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
3939
traceLog(`Module: ${serverInfo.module}`);
4040
traceVerbose(`Configuration: ${JSON.stringify(serverInfo)}`);
4141

42+
let isRestarting = false;
43+
let restartTimer: NodeJS.Timeout | undefined;
4244
const runServer = async () => {
43-
const projectRoot = await getProjectRoot();
44-
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true);
45-
if (workspaceSetting.interpreter.length === 0) {
46-
updateStatus(vscode.l10n.t('Please select a Python interpreter.'), vscode.LanguageStatusSeverity.Error);
47-
traceError(
48-
'Python interpreter missing:\r\n' +
49-
'[Option 1] Select python interpreter using the ms-python.python.\r\n' +
50-
`[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n`,
51-
'Please use Python 3.8 or greater.',
52-
);
53-
} else {
54-
lsClient = await restartServer(workspaceSetting, serverId, serverName, outputChannel, lsClient);
45+
if (isRestarting) {
46+
if (restartTimer) {
47+
clearTimeout(restartTimer);
48+
}
49+
restartTimer = setTimeout(runServer, LS_SERVER_RESTART_DELAY);
50+
return;
51+
}
52+
isRestarting = true;
53+
try {
54+
const projectRoot = await getProjectRoot();
55+
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true);
56+
if (workspaceSetting.interpreter.length === 0) {
57+
updateStatus(vscode.l10n.t('Please select a Python interpreter.'), vscode.LanguageStatusSeverity.Error);
58+
traceError(
59+
'Python interpreter missing:\r\n' +
60+
'[Option 1] Select python interpreter using the ms-python.python (select interpreter command).\r\n' +
61+
`[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n`,
62+
`Please use Python ${PYTHON_VERSION} or greater.`,
63+
);
64+
} else {
65+
lsClient = await restartServer(workspaceSetting, serverId, serverName, outputChannel, lsClient);
66+
}
67+
} finally {
68+
isRestarting = false;
5569
}
5670
};
5771

0 commit comments

Comments
 (0)