Skip to content

Commit f29f747

Browse files
Merge pull request #402 from laravel/memory-improvements
Memory improvements
2 parents 9a01a2c + 2942241 commit f29f747

File tree

5 files changed

+151
-12
lines changed

5 files changed

+151
-12
lines changed

src/extension.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ import {
2626
watchForComposerChanges,
2727
} from "./support/fileWatcher";
2828
import { info } from "./support/logger";
29-
import { setParserBinaryPath } from "./support/parser";
30-
import { clearDefaultPhpCommand, initVendorWatchers } from "./support/php";
29+
import { clearParserCaches, setParserBinaryPath } from "./support/parser";
30+
import {
31+
clearDefaultPhpCommand,
32+
clearPhpFileCache,
33+
initVendorWatchers,
34+
} from "./support/php";
3135
import { hasWorkspace, projectPathExists } from "./support/project";
3236
import { cleanUpTemp } from "./support/util";
3337

@@ -185,6 +189,8 @@ export function deactivate() {
185189
}
186190

187191
disposeWatchers();
192+
clearParserCaches();
193+
clearPhpFileCache();
188194

189195
if (client) {
190196
client.stop();

src/support/cache.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import * as fs from "fs";
2+
3+
export class Cache<K, V> {
4+
private maxSize: number;
5+
private cache: Map<K, V>;
6+
7+
constructor(maxSize: number) {
8+
this.maxSize = maxSize;
9+
this.cache = new Map();
10+
}
11+
12+
get(key: K): V | undefined {
13+
const value = this.cache.get(key);
14+
15+
if (value !== undefined) {
16+
this.cache.delete(key);
17+
this.cache.set(key, value);
18+
}
19+
20+
return value;
21+
}
22+
23+
set(key: K, value: V): void {
24+
if (this.cache.has(key)) {
25+
this.cache.delete(key);
26+
} else if (this.cache.size >= this.maxSize) {
27+
const firstKey = this.cache.keys().next().value;
28+
29+
if (firstKey !== undefined) {
30+
this.cache.delete(firstKey);
31+
}
32+
}
33+
34+
this.cache.set(key, value);
35+
}
36+
37+
has(key: K): boolean {
38+
return this.cache.has(key);
39+
}
40+
41+
clear(): void {
42+
this.cache.clear();
43+
}
44+
45+
size(): number {
46+
return this.cache.size;
47+
}
48+
}
49+
50+
export class BoundedFileCache {
51+
private cache = new Map<string, string>();
52+
private maxSize: number;
53+
54+
constructor(maxSize: number) {
55+
this.maxSize = maxSize;
56+
}
57+
58+
get(key: string): string | undefined {
59+
return this.cache.get(key);
60+
}
61+
62+
set(key: string, filePath: string): void {
63+
if (this.cache.has(key)) {
64+
return;
65+
}
66+
67+
if (this.cache.size >= this.maxSize) {
68+
const oldestKey = this.cache.keys().next().value;
69+
if (oldestKey !== undefined) {
70+
const oldFilePath = this.cache.get(oldestKey);
71+
if (oldFilePath && fs.existsSync(oldFilePath)) {
72+
try {
73+
fs.unlinkSync(oldFilePath);
74+
} catch (e) {
75+
// File might already be deleted, ignore
76+
}
77+
}
78+
this.cache.delete(oldestKey);
79+
}
80+
}
81+
82+
this.cache.set(key, filePath);
83+
}
84+
85+
has(key: string): boolean {
86+
return this.cache.has(key);
87+
}
88+
89+
delete(key: string): void {
90+
const filePath = this.cache.get(key);
91+
92+
if (filePath && fs.existsSync(filePath)) {
93+
try {
94+
fs.unlinkSync(filePath);
95+
} catch (e) {
96+
// File might already be deleted, ignore
97+
}
98+
}
99+
100+
this.cache.delete(key);
101+
}
102+
103+
clear(): void {
104+
for (const [key] of this.cache.entries()) {
105+
this.delete(key);
106+
}
107+
108+
this.cache.clear();
109+
}
110+
111+
size(): number {
112+
return this.cache.size;
113+
}
114+
115+
deleteByFilePath(filePath: string): void {
116+
for (const [key, value] of this.cache.entries()) {
117+
if (value === filePath) {
118+
this.cache.delete(key);
119+
break;
120+
}
121+
}
122+
}
123+
}

src/support/fileWatcher.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,8 @@ export const registerWatcher = (watcher: vscode.FileSystemWatcher) => {
175175
export const disposeWatchers = () => {
176176
watchers.forEach((watcher) => watcher.dispose());
177177
watchers = [];
178+
179+
for (const pattern in patternWatchers) {
180+
delete patternWatchers[pattern];
181+
}
178182
};

src/support/parser.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ import * as fs from "fs";
99
import * as os from "os";
1010
import * as vscode from "vscode";
1111
import { FeatureTag, ValidDetectParamTypes } from "..";
12+
import { Cache } from "./cache";
1213
import { showErrorPopup } from "./popup";
1314
import { md5, tempPath, toArray } from "./util";
1415

15-
const currentlyParsing = new Map<string, Promise<AutocompleteResult>>();
16-
const detected = new Map<
16+
const currentlyParsing = new Cache<string, Promise<AutocompleteResult>>(100);
17+
const detected = new Cache<
1718
string,
1819
Promise<AutocompleteParsingResult.ContextValue[]>
19-
>();
20+
>(50);
21+
22+
export const clearParserCaches = (): void => {
23+
currentlyParsing.clear();
24+
detected.clear();
25+
};
2026

2127
type TokenFormatted = [string, string, number];
2228
type Token = string | TokenFormatted;

src/support/php.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getTemplate, TemplateName } from "@src/templates";
22
import * as cp from "child_process";
33
import * as fs from "fs";
44
import * as vscode from "vscode";
5+
import { BoundedFileCache } from "./cache";
56
import { config, PhpEnvironment } from "./config";
67
import { registerWatcher } from "./fileWatcher";
78
import { error, info } from "./logger";
@@ -24,7 +25,7 @@ const toTemplateVar = (str: string) => {
2425

2526
let defaultPhpCommand: string | null = null;
2627

27-
const discoverFiles = new Map<string, string>();
28+
const discoverFiles = new BoundedFileCache(50);
2829

2930
let hasVendor = projectPathExists("vendor/autoload.php");
3031
const hasBootstrap = projectPathExists("bootstrap/app.php");
@@ -46,12 +47,7 @@ export const initVendorWatchers = () => {
4647
);
4748

4849
watcher.onDidDelete((file) => {
49-
for (const [key, value] of discoverFiles) {
50-
if (value === file.fsPath) {
51-
discoverFiles.delete(key);
52-
break;
53-
}
54-
}
50+
discoverFiles.deleteByFilePath(file.fsPath);
5551
});
5652

5753
[internalVendorPath(), projectPath("vendor")].forEach((path) => {
@@ -173,6 +169,10 @@ export const clearDefaultPhpCommand = () => {
173169
defaultPhpCommand = null;
174170
};
175171

172+
export const clearPhpFileCache = () => {
173+
discoverFiles.clear();
174+
};
175+
176176
const getDefaultPhpCommand = (): string => {
177177
defaultPhpCommand ??= getPhpCommand();
178178

0 commit comments

Comments
 (0)