Skip to content

Commit 966fb03

Browse files
committed
fix(editor): fix memory leaks when server or watchers restarted (#10628)
relates to #10627
1 parent 723b4c6 commit 966fb03

File tree

1 file changed

+37
-14
lines changed

1 file changed

+37
-14
lines changed

editors/vscode/client/extension.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { promises as fsPromises } from 'node:fs';
33
import {
44
commands,
55
ExtensionContext,
6+
FileSystemWatcher,
67
RelativePattern,
78
StatusBarAlignment,
89
StatusBarItem,
@@ -38,6 +39,8 @@ let client: LanguageClient | undefined;
3839

3940
let myStatusBarItem: StatusBarItem;
4041

42+
const globalWatchers: FileSystemWatcher[] = [];
43+
4144
export async function activate(context: ExtensionContext) {
4245
const configService = new ConfigService();
4346
const restartCommand = commands.registerCommand(
@@ -113,16 +116,23 @@ export async function activate(context: ExtensionContext) {
113116
},
114117
);
115118

119+
const outputChannel = window.createOutputChannel(outputChannelName, { log: true });
120+
const fileWatchers = createFileEventWatchers(configService.rootServerConfig.configPath);
121+
116122
context.subscriptions.push(
117123
applyAllFixesFile,
118124
restartCommand,
119125
showOutputCommand,
120126
toggleEnable,
121127
configService,
128+
outputChannel,
129+
{
130+
dispose: () => {
131+
globalWatchers.forEach((watcher) => watcher.dispose());
132+
},
133+
},
122134
);
123135

124-
const outputChannel = window.createOutputChannel(outputChannelName, { log: true });
125-
126136
async function findBinary(): Promise<string> {
127137
let bin = configService.vsCodeConfig.binPath;
128138
if (bin) {
@@ -178,9 +188,6 @@ export async function activate(context: ExtensionContext) {
178188
debug: run,
179189
};
180190

181-
const fileWatchers = createFileEventWatchers(configService.rootServerConfig.configPath);
182-
context.subscriptions.push(...fileWatchers);
183-
184191
// If the extension is launched in debug mode then the debug server options are used
185192
// Otherwise the run options are used
186193
// Options to control the language client
@@ -214,7 +221,8 @@ export async function activate(context: ExtensionContext) {
214221
serverOptions,
215222
clientOptions,
216223
);
217-
client.onNotification(ShowMessageNotification.type, (params) => {
224+
225+
const onNotificationDispose = client.onNotification(ShowMessageNotification.type, (params) => {
218226
switch (params.type) {
219227
case MessageType.Debug:
220228
outputChannel.debug(params.message);
@@ -236,12 +244,16 @@ export async function activate(context: ExtensionContext) {
236244
}
237245
});
238246

239-
workspace.onDidDeleteFiles((event) => {
247+
context.subscriptions.push(onNotificationDispose);
248+
249+
const onDeleteFilesDispose = workspace.onDidDeleteFiles((event) => {
240250
for (const fileUri of event.files) {
241251
client?.diagnostics?.delete(fileUri);
242252
}
243253
});
244254

255+
context.subscriptions.push(onDeleteFilesDispose);
256+
245257
configService.onConfigChange = async function onConfigChange(event) {
246258
let settings = this.rootServerConfig.toLanguageServerConfig();
247259
updateStatsBar(this.vsCodeConfig.enable);
@@ -261,7 +273,7 @@ export async function activate(context: ExtensionContext) {
261273
await client.restart();
262274
}
263275
} else if (client.isRunning()) {
264-
client.sendNotification('workspace/didChangeConfiguration', { settings });
276+
await client.sendNotification('workspace/didChangeConfiguration', { settings });
265277
}
266278
};
267279

@@ -285,6 +297,7 @@ export async function activate(context: ExtensionContext) {
285297
myStatusBarItem.backgroundColor = bgColor;
286298
}
287299

300+
updateStatsBar(configService.vsCodeConfig.enable);
288301
if (configService.vsCodeConfig.enable) {
289302
await client.start();
290303
}
@@ -299,15 +312,25 @@ export async function deactivate(): Promise<void> {
299312
}
300313

301314
// FileSystemWatcher are not ready on the start and can take some seconds on bigger repositories
302-
// ToDo: create test to make sure this will never break
303-
function createFileEventWatchers(configRelativePath: string | null) {
315+
function createFileEventWatchers(configRelativePath: string | null): FileSystemWatcher[] {
316+
// cleanup old watchers
317+
globalWatchers.forEach((watcher) => watcher.dispose());
318+
globalWatchers.length = 0;
319+
320+
// create new watchers
321+
let localWatchers;
304322
if (configRelativePath !== null) {
305-
return (workspace.workspaceFolders || []).map((workspaceFolder) =>
323+
localWatchers = (workspace.workspaceFolders || []).map((workspaceFolder) =>
306324
workspace.createFileSystemWatcher(new RelativePattern(workspaceFolder, configRelativePath))
307325
);
326+
} else {
327+
localWatchers = [
328+
workspace.createFileSystemWatcher(`**/${oxlintConfigFileName}`),
329+
];
308330
}
309331

310-
return [
311-
workspace.createFileSystemWatcher(`**/${oxlintConfigFileName}`),
312-
];
332+
// assign watchers to global variable, so we can cleanup them on next call
333+
globalWatchers.push(...localWatchers);
334+
335+
return localWatchers;
313336
}

0 commit comments

Comments
 (0)