Skip to content

Commit aca76a4

Browse files
Update CHANGELOG and language server
1 parent 5cf2b49 commit aca76a4

8 files changed

Lines changed: 128 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ Document some advanced types so users may try them out and provide feedback. The
1818

1919
IDL 9.1 introduces new, command-line based progress bars. We have a first-pass of support for these progress bars inside IDL Notebooks.
2020

21+
## Unreleased
22+
23+
Changed the language server startup process to remove all files from memory after initial startup. This reduces RAM by 0.25-0.5 GB of memory after startup, depending on the volume of code in workspaces and on your path.
24+
25+
When we clean up the language server (happens every 5 minutes), we now check our in-memory cache and remove any files that haven't been accessed recently. Helps reduce overall RAM when VSCode is open for long periods at a time.
26+
27+
Changed the way that the language server sends work to the threads that parse code. The main difference is that we no longer send the parsed version of code back to the main thread which we were doing.
28+
29+
For large files, this had a significant impact on perceived performance as the worker threads could get locked serializing and de-serializing objects (also leads to more memory usage). Now, large files like slicer3.pro which are included in the IDL installation, provide auto-complete and hover help in about ~0.5 seconds instead of 5+ seconds.
30+
2131
## 4.6.1 - September 2024
2232

2333
Added layer controls to the Notebook Map.

apps/parsing-worker/src/main.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {
1313
IDLIndex,
1414
ReduceGlobals,
1515
} from '@idl/parsing/index';
16-
import { RemoveScopeDetail } from '@idl/parsing/syntax-tree';
16+
import {
17+
IParsedLightWeight,
18+
RemoveScopeDetail,
19+
} from '@idl/parsing/syntax-tree';
1720
import { IDL_TRANSLATION } from '@idl/translation';
1821
import {
1922
ChangeDetectionResponse,
@@ -181,17 +184,11 @@ client.on(
181184
/**
182185
* Clean up and return memory usage
183186
*/
184-
client.on(LSP_WORKER_THREAD_MESSAGE_LOOKUP.CLEAN_UP, async () => {
187+
client.on(LSP_WORKER_THREAD_MESSAGE_LOOKUP.CLEAN_UP, async (message) => {
188+
await WORKER_INDEX.clearParsedCache(message?.all);
185189
await WORKER_INDEX.cleanUp();
186190
});
187191

188-
/**
189-
* Listen for messages about removing tokens from our in-memory cache
190-
*/
191-
client.on(LSP_WORKER_THREAD_MESSAGE_LOOKUP.CLEAR_CACHE, async () => {
192-
await WORKER_INDEX.clearParsedCache(true);
193-
});
194-
195192
/**
196193
* Get auto complete for files we manage
197194
*/
@@ -264,11 +261,15 @@ client.on(
264261
message
265262
);
266263

267-
// make non-circular
268-
RemoveScopeDetail(parsed, cancel, true);
264+
const lightParsed: IParsedLightWeight = {
265+
disabledProblems: parsed.disabledProblems,
266+
global: parsed.global,
267+
parseProblems: parsed.parseProblems,
268+
postProcessProblems: parsed.postProcessProblems,
269+
};
269270

270271
// return
271-
return parsed;
272+
return lightParsed;
272273
}
273274
);
274275

@@ -291,8 +292,15 @@ client.on(
291292
// make non-circular
292293
RemoveScopeDetail(parsed, cancel, true);
293294

295+
const lightParsed: IParsedLightWeight = {
296+
disabledProblems: parsed.disabledProblems,
297+
global: parsed.global,
298+
parseProblems: parsed.parseProblems,
299+
postProcessProblems: parsed.postProcessProblems,
300+
};
301+
294302
// return
295-
return parsed;
303+
return lightParsed;
296304
}
297305
);
298306

@@ -476,12 +484,13 @@ client.on(
476484
cancel
477485
);
478486

479-
// make non-circular
480-
if (parsed !== undefined) {
481-
RemoveScopeDetail(parsed, cancel, true);
482-
}
483-
484-
return parsed;
487+
const lightParsed: IParsedLightWeight = {
488+
disabledProblems: parsed.disabledProblems,
489+
global: parsed.global,
490+
parseProblems: parsed.parseProblems,
491+
postProcessProblems: parsed.postProcessProblems,
492+
};
493+
return lightParsed;
485494
}
486495
);
487496

libs/parsing/index/src/lib/get-parsed/get-parsed-pro-code.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export async function GetParsedPROCode(
107107
const newPending: IGetParsedPROCodePending = {
108108
checksum,
109109
token: resp.token,
110-
promise: resp.response,
110+
promise: resp.response as any,
111111
};
112112

113113
// save new pending file
@@ -144,7 +144,7 @@ export async function GetParsedPROCode(
144144
const newPending: IGetParsedPROCodePending = {
145145
checksum,
146146
token: resp.token,
147-
promise: resp.response,
147+
promise: resp.response as any,
148148
};
149149

150150
// save new pending file

libs/parsing/index/src/lib/idl-index.class.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ export class IDLIndex {
332332
this.indexerPool.workerio.postAndReceiveMessage(
333333
ids[i],
334334
LSP_WORKER_THREAD_MESSAGE_LOOKUP.CLEAN_UP,
335-
undefined
335+
{ all: false }
336336
).response
337337
);
338338
}
@@ -1933,10 +1933,9 @@ export class IDLIndex {
19331933
await Promise.all(postProcessing);
19341934

19351935
// send message to clean up
1936-
this.indexerPool.postToAll(
1937-
LSP_WORKER_THREAD_MESSAGE_LOOKUP.CLEAN_UP,
1938-
undefined
1939-
);
1936+
this.indexerPool.postToAll(LSP_WORKER_THREAD_MESSAGE_LOOKUP.CLEAN_UP, {
1937+
all: true,
1938+
});
19401939

19411940
// save syntax problems for our file
19421941
for (let i = 0; i < postProcessing.length; i++) {

libs/parsing/index/src/lib/idl-parsed-cache.class.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
import { CancellationToken } from '@idl/cancellation-tokens';
22
import { IParsed, RemoveScopeDetail } from '@idl/parsing/syntax-tree';
33
import copy from 'fast-copy';
4+
import { performance } from 'perf_hooks';
45
import { DocumentSymbol, SemanticTokens } from 'vscode-languageserver';
56

67
import { IDL_INDEX_OPTIONS } from './idl-index.interface';
7-
8-
/**
9-
* If files have more than this many lines of code, we dont compress because
10-
* it is just too slow
11-
*/
12-
const COMPRESSION_LINE_THRESHOLD = 2000;
13-
14-
/**
15-
* Tags that we compress/uncompress
16-
*/
17-
const COMPRESS_THESE = ['tree', 'global'];
8+
import {
9+
ACCESS_EXPIRATION_MS,
10+
COMPRESS_THESE,
11+
COMPRESSION_LINE_THRESHOLD,
12+
} from './idl-parsed-cache.interface';
1813

1914
/**
2015
* The keys that we compress so we can pick out a single field
@@ -36,6 +31,11 @@ export class IDLParsedCache {
3631
*/
3732
private byFile: { [key: string]: IParsed } = {};
3833

34+
/**
35+
* Track last time we accessed teh file
36+
*/
37+
private lastAccess: { [key: string]: number } = {};
38+
3939
/**
4040
* Compress
4141
*/
@@ -96,11 +96,19 @@ export class IDLParsedCache {
9696
return parsed;
9797
}
9898

99+
/**
100+
* Last time we accessed an item in our cache
101+
*/
102+
private _trackAccess(file: string) {
103+
this.lastAccess[file] = performance.now();
104+
}
105+
99106
/**
100107
* Add parsed to the cache
101108
*/
102109
add(file: string, parsed: IParsed) {
103110
this.byFile[file] = this.compress(parsed);
111+
this._trackAccess(file);
104112
}
105113

106114
/**
@@ -115,6 +123,7 @@ export class IDLParsedCache {
115123
*/
116124
checksumMatches(file: string, checksum: string) {
117125
if (file in this.byFile) {
126+
this._trackAccess(file);
118127
return this.byFile[file].checksum === checksum;
119128
}
120129
return false;
@@ -125,8 +134,23 @@ export class IDLParsedCache {
125134
*/
126135
cleanup(all = false) {
127136
if (all) {
128-
delete this.byFile;
129137
this.byFile = {};
138+
this.lastAccess = {};
139+
} else {
140+
/** Time right now */
141+
const now = performance.now();
142+
143+
/** Current files */
144+
const keys = Object.keys(this.byFile);
145+
146+
// process all the files we have
147+
for (let i = 0; i < keys.length; i++) {
148+
if (keys[i] in this.lastAccess) {
149+
if (now - this.lastAccess[keys[i]] > ACCESS_EXPIRATION_MS) {
150+
this.remove(keys[i]);
151+
}
152+
}
153+
}
130154
}
131155
}
132156

@@ -137,6 +161,7 @@ export class IDLParsedCache {
137161
*/
138162
get(file: string): IParsed | undefined {
139163
if (file in this.byFile) {
164+
this._trackAccess(file);
140165
return this.decompress(this.byFile[file]);
141166
}
142167
return undefined;
@@ -154,6 +179,7 @@ export class IDLParsedCache {
154179
*/
155180
lines(file: string): number | undefined {
156181
if (file in this.byFile) {
182+
this._trackAccess(file);
157183
return this.byFile[file].lines;
158184
}
159185
}
@@ -163,6 +189,7 @@ export class IDLParsedCache {
163189
*/
164190
outline(file: string): DocumentSymbol[] | undefined {
165191
if (file in this.byFile) {
192+
this._trackAccess(file);
166193
return this.byFile[file].outline;
167194
}
168195
}
@@ -175,6 +202,7 @@ export class IDLParsedCache {
175202
*/
176203
semantic(file: string): SemanticTokens | undefined {
177204
if (file in this.byFile) {
205+
this._trackAccess(file);
178206
return this.byFile[file].semantic.built;
179207
}
180208
}
@@ -185,6 +213,7 @@ export class IDLParsedCache {
185213
*/
186214
text(file: string): string[] | undefined {
187215
if (file in this.byFile) {
216+
this._trackAccess(file);
188217
return this.byFile[file].text;
189218
}
190219
}
@@ -194,6 +223,7 @@ export class IDLParsedCache {
194223
*/
195224
remove(file: string) {
196225
delete this.byFile[file];
226+
delete this.lastAccess[file];
197227
}
198228

199229
/**
@@ -203,6 +233,7 @@ export class IDLParsedCache {
203233
*/
204234
uses(file: string) {
205235
if (file in this.byFile) {
236+
this._trackAccess(file);
206237
return this.byFile[file].uses;
207238
}
208239
}
@@ -213,6 +244,7 @@ export class IDLParsedCache {
213244
*/
214245
updateProblems(file: string, parsed: IParsed) {
215246
if (file in this.byFile) {
247+
this._trackAccess(file);
216248
this.byFile[file].parseProblems = parsed.parseProblems;
217249
this.byFile[file].postProcessProblems = parsed.postProcessProblems;
218250
}
@@ -223,6 +255,7 @@ export class IDLParsedCache {
223255
*/
224256
updateSemantic(file: string, parsed: IParsed) {
225257
if (file in this.byFile) {
258+
this._trackAccess(file);
226259
this.byFile[file].semantic = parsed.semantic;
227260
}
228261
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* If files have more than this many lines of code, we dont compress because
3+
* it is just too slow
4+
*/
5+
export const COMPRESSION_LINE_THRESHOLD = 2000;
6+
7+
/**
8+
* Tags that we compress/decompress
9+
*/
10+
export const COMPRESS_THESE = ['tree', 'global'];
11+
12+
/**
13+
* How long (ms) before we remove items from our cache without
14+
* accessing it
15+
*/
16+
export const ACCESS_EXPIRATION_MS = 300000;

libs/parsing/syntax-tree/src/lib/parsed.interface.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,24 @@ import { ILocalTokens } from './populators/populate-local.interface';
1616
*/
1717
export type ParsedType = 'def' | 'notebook' | 'pro';
1818

19+
/**
20+
* Lightweight parsed response for advanced use cases
21+
*/
22+
export interface IParsedLightWeight {
23+
/** What problems are disabled? */
24+
disabledProblems: IDisabledProblems;
25+
/** Problems found within code from basic parsing */
26+
parseProblems: SyntaxProblems;
27+
/** Problems from post processing (type errors) which get reset every time we post-process */
28+
postProcessProblems: SyntaxProblems;
29+
/** Global tokens that we want to find in other places */
30+
global: GlobalTokens;
31+
}
32+
1933
/**
2034
* Data structure for parsed code
2135
*/
22-
export interface IParsed extends IFoundTokens {
36+
export interface IParsed extends IFoundTokens, IParsedLightWeight {
2337
/**
2438
* type of parsed code
2539
*/
@@ -37,16 +51,8 @@ export interface IParsed extends IFoundTokens {
3751
* If we have set a token cache or not
3852
*/
3953
hasCache: boolean;
40-
/** What problems are disabled? */
41-
disabledProblems: IDisabledProblems;
42-
/** Problems found within code from basic parsing */
43-
parseProblems: SyntaxProblems;
44-
/** Problems from post processing (type errors) which get reset every time we post-process */
45-
postProcessProblems: SyntaxProblems;
4654
/** Tokens converted into syntax tree */
4755
tree: SyntaxTree;
48-
/** Global tokens that we want to find in other places */
49-
global: GlobalTokens;
5056
/** Local tokens that we have found */
5157
local: ILocalTokens;
5258
/** Compile options by routine */

0 commit comments

Comments
 (0)