Skip to content

Commit df2493d

Browse files
committed
vscode: fix memory leak on server restart centralize disposing
The memory leak was because on the server restrart the array of extensionContext.substiptions was not cleared
1 parent 0bdf000 commit df2493d

File tree

6 files changed

+57
-37
lines changed

6 files changed

+57
-37
lines changed

editors/code/src/ctx.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ import * as lc from 'vscode-languageclient';
33

44
import { Config } from './config';
55
import { createClient } from './client';
6-
import { isRustEditor, RustEditor } from './util';
6+
import { isRustEditor, RustEditor, dispose, Disposable } from './util';
77

88
export class Ctx {
99
private constructor(
1010
readonly config: Config,
1111
private readonly extCtx: vscode.ExtensionContext,
1212
readonly client: lc.LanguageClient,
1313
readonly serverPath: string,
14-
) {
14+
) { }
1515

16+
async dispose() {
17+
dispose(this.extCtx.subscriptions);
18+
await this.client.stop();
1619
}
1720

1821
static async create(config: Config, extCtx: vscode.ExtensionContext, serverPath: string): Promise<Ctx> {
@@ -54,7 +57,4 @@ export class Ctx {
5457
}
5558
}
5659

57-
export interface Disposable {
58-
dispose(): void;
59-
}
6060
export type Cmd = (...args: any[]) => unknown;

editors/code/src/highlighting.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as ra from './rust-analyzer-api';
44
import { ColorTheme, TextMateRuleSettings } from './color_theme';
55

66
import { Ctx } from './ctx';
7-
import { sendRequestWithRetry, isRustDocument } from './util';
7+
import { sendRequestWithRetry, isRustDocument, dispose } from './util';
88

99
export function activateHighlighting(ctx: Ctx) {
1010
const highlighter = new Highlighter(ctx);
@@ -83,10 +83,7 @@ class Highlighter {
8383
}
8484

8585
// Decorations are removed when the object is disposed
86-
for (const decoration of this.decorations.values()) {
87-
decoration.dispose();
88-
}
89-
86+
dispose(this.decorations);
9087
this.decorations = null;
9188
}
9289

editors/code/src/inlay_hints.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@ import * as lc from "vscode-languageclient";
22
import * as vscode from 'vscode';
33
import * as ra from './rust-analyzer-api';
44

5-
import { Ctx, Disposable } from './ctx';
6-
import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor } from './util';
5+
import { Ctx } from './ctx';
6+
import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor, dispose, Disposable } from './util';
77

88

99
export function activateInlayHints(ctx: Ctx) {
1010
const maybeUpdater = {
11-
updater: null as null | HintsUpdater,
11+
updater: undefined as undefined | HintsUpdater,
1212
onConfigChange() {
1313
if (
1414
!ctx.config.inlayHints.typeHints &&
1515
!ctx.config.inlayHints.parameterHints &&
1616
!ctx.config.inlayHints.chainingHints
1717
) {
18-
return this.dispose();
18+
return dispose(this);
1919
}
2020
if (!this.updater) this.updater = new HintsUpdater(ctx);
2121
},
2222
dispose() {
23-
this.updater?.dispose();
24-
this.updater = null;
23+
dispose(this.updater);
24+
this.updater = undefined;
2525
}
2626
};
2727

@@ -115,8 +115,10 @@ class HintsUpdater implements Disposable {
115115

116116
dispose() {
117117
this.sourceFiles.forEach(file => file.inlaysRequest?.cancel());
118-
this.ctx.visibleRustEditors.forEach(editor => this.renderDecorations(editor, { param: [], type: [], chaining: [] }));
119-
this.disposables.forEach(d => d.dispose());
118+
this.ctx.visibleRustEditors.forEach(
119+
editor => this.renderDecorations(editor, { param: [], type: [], chaining: [] })
120+
);
121+
dispose(this.disposables);
120122
}
121123

122124
onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) {

editors/code/src/main.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { activateStatusDisplay } from './status_display';
99
import { Ctx } from './ctx';
1010
import { activateHighlighting } from './highlighting';
1111
import { Config, NIGHTLY_TAG } from './config';
12-
import { log, assert } from './util';
12+
import { log, assert, dispose } from './util';
1313
import { PersistentState } from './persistent_state';
1414
import { fetchRelease, download } from './net';
1515
import { spawnSync } from 'child_process';
@@ -48,21 +48,12 @@ export async function activate(context: vscode.ExtensionContext) {
4848
ctx = await Ctx.create(config, context, serverPath);
4949

5050
// Commands which invokes manually via command palette, shortcut, etc.
51-
ctx.registerCommand('reload', (ctx) => {
52-
return async () => {
53-
vscode.window.showInformationMessage('Reloading rust-analyzer...');
54-
// @DanTup maneuver
55-
// https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895
56-
await deactivate();
57-
for (const sub of ctx.subscriptions) {
58-
try {
59-
sub.dispose();
60-
} catch (e) {
61-
log.error(e);
62-
}
63-
}
64-
await activate(context);
65-
};
51+
52+
// Reloading is inspired by @DanTup maneuver: https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895
53+
ctx.registerCommand('reload', _ => () => {
54+
void vscode.window.showInformationMessage('Reloading rust-analyzer...');
55+
56+
deactivate().then(() => activate(context)).catch(log.error);
6657
});
6758

6859
ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
@@ -74,7 +65,7 @@ export async function activate(context: vscode.ExtensionContext) {
7465
ctx.registerCommand('expandMacro', commands.expandMacro);
7566
ctx.registerCommand('run', commands.run);
7667

77-
defaultOnEnter.dispose();
68+
dispose(defaultOnEnter);
7869
ctx.registerCommand('onEnter', commands.onEnter);
7970

8071
ctx.registerCommand('ssr', commands.ssr);
@@ -96,7 +87,7 @@ export async function activate(context: vscode.ExtensionContext) {
9687
}
9788

9889
export async function deactivate() {
99-
await ctx?.client?.stop();
90+
await ctx?.dispose();
10091
ctx = undefined;
10192
}
10293

editors/code/src/status_display.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as vscode from 'vscode';
33
import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient';
44

55
import { Ctx } from './ctx';
6+
import { dispose } from './util';
67

78
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
89

@@ -64,7 +65,7 @@ class StatusDisplay implements Disposable {
6465
this.timer = undefined;
6566
}
6667

67-
this.statusBarItem.dispose();
68+
dispose(this.statusBarItem);
6869
}
6970

7071
refreshLabel() {

editors/code/src/util.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,32 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD
8282
export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
8383
return isRustDocument(editor.document);
8484
}
85+
86+
87+
export interface Disposable {
88+
dispose(): void;
89+
}
90+
91+
92+
export function dispose(disposable: undefined | Disposable | Disposable[] | Map<unknown, Disposable>) {
93+
if (disposable === undefined) return;
94+
95+
if (disposable instanceof Array) {
96+
while (disposable.length > 0) {
97+
dispose(disposable.pop());
98+
}
99+
return;
100+
}
101+
102+
if (disposable instanceof Map) {
103+
disposable.forEach(dispose);
104+
disposable.clear();
105+
return;
106+
}
107+
108+
try {
109+
disposable.dispose();
110+
} catch (err) {
111+
log.error("Error was thrown while disposing an object", err);
112+
}
113+
}

0 commit comments

Comments
 (0)