Skip to content

Commit b0d6d34

Browse files
authored
Nonresponsive editor with large project when typescript.tsserver.watchOptions: vscode (fix #237351) (#237459)
1 parent 9b0b13d commit b0d6d34

File tree

4 files changed

+29
-55
lines changed

4 files changed

+29
-55
lines changed

src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts

+11-24
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { BaseWatcher } from '../baseWatcher.js';
99
import { isLinux } from '../../../../../base/common/platform.js';
1010
import { INonRecursiveWatchRequest, INonRecursiveWatcher, IRecursiveWatcherWithSubscribe } from '../../../common/watcher.js';
1111
import { NodeJSFileWatcherLibrary } from './nodejsWatcherLib.js';
12-
import { isEqual } from '../../../../../base/common/extpath.js';
1312

1413
export interface INodeJSWatcherInstance {
1514

@@ -28,7 +27,8 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
2827

2928
readonly onDidError = Event.None;
3029

31-
readonly watchers = new Set<INodeJSWatcherInstance>();
30+
private readonly _watchers = new Map<string /* path */ | number /* correlation ID */, INodeJSWatcherInstance>();
31+
get watchers() { return this._watchers.values(); }
3232

3333
constructor(protected readonly recursiveWatcher: IRecursiveWatcherWithSubscribe | undefined) {
3434
super();
@@ -43,7 +43,7 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
4343
const requestsToStart: INonRecursiveWatchRequest[] = [];
4444
const watchersToStop = new Set(Array.from(this.watchers));
4545
for (const request of requests) {
46-
const watcher = this.findWatcher(request);
46+
const watcher = this._watchers.get(this.requestToWatcherKey(request));
4747
if (watcher && patternsEquals(watcher.request.excludes, request.excludes) && patternsEquals(watcher.request.includes, request.includes)) {
4848
watchersToStop.delete(watcher); // keep watcher
4949
} else {
@@ -72,25 +72,12 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
7272
}
7373
}
7474

75-
private findWatcher(request: INonRecursiveWatchRequest): INodeJSWatcherInstance | undefined {
76-
for (const watcher of this.watchers) {
77-
78-
// Requests or watchers with correlation always match on that
79-
if (typeof request.correlationId === 'number' || typeof watcher.request.correlationId === 'number') {
80-
if (watcher.request.correlationId === request.correlationId) {
81-
return watcher;
82-
}
83-
}
84-
85-
// Non-correlated requests or watchers match on path
86-
else {
87-
if (isEqual(watcher.request.path, request.path, !isLinux /* ignorecase */)) {
88-
return watcher;
89-
}
90-
}
91-
}
75+
private requestToWatcherKey(request: INonRecursiveWatchRequest): string | number {
76+
return typeof request.correlationId === 'number' ? request.correlationId : this.pathToWatcherKey(request.path);
77+
}
9278

93-
return undefined;
79+
private pathToWatcherKey(path: string): string {
80+
return isLinux ? path : path.toLowerCase() /* ignore path casing */;
9481
}
9582

9683
private startWatching(request: INonRecursiveWatchRequest): void {
@@ -100,7 +87,7 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
10087

10188
// Remember as watcher instance
10289
const watcher: INodeJSWatcherInstance = { request, instance };
103-
this.watchers.add(watcher);
90+
this._watchers.set(this.requestToWatcherKey(request), watcher);
10491
}
10592

10693
override async stop(): Promise<void> {
@@ -114,7 +101,7 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
114101
private stopWatching(watcher: INodeJSWatcherInstance): void {
115102
this.trace(`stopping file watcher`, watcher);
116103

117-
this.watchers.delete(watcher);
104+
this._watchers.delete(this.requestToWatcherKey(watcher.request));
118105

119106
watcher.instance.dispose();
120107
}
@@ -124,14 +111,14 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
124111

125112
// Ignore requests for the same paths that have the same correlation
126113
for (const request of requests) {
127-
const path = isLinux ? request.path : request.path.toLowerCase(); // adjust for case sensitivity
128114

129115
let requestsForCorrelation = mapCorrelationtoRequests.get(request.correlationId);
130116
if (!requestsForCorrelation) {
131117
requestsForCorrelation = new Map<string, INonRecursiveWatchRequest>();
132118
mapCorrelationtoRequests.set(request.correlationId, requestsForCorrelation);
133119
}
134120

121+
const path = this.pathToWatcherKey(request.path);
135122
if (requestsForCorrelation.has(path)) {
136123
this.trace(`ignoring a request for watching who's path is already watched: ${this.requestToString(request)}`);
137124
}

src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts

+12-24
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
157157
private readonly _onDidError = this._register(new Emitter<IWatcherErrorEvent>());
158158
readonly onDidError = this._onDidError.event;
159159

160-
readonly watchers = new Set<ParcelWatcherInstance>();
160+
private readonly _watchers = new Map<string /* path */ | number /* correlation ID */, ParcelWatcherInstance>();
161+
get watchers() { return this._watchers.values(); }
161162

162163
// A delay for collecting file changes from Parcel
163164
// before collecting them for coalescing and emitting.
@@ -206,7 +207,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
206207
const requestsToStart: IRecursiveWatchRequest[] = [];
207208
const watchersToStop = new Set(Array.from(this.watchers));
208209
for (const request of requests) {
209-
const watcher = this.findWatcher(request);
210+
const watcher = this._watchers.get(this.requestToWatcherKey(request));
210211
if (watcher && patternsEquals(watcher.request.excludes, request.excludes) && patternsEquals(watcher.request.includes, request.includes) && watcher.request.pollingInterval === request.pollingInterval) {
211212
watchersToStop.delete(watcher); // keep watcher
212213
} else {
@@ -238,25 +239,12 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
238239
}
239240
}
240241

241-
private findWatcher(request: IRecursiveWatchRequest): ParcelWatcherInstance | undefined {
242-
for (const watcher of this.watchers) {
243-
244-
// Requests or watchers with correlation always match on that
245-
if (this.isCorrelated(request) || this.isCorrelated(watcher.request)) {
246-
if (watcher.request.correlationId === request.correlationId) {
247-
return watcher;
248-
}
249-
}
250-
251-
// Non-correlated requests or watchers match on path
252-
else {
253-
if (isEqual(watcher.request.path, request.path, !isLinux /* ignorecase */)) {
254-
return watcher;
255-
}
256-
}
257-
}
242+
private requestToWatcherKey(request: IRecursiveWatchRequest): string | number {
243+
return typeof request.correlationId === 'number' ? request.correlationId : this.pathToWatcherKey(request.path);
244+
}
258245

259-
return undefined;
246+
private pathToWatcherKey(path: string): string {
247+
return isLinux ? path : path.toLowerCase() /* ignore path casing */;
260248
}
261249

262250
private startPolling(request: IRecursiveWatchRequest, pollingInterval: number, restarts = 0): void {
@@ -283,7 +271,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
283271
unlinkSync(snapshotFile);
284272
}
285273
);
286-
this.watchers.add(watcher);
274+
this._watchers.set(this.requestToWatcherKey(request), watcher);
287275

288276
// Path checks for symbolic links / wrong casing
289277
const { realPath, realPathDiffers, realPathLength } = this.normalizePath(request);
@@ -352,7 +340,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
352340
await watcherInstance?.unsubscribe();
353341
}
354342
);
355-
this.watchers.add(watcher);
343+
this._watchers.set(this.requestToWatcherKey(request), watcher);
356344

357345
// Path checks for symbolic links / wrong casing
358346
const { realPath, realPathDiffers, realPathLength } = this.normalizePath(request);
@@ -643,7 +631,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
643631
private async stopWatching(watcher: ParcelWatcherInstance, joinRestart?: Promise<void>): Promise<void> {
644632
this.trace(`stopping file watcher`, watcher);
645633

646-
this.watchers.delete(watcher);
634+
this._watchers.delete(this.requestToWatcherKey(watcher.request));
647635

648636
try {
649637
await watcher.stop(joinRestart);
@@ -666,14 +654,14 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS
666654
continue; // path is ignored entirely (via `**` glob exclude)
667655
}
668656

669-
const path = isLinux ? request.path : request.path.toLowerCase(); // adjust for case sensitivity
670657

671658
let requestsForCorrelation = mapCorrelationtoRequests.get(request.correlationId);
672659
if (!requestsForCorrelation) {
673660
requestsForCorrelation = new Map<string, IRecursiveWatchRequest>();
674661
mapCorrelationtoRequests.set(request.correlationId, requestsForCorrelation);
675662
}
676663

664+
const path = this.pathToWatcherKey(request.path);
677665
if (requestsForCorrelation.has(path)) {
678666
this.trace(`ignoring a request for watching who's path is already watched: ${this.requestToString(request)}`);
679667
}

src/vs/platform/files/node/watcher/watcherStats.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export function computeStats(
3434
lines.push('[Summary]');
3535
lines.push(`- Recursive Requests: total: ${allRecursiveRequests.length}, suspended: ${recursiveRequestsStatus.suspended}, polling: ${recursiveRequestsStatus.polling}, failed: ${failedRecursiveRequests}`);
3636
lines.push(`- Non-Recursive Requests: total: ${allNonRecursiveRequests.length}, suspended: ${nonRecursiveRequestsStatus.suspended}, polling: ${nonRecursiveRequestsStatus.polling}`);
37-
lines.push(`- Recursive Watchers: total: ${recursiveWatcher.watchers.size}, active: ${recursiveWatcherStatus.active}, failed: ${recursiveWatcherStatus.failed}, stopped: ${recursiveWatcherStatus.stopped}`);
38-
lines.push(`- Non-Recursive Watchers: total: ${nonRecursiveWatcher.watchers.size}, active: ${nonRecursiveWatcherStatus.active}, failed: ${nonRecursiveWatcherStatus.failed}, reusing: ${nonRecursiveWatcherStatus.reusing}`);
37+
lines.push(`- Recursive Watchers: total: ${Array.from(recursiveWatcher.watchers).length}, active: ${recursiveWatcherStatus.active}, failed: ${recursiveWatcherStatus.failed}, stopped: ${recursiveWatcherStatus.stopped}`);
38+
lines.push(`- Non-Recursive Watchers: total: ${Array.from(nonRecursiveWatcher.watchers).length}, active: ${nonRecursiveWatcherStatus.active}, failed: ${nonRecursiveWatcherStatus.failed}, reusing: ${nonRecursiveWatcherStatus.reusing}`);
3939
lines.push(`- I/O Handles Impact: total: ${recursiveRequestsStatus.polling + nonRecursiveRequestsStatus.polling + recursiveWatcherStatus.active + nonRecursiveWatcherStatus.active}`);
4040

4141
lines.push(`\n[Recursive Requests (${allRecursiveRequests.length}, suspended: ${recursiveRequestsStatus.suspended}, polling: ${recursiveRequestsStatus.polling})]:`);
@@ -106,7 +106,7 @@ function computeRecursiveWatchStatus(recursiveWatcher: ParcelWatcher): { active:
106106
let failed = 0;
107107
let stopped = 0;
108108

109-
for (const watcher of recursiveWatcher.watchers.values()) {
109+
for (const watcher of recursiveWatcher.watchers) {
110110
if (!watcher.failed && !watcher.stopped) {
111111
active++;
112112
}
@@ -189,7 +189,7 @@ function requestDetailsToString(request: IUniversalWatchRequest): string {
189189
}
190190

191191
function fillRecursiveWatcherStats(lines: string[], recursiveWatcher: ParcelWatcher): void {
192-
const watchers = sortByPathPrefix(Array.from(recursiveWatcher.watchers.values()));
192+
const watchers = sortByPathPrefix(Array.from(recursiveWatcher.watchers));
193193

194194
const { active, failed, stopped } = computeRecursiveWatchStatus(recursiveWatcher);
195195
lines.push(`\n[Recursive Watchers (${watchers.length}, active: ${active}, failed: ${failed}, stopped: ${stopped})]:`);
@@ -213,7 +213,7 @@ function fillRecursiveWatcherStats(lines: string[], recursiveWatcher: ParcelWatc
213213
}
214214

215215
function fillNonRecursiveWatcherStats(lines: string[], nonRecursiveWatcher: NodeJSWatcher): void {
216-
const allWatchers = sortByPathPrefix(Array.from(nonRecursiveWatcher.watchers.values()));
216+
const allWatchers = sortByPathPrefix(Array.from(nonRecursiveWatcher.watchers));
217217
const activeWatchers = allWatchers.filter(watcher => !watcher.instance.failed && !watcher.instance.isReusingRecursiveWatcher);
218218
const failedWatchers = allWatchers.filter(watcher => watcher.instance.failed);
219219
const reusingWatchers = allWatchers.filter(watcher => watcher.instance.isReusingRecursiveWatcher);

src/vs/platform/files/test/node/parcelWatcher.test.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ suite.skip('File Watcher (parcel)', function () {
105105
});
106106

107107
teardown(async () => {
108-
const watchers = watcher.watchers.size;
108+
const watchers = Array.from(watcher.watchers).length;
109109
let stoppedInstances = 0;
110110
for (const instance of watcher.watchers) {
111111
Event.once(instance.onDidStop)(() => {
@@ -190,7 +190,6 @@ suite.skip('File Watcher (parcel)', function () {
190190
test('basics', async function () {
191191
const request = { path: testDir, excludes: [], recursive: true };
192192
await watcher.watch([request]);
193-
assert.strictEqual(watcher.watchers.size, watcher.watchers.size);
194193

195194
const instance = Array.from(watcher.watchers)[0];
196195
assert.strictEqual(request, instance.request);

0 commit comments

Comments
 (0)