Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event } from '../../../../../base/common/event.js';
7
import { patternsEquals } from '../../../../../base/common/glob.js';
8
import { BaseWatcher } from '../baseWatcher.js';
9
import { isLinux } from '../../../../../base/common/platform.js';
10
import { INonRecursiveWatchRequest, INonRecursiveWatcher, IRecursiveWatcherWithSubscribe } from '../../../common/watcher.js';
11
import { NodeJSFileWatcherLibrary } from './nodejsWatcherLib.js';
12
import { ThrottledWorker } from '../../../../../base/common/async.js';
13
import { MutableDisposable } from '../../../../../base/common/lifecycle.js';
14
15
export interface INodeJSWatcherInstance {
16
17
/**
18
* The watcher instance.
19
*/
20
readonly instance: NodeJSFileWatcherLibrary;
21
22
/**
23
* The watch request associated to the watcher.
24
*/
25
readonly request: INonRecursiveWatchRequest;
26
}
27
28
export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {
29
30
readonly onDidError = Event.None;
31
32
private readonly _watchers = new Map<string /* path */ | number /* correlation ID */, INodeJSWatcherInstance>();
33
get watchers() { return this._watchers.values(); }
34
35
private readonly worker = this._register(new MutableDisposable<ThrottledWorker<INonRecursiveWatchRequest>>());
36
37
constructor(protected readonly recursiveWatcher: IRecursiveWatcherWithSubscribe | undefined) {
38
super();
39
}
40
41
protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise<void> {
42
43
// Figure out duplicates to remove from the requests
44
requests = this.removeDuplicateRequests(requests);
45
46
// Figure out which watchers to start and which to stop
47
const requestsToStart: INonRecursiveWatchRequest[] = [];
48
const watchersToStop = new Set(Array.from(this.watchers));
49
for (const request of requests) {
50
const watcher = this._watchers.get(this.requestToWatcherKey(request));
51
if (watcher && patternsEquals(watcher.request.excludes, request.excludes) && patternsEquals(watcher.request.includes, request.includes)) {
52
watchersToStop.delete(watcher); // keep watcher
53
} else {
54
requestsToStart.push(request); // start watching
55
}
56
}
57
58
// Logging
59
60
if (requestsToStart.length) {
61
this.trace(`Request to start watching: ${requestsToStart.map(request => this.requestToString(request)).join(',')}`);
62
}
63
64
if (watchersToStop.size) {
65
this.trace(`Request to stop watching: ${Array.from(watchersToStop).map(watcher => this.requestToString(watcher.request)).join(',')}`);
66
}
67
68
// Stop the worker
69
this.worker.clear();
70
71
// Stop watching as instructed
72
for (const watcher of watchersToStop) {
73
this.stopWatching(watcher);
74
}
75
76
// Start watching as instructed
77
this.createWatchWorker().work(requestsToStart);
78
}
79
80
private createWatchWorker(): ThrottledWorker<INonRecursiveWatchRequest> {
81
82
// We see very large amount of non-recursive file watcher requests
83
// in large workspaces. To prevent the overhead of starting thousands
84
// of watchers at once, we use a throttled worker to distribute this
85
// work over time.
86
87
this.worker.value = new ThrottledWorker<INonRecursiveWatchRequest>({
88
maxWorkChunkSize: 100, // only start 100 watchers at once before...
89
throttleDelay: 100, // ...resting for 100ms until we start watchers again...
90
maxBufferedWork: Number.MAX_VALUE // ...and never refuse any work.
91
}, requests => {
92
for (const request of requests) {
93
this.startWatching(request);
94
}
95
});
96
97
return this.worker.value;
98
}
99
100
private requestToWatcherKey(request: INonRecursiveWatchRequest): string | number {
101
return typeof request.correlationId === 'number' ? request.correlationId : this.pathToWatcherKey(request.path);
102
}
103
104
private pathToWatcherKey(path: string): string {
105
return isLinux ? path : path.toLowerCase() /* ignore path casing */;
106
}
107
108
private startWatching(request: INonRecursiveWatchRequest): void {
109
110
// Start via node.js lib
111
const instance = new NodeJSFileWatcherLibrary(request, this.recursiveWatcher, changes => this._onDidChangeFile.fire(changes), () => this._onDidWatchFail.fire(request), msg => this._onDidLogMessage.fire(msg), this.verboseLogging);
112
113
// Remember as watcher instance
114
const watcher: INodeJSWatcherInstance = { request, instance };
115
this._watchers.set(this.requestToWatcherKey(request), watcher);
116
}
117
118
override async stop(): Promise<void> {
119
await super.stop();
120
121
for (const watcher of this.watchers) {
122
this.stopWatching(watcher);
123
}
124
}
125
126
private stopWatching(watcher: INodeJSWatcherInstance): void {
127
this.trace(`stopping file watcher`, watcher);
128
129
this._watchers.delete(this.requestToWatcherKey(watcher.request));
130
131
watcher.instance.dispose();
132
}
133
134
private removeDuplicateRequests(requests: INonRecursiveWatchRequest[]): INonRecursiveWatchRequest[] {
135
const mapCorrelationtoRequests = new Map<number | undefined /* correlation */, Map<string, INonRecursiveWatchRequest>>();
136
137
// Ignore requests for the same paths that have the same correlation
138
for (const request of requests) {
139
140
let requestsForCorrelation = mapCorrelationtoRequests.get(request.correlationId);
141
if (!requestsForCorrelation) {
142
requestsForCorrelation = new Map<string, INonRecursiveWatchRequest>();
143
mapCorrelationtoRequests.set(request.correlationId, requestsForCorrelation);
144
}
145
146
const path = this.pathToWatcherKey(request.path);
147
if (requestsForCorrelation.has(path)) {
148
this.trace(`ignoring a request for watching who's path is already watched: ${this.requestToString(request)}`);
149
}
150
151
requestsForCorrelation.set(path, request);
152
}
153
154
return Array.from(mapCorrelationtoRequests.values()).map(requests => Array.from(requests.values())).flat();
155
}
156
157
override async setVerboseLogging(enabled: boolean): Promise<void> {
158
super.setVerboseLogging(enabled);
159
160
for (const watcher of this.watchers) {
161
watcher.instance.setVerboseLogging(enabled);
162
}
163
}
164
165
protected trace(message: string, watcher?: INodeJSWatcherInstance): void {
166
if (this.verboseLogging) {
167
this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message, watcher) });
168
}
169
}
170
171
protected warn(message: string): void {
172
this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message) });
173
}
174
175
private toMessage(message: string, watcher?: INodeJSWatcherInstance): string {
176
return watcher ? `[File Watcher (node.js)] ${message} (${this.requestToString(watcher.request)})` : `[File Watcher (node.js)] ${message}`;
177
}
178
}
179
180