Path: blob/main/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Event } from '../../../../../base/common/event.js';6import { patternsEquals } from '../../../../../base/common/glob.js';7import { BaseWatcher } from '../baseWatcher.js';8import { isLinux } from '../../../../../base/common/platform.js';9import { INonRecursiveWatchRequest, INonRecursiveWatcher, IRecursiveWatcherWithSubscribe } from '../../../common/watcher.js';10import { NodeJSFileWatcherLibrary } from './nodejsWatcherLib.js';11import { ThrottledWorker } from '../../../../../base/common/async.js';12import { MutableDisposable } from '../../../../../base/common/lifecycle.js';1314export interface INodeJSWatcherInstance {1516/**17* The watcher instance.18*/19readonly instance: NodeJSFileWatcherLibrary;2021/**22* The watch request associated to the watcher.23*/24readonly request: INonRecursiveWatchRequest;25}2627export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher {2829readonly onDidError = Event.None;3031private readonly _watchers = new Map<string /* path */ | number /* correlation ID */, INodeJSWatcherInstance>();32get watchers() { return this._watchers.values(); }3334private readonly worker = this._register(new MutableDisposable<ThrottledWorker<INonRecursiveWatchRequest>>());3536constructor(protected readonly recursiveWatcher: IRecursiveWatcherWithSubscribe | undefined) {37super();38}3940protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise<void> {4142// Figure out duplicates to remove from the requests43requests = this.removeDuplicateRequests(requests);4445// Figure out which watchers to start and which to stop46const requestsToStart: INonRecursiveWatchRequest[] = [];47const watchersToStop = new Set(Array.from(this.watchers));48for (const request of requests) {49const watcher = this._watchers.get(this.requestToWatcherKey(request));50if (watcher && patternsEquals(watcher.request.excludes, request.excludes) && patternsEquals(watcher.request.includes, request.includes)) {51watchersToStop.delete(watcher); // keep watcher52} else {53requestsToStart.push(request); // start watching54}55}5657// Logging5859if (requestsToStart.length) {60this.trace(`Request to start watching: ${requestsToStart.map(request => this.requestToString(request)).join(',')}`);61}6263if (watchersToStop.size) {64this.trace(`Request to stop watching: ${Array.from(watchersToStop).map(watcher => this.requestToString(watcher.request)).join(',')}`);65}6667// Stop the worker68this.worker.clear();6970// Stop watching as instructed71for (const watcher of watchersToStop) {72this.stopWatching(watcher);73}7475// Start watching as instructed76this.createWatchWorker().work(requestsToStart);77}7879private createWatchWorker(): ThrottledWorker<INonRecursiveWatchRequest> {8081// We see very large amount of non-recursive file watcher requests82// in large workspaces. To prevent the overhead of starting thousands83// of watchers at once, we use a throttled worker to distribute this84// work over time.8586this.worker.value = new ThrottledWorker<INonRecursiveWatchRequest>({87maxWorkChunkSize: 100, // only start 100 watchers at once before...88throttleDelay: 100, // ...resting for 100ms until we start watchers again...89maxBufferedWork: Number.MAX_VALUE // ...and never refuse any work.90}, requests => {91for (const request of requests) {92this.startWatching(request);93}94});9596return this.worker.value;97}9899private requestToWatcherKey(request: INonRecursiveWatchRequest): string | number {100return typeof request.correlationId === 'number' ? request.correlationId : this.pathToWatcherKey(request.path);101}102103private pathToWatcherKey(path: string): string {104return isLinux ? path : path.toLowerCase() /* ignore path casing */;105}106107private startWatching(request: INonRecursiveWatchRequest): void {108109// Start via node.js lib110const instance = new NodeJSFileWatcherLibrary(request, this.recursiveWatcher, changes => this._onDidChangeFile.fire(changes), () => this._onDidWatchFail.fire(request), msg => this._onDidLogMessage.fire(msg), this.verboseLogging);111112// Remember as watcher instance113const watcher: INodeJSWatcherInstance = { request, instance };114this._watchers.set(this.requestToWatcherKey(request), watcher);115}116117override async stop(): Promise<void> {118await super.stop();119120for (const watcher of this.watchers) {121this.stopWatching(watcher);122}123}124125private stopWatching(watcher: INodeJSWatcherInstance): void {126this.trace(`stopping file watcher`, watcher);127128this._watchers.delete(this.requestToWatcherKey(watcher.request));129130watcher.instance.dispose();131}132133private removeDuplicateRequests(requests: INonRecursiveWatchRequest[]): INonRecursiveWatchRequest[] {134const mapCorrelationtoRequests = new Map<number | undefined /* correlation */, Map<string, INonRecursiveWatchRequest>>();135136// Ignore requests for the same paths that have the same correlation137for (const request of requests) {138139let requestsForCorrelation = mapCorrelationtoRequests.get(request.correlationId);140if (!requestsForCorrelation) {141requestsForCorrelation = new Map<string, INonRecursiveWatchRequest>();142mapCorrelationtoRequests.set(request.correlationId, requestsForCorrelation);143}144145const path = this.pathToWatcherKey(request.path);146if (requestsForCorrelation.has(path)) {147this.trace(`ignoring a request for watching who's path is already watched: ${this.requestToString(request)}`);148}149150requestsForCorrelation.set(path, request);151}152153return Array.from(mapCorrelationtoRequests.values()).map(requests => Array.from(requests.values())).flat();154}155156override async setVerboseLogging(enabled: boolean): Promise<void> {157super.setVerboseLogging(enabled);158159for (const watcher of this.watchers) {160watcher.instance.setVerboseLogging(enabled);161}162}163164protected trace(message: string, watcher?: INodeJSWatcherInstance): void {165if (this.verboseLogging) {166this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message, watcher) });167}168}169170protected warn(message: string): void {171this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message) });172}173174private toMessage(message: string, watcher?: INodeJSWatcherInstance): string {175return watcher ? `[File Watcher (node.js)] ${message} (${this.requestToString(watcher.request)})` : `[File Watcher (node.js)] ${message}`;176}177}178179180