diskFileSystemProvider.ts ×21

Frontier kind: Code frontier

unlabeled · c_0f733f9c85af

32 tests · 13076 LOC · 64 files · introduces 0 tests · 577 LOC · 8 files

Introduces — evidence that enters the hierarchy at this concept

Code
97 ranges577 lines · 8 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1891 ranges13076 lines · 64 files · Browse complete extent
All tests (intent)
32 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

8 files ranked by introduced lines: 577 introduced LOC across 97 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts 125 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- nodejsWatcherLib.ts
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 { watch, promises } from 'fs';
7 > import { RunOnceWorker, ThrottledWorker } from '../../../../../base/common/async.js';
8 > import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';
9 > import { isEqual, isEqualOrParent } from '../../../../../base/common/extpath.js';
10 > import { Disposable, DisposableStore, IDisposable, thenRegisterOrDispose, toDisposable } from '../../../../../base/common/lifecycle.js';
11 > import { normalizeNFC } from '../../../../../base/common/normalization.js';
12 > import { basename, dirname, join } from '../../../../../base/common/path.js';
13 > import { isLinux, isMacintosh } from '../../../../../base/common/platform.js';
14 > import { joinPath } from '../../../../../base/common/resources.js';
15 > import { URI } from '../../../../../base/common/uri.js';
16 > import { Promises } from '../../../../../base/node/pfs.js';
17 > import { FileChangeFilter, FileChangeType, IFileChange } from '../../../common/files.js';
18 > import { ILogMessage, coalesceEvents, INonRecursiveWatchRequest, parseWatcherPatterns, IRecursiveWatcherWithSubscribe, isFiltered, isWatchRequestWithCorrelation } from '../../../common/watcher.js';
19 > import { Lazy } from '../../../../../base/common/lazy.js';
20 > import { ParsedPattern } from '../../../../../base/common/glob.js';
21 >
22 > export class NodeJSFileWatcherLibrary extends Disposable {
23 >
24 > // A delay in reacting to file deletes to support
25 > // atomic save operations where a tool may chose
26 > // to delete a file before creating it again for
27 > // an update.
28 > private static readonly FILE_DELETE_HANDLER_DELAY = 100;
29 >
30 > // A delay for collecting file changes from node.js
31 > // before collecting them for coalescing and emitting
32 > // Same delay as used for the recursive watcher.
33 > private static readonly FILE_CHANGES_HANDLER_DELAY = 75;
34 >
35 > // Reduce likelyhood of spam from file events via throttling.
36 > // These numbers are a bit more aggressive compared to the
37 > // recursive watcher because we can have many individual
38 > // node.js watchers per request.
39 > // (https://github.com/microsoft/vscode/issues/124723)
40 > private readonly throttledFileChangesEmitter = this._register(new ThrottledWorker<IFileChange>(
41 > {
42 > maxWorkChunkSize: 100, // only process up to 100 changes at once before...
43 > throttleDelay: 200, // ...resting for 200ms until we process events again...
44 > maxBufferedWork: 10000 // ...but never buffering more than 10000 events in memory
45 > },
46 > events => this.onDidFilesChange(events)
47 > ));
48 >
49 > // Aggregate file changes over FILE_CHANGES_HANDLER_DELAY
50 > // to coalesce events and reduce spam.
51 > private readonly fileChangesAggregator = this._register(new RunOnceWorker<IFileChange>(events => this.handleFileChanges(events), NodeJSFileWatcherLibrary.FILE_CHANGES_HANDLER_DELAY));
52 >
53 > private readonly excludes: ParsedPattern[];
54 > private readonly includes: ParsedPattern[] | undefined;
55 > private readonly filter: FileChangeFilter | undefined;
56 >
57 > private readonly cts = new CancellationTokenSource();
58 >
59 > private readonly realPath = new Lazy(async () => {
60 >
61 > // This property is intentionally `Lazy` and not using `realcase()` as the counterpart
62 > // in the recursive watcher because of the amount of paths this watcher is dealing with.
63 > // We try as much as possible to avoid even needing `realpath()` if we can because even
64 > // that method does an `lstat()` per segment of the path.
65 >
66 > let result = this.request.path;
67 >
68 > try {
69 > result = await Promises.realpath(this.request.path);
70 >
71 > if (this.request.path !== result) {
72 > this.trace(`correcting a path to watch that seems to be a symbolic link (original: ${this.request.path}, real: ${result})`);
73 > }
74 > } catch (error) {
75 > // ignore
76 > }
77 >
78 > return result;
79 > });
80 >
81 > readonly ready: Promise<void>;
82 >
83 > private _isReusingRecursiveWatcher = false;
84 > get isReusingRecursiveWatcher(): boolean { return this._isReusingRecursiveWatcher; }
85 >
86 > private didFail = false;
87 > get failed(): boolean { return this.didFail; }
88 >
89 > constructor(
90 private readonly request: INonRecursiveWatchRequest,
91 private readonly recursiveWatcher: IRecursiveWatcherWithSubscribe | undefined,
104 this.ready = this.watch();
105 }
107 > private async watch(): Promise<void> {
108 try {
109 const stat = await promises.stat(this.request.path);
124 }
125 }
127 > private notifyWatchFailed(): void {
128 this.didFail = true;
129
130 this.onDidWatchFail?.();
131 }
133 > private async doWatch(isDirectory: boolean): Promise<IDisposable> {
134 const disposables = new DisposableStore();
135
144 return disposables;
145 }
147 > private doWatchWithExistingWatcher(isDirectory: boolean, disposables: DisposableStore): boolean {
148 if (isDirectory) {
149 // Recursive watcher re-use is currently not enabled for when
182 return false;
183 }
185 > private async doWatchWithNodeJS(isDirectory: boolean, disposables: DisposableStore): Promise<void> {
186 const realPath = await this.realPath.value;
187
438 }
439 }
441 > private onWatchedPathDeleted(resource: URI): void {
442 this.warn('Watcher shutdown because watched path got deleted');
443
448 this.notifyWatchFailed();
449 }
451 > private onFileChange(event: IFileChange, skipIncludeExcludeChecks = false): void {
452 if (this.cts.token.isCancellationRequested) {
453 return;
472 }
473 }
475 > private handleFileChanges(fileChanges: IFileChange[]): void {
476
477 // Coalesce events: merge events of same kind
515 }
516 }
518 > private async existsChildStrictCase(path: string): Promise<boolean> {
519 if (isLinux) {
520 return Promises.exists(path);
532 }
533 }
535 > setVerboseLogging(verboseLogging: boolean): void {
536 this.verboseLogging = verboseLogging;
537 }
539 > private error(error: string): void {
540 if (!this.cts.token.isCancellationRequested) {
541 this.onLogMessage?.({ type: 'error', message: `[File Watcher (node.js)] ${error}` });
542 }
543 }
545 > private warn(message: string): void {
546 if (!this.cts.token.isCancellationRequested) {
547 this.onLogMessage?.({ type: 'warn', message: `[File Watcher (node.js)] ${message}` });
548 }
549 }
551 > private trace(message: string): void {
552 if (!this.cts.token.isCancellationRequested && this.verboseLogging) {
553 this.onLogMessage?.({ type: 'trace', message: `[File Watcher (node.js)] ${message}` });
554 }
555 }
557 > private traceWithCorrelation(message: string): void {
558 if (!this.cts.token.isCancellationRequested && this.verboseLogging) {
559 this.trace(`${message}${typeof this.request.correlationId === 'number' ? ` <${this.request.correlationId}> ` : ``}`);
560 }
561 }
563 > override dispose(): void {
564 this.cts.dispose(true);
565
566 super.dispose();
567 }
569 >
570 > /**
571 > * Watch the provided `path` for changes and return
572 > * the data in chunks of `Uint8Array` for further use.
573 > */
574 export async function watchFileContents(path: string, onData: (chunk: Uint8Array) => void, onReady: () => void, token: CancellationToken, bufferSize = 512): Promise<void> {
575 const handle = await Promises.open(path, 'r');
src/vs/base/parts/ipc/node/ipc.cp.ts 99 introduced LOC · 10 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- ipc.cp.ts
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 { ChildProcess, fork, ForkOptions } from 'child_process';
7 > import { createCancelablePromise, Delayer } from '../../../common/async.js';
8 > import { VSBuffer } from '../../../common/buffer.js';
9 > import { CancellationToken } from '../../../common/cancellation.js';
10 > import { isRemoteConsoleLog, log } from '../../../common/console.js';
11 > import * as errors from '../../../common/errors.js';
12 > import { Emitter, Event } from '../../../common/event.js';
13 > import { dispose, IDisposable, toDisposable } from '../../../common/lifecycle.js';
14 > import { deepClone } from '../../../common/objects.js';
15 > import { createQueuedSender } from '../../../node/processes.js';
16 > import { removeDangerousEnvVariables } from '../../../common/processes.js';
17 > import { ChannelClient as IPCClient, ChannelServer as IPCServer, IChannel, IChannelClient } from '../common/ipc.js';
18 >
19 > /**
20 > * This implementation doesn't perform well since it uses base64 encoding for buffers.
21 > * We should move all implementations to use named ipc.net, so we stop depending on cp.fork.
22 > */
23 >
24 > export class Server<TContext extends string> extends IPCServer<TContext> {
25 > constructor(ctx: TContext) {
26 super({
27 send: r => {
35 process.once('disconnect', () => this.dispose());
36 }
37 > } ipc.cp.ts
38 >
39 > export interface IIPCOptions {
40 >
41 > /**
42 > * A descriptive name for the server this connection is to. Used in logging.
43 > */
44 > serverName: string;
45 >
46 > /**
47 > * Time in millies before killing the ipc process. The next request after killing will start it again.
48 > */
49 > timeout?: number;
50 >
51 > /**
52 > * Arguments to the module to execute.
53 > */
54 > args?: string[];
55 >
56 > /**
57 > * Environment key-value pairs to be passed to the process that gets spawned for the ipc.
58 > */
59 > env?: any;
60 >
61 > /**
62 > * Allows to assign a debug port for debugging the application executed.
63 > */
64 > debug?: number;
65 >
66 > /**
67 > * Allows to assign a debug port for debugging the application and breaking it on the first line.
68 > */
69 > debugBrk?: number;
70 >
71 > /**
72 > * If set, starts the fork with empty execArgv. If not set, execArgv from the parent process are inherited,
73 > * except --inspect= and --inspect-brk= which are filtered as they would result in a port conflict.
74 > */
75 > freshExecArgv?: boolean;
76 >
77 > /**
78 > * Enables our createQueuedSender helper for this Client. Uses a queue when the internal Node.js queue is
79 > * full of messages - see notes on that method.
80 > */
81 > useQueue?: boolean;
82 > }
83 >
84 > export class Client implements IChannelClient, IDisposable {
85 >
86 > private disposeDelayer: Delayer<void> | undefined;
87 > private activeRequests = new Set<IDisposable>();
88 > private child: ChildProcess | null;
89 > private _client: IPCClient | null;
90 > private channels = new Map<string, IChannel>();
91 >
92 > private readonly _onDidProcessExit = new Emitter<{ code: number; signal: string }>();
93 > readonly onDidProcessExit = this._onDidProcessExit.event;
94 >
95 > constructor(private modulePath: string, private options: IIPCOptions) {
96 const timeout = options.timeout || 60000;
97 this.disposeDelayer = new Delayer<void>(timeout);
99 this._client = null;
100 }
101 > ipc.cp.ts
102 > getChannel<T extends IChannel>(channelName: string): T {
103 const that = this;
104
113 } as T;
114 }
115 > ipc.cp.ts
116 > protected requestPromise<T>(channelName: string, name: string, arg?: any, cancellationToken = CancellationToken.None): Promise<T> {
117 if (!this.disposeDelayer) {
118 return Promise.reject(new Error('disposed'));
143 return result;
144 }
145 > ipc.cp.ts
146 > protected requestEvent<T>(channelName: string, name: string, arg?: any): Event<T> {
147 if (!this.disposeDelayer) {
148 return Event.None;
172 return emitter.event;
173 }
174 > ipc.cp.ts
175 > private get client(): IPCClient {
176 if (!this._client) {
177 const args = this.options.args || [];
252 return this._client;
253 }
254 > ipc.cp.ts
255 > private getCachedChannel(name: string): IChannel {
256 let channel = this.channels.get(name);
257
263 return channel;
264 }
265 > ipc.cp.ts
266 > private disposeClient() {
267 if (this._client) {
268 if (this.child) {
274 }
275 }
276 > ipc.cp.ts
277 > dispose() {
278 this._onDidProcessExit.dispose();
279 this.disposeDelayer?.cancel();
282 this.activeRequests.clear();
283 }
284 > } ipc.cp.ts
src/vs/platform/files/node/diskFileSystemProvider.ts 96 introduced LOC · 21 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- diskFileSystemProvider.ts
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 { Stats, constants, promises } from 'fs';
7 > import { Barrier, retry } from '../../../base/common/async.js';
8 > import { ResourceMap } from '../../../base/common/map.js';
9 > import { VSBuffer } from '../../../base/common/buffer.js';
10 > import { CancellationToken } from '../../../base/common/cancellation.js';
11 > import { Event } from '../../../base/common/event.js';
12 > import { isEqual } from '../../../base/common/extpath.js';
13 > import { DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
14 > import { basename, dirname, join } from '../../../base/common/path.js';
15 > import { isLinux, isWindows } from '../../../base/common/platform.js';
16 > import { extUriBiasedIgnorePathCase, joinPath, basename as resourcesBasename, dirname as resourcesDirname } from '../../../base/common/resources.js';
17 > import { newWriteableStream, ReadableStreamEvents } from '../../../base/common/stream.js';
18 > import { URI } from '../../../base/common/uri.js';
19 > import { IDirent, Promises, RimRafMode, SymlinkSupport } from '../../../base/node/pfs.js';
20 > import { localize } from '../../../nls.js';
21 > import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileChange, IFileSystemProviderWithFileRealpathCapability } from '../common/files.js';
22 > import { readFileIntoStream } from '../common/io.js';
23 > import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage } from '../common/watcher.js';
24 > import { AbstractDiskFileSystemProvider } from '../common/diskFileSystemProvider.js';
25 > import { UniversalWatcherClient } from './watcher/watcherClient.js';
26 > import { NodeJSWatcherClient } from './watcher/nodejs/nodejsClient.js';
27 >
28 > export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider implements
29 > IFileSystemProviderWithFileReadWriteCapability,
30 > IFileSystemProviderWithOpenReadWriteCloseCapability,
31 > IFileSystemProviderWithFileReadStreamCapability,
32 > IFileSystemProviderWithFileFolderCopyCapability,
33 > IFileSystemProviderWithFileAtomicReadCapability,
34 > IFileSystemProviderWithFileAtomicWriteCapability,
35 > IFileSystemProviderWithFileAtomicDeleteCapability,
36 > IFileSystemProviderWithFileCloneCapability,
37 > IFileSystemProviderWithFileRealpathCapability {
38
39 private static TRACE_LOG_RESOURCE_LOCKS = false; // not enabled by default because very spammy
342
343 private readonly writeHandles = new Map<number, URI>();
345 > private static canFlush = true;
346 >
347 > static configureFlushOnWrite(enabled: boolean): void {
348 DiskFileSystemProvider.canFlush = enabled;
349 }
351 > async open(resource: URI, opts: IFileOpenOptions, disableWriteLock?: boolean): Promise<number> {
352 const filePath = this.toFilePath(resource);
353
471 return fd;
472 }
474 > async close(fd: number): Promise<void> {
475
476 // It is very important that we keep any associated lock
515 }
516 }
518 > async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
519 const normalizedPos = this.normalizePos(fd, pos);
520
530 return bytesRead;
531 }
533 > private normalizePos(fd: number, pos: number): number | null {
534
535 // When calling fs.read/write we try to avoid passing in the "pos" argument and
546 return pos;
547 }
549 > private updatePos(fd: number, pos: number | null, bytesLength: number | null): void {
550 const lastKnownPos = this.mapHandleToPos.get(fd);
551 if (typeof lastKnownPos === 'number') {
588 }
589 }
591 > async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
592
593 // We know at this point that the file to write to is truncated and thus empty
596 return retry(() => this.doWrite(fd, pos, data, offset, length), 100 /* ms delay */, 3 /* retries */);
597 }
599 > private async doWrite(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
600 const normalizedPos = this.normalizePos(fd, pos);
601
611 return bytesWritten;
612 }
614 > //#endregion
615 >
616 > //#region Move/Copy/Delete/Create Folder
617 >
618 > async mkdir(resource: URI): Promise<void> {
619 try {
620 await promises.mkdir(this.toFilePath(resource));
623 }
624 }
626 > async delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {
627 try {
628 const filePath = this.toFilePath(resource);
667 }
668 }
670 > async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
671 const fromFilePath = this.toFilePath(from);
672 const toFilePath = this.toFilePath(to);
694 }
695 }
697 > async copy(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
698 const fromFilePath = this.toFilePath(from);
699 const toFilePath = this.toFilePath(to);
721 }
722 }
724 > private async validateMoveCopy(from: URI, to: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<void> {
725 const fromFilePath = this.toFilePath(from);
726 const toFilePath = this.toFilePath(to);
772 }
773 }
775 > //#endregion
776 >
777 > //#region Clone File
778 >
779 > async cloneFile(from: URI, to: URI): Promise<void> {
780 return this.doCloneFile(from, to, false /* optimistically assume parent folders exist */);
781 }
783 > private async doCloneFile(from: URI, to: URI, mkdir: boolean): Promise<void> {
784 const fromFilePath = this.toFilePath(from);
785 const toFilePath = this.toFilePath(to);
815 }
816 }
818 > //#endregion
819 >
820 > //#region File Watching
821 >
822 > protected createUniversalWatcher(
823 onChange: (changes: IFileChange[]) => void,
824 onLogMessage: (msg: ILogMessage) => void,
827 return new UniversalWatcherClient(changes => onChange(changes), msg => onLogMessage(msg), verboseLogging);
828 }
830 > protected createNonRecursiveWatcher(
831 onChange: (changes: IFileChange[]) => void,
832 onLogMessage: (msg: ILogMessage) => void,
835 return new NodeJSWatcherClient(changes => onChange(changes), msg => onLogMessage(msg), verboseLogging);
836 }
838 > //#endregion
839 >
840 > //#region Helpers
841 >
842 > private toFileSystemProviderError(error: NodeJS.ErrnoException): FileSystemProviderError {
843 if (error instanceof FileSystemProviderError) {
844 return error; // avoid double conversion
874 return createFileSystemProviderError(resultError, code);
875 }
877 > private async toFileSystemProviderWriteError(resource: URI | undefined, error: NodeJS.ErrnoException): Promise<FileSystemProviderError> {
878 let fileSystemProviderWriteError = this.toFileSystemProviderError(error);
879
src/vs/platform/files/node/watcher/baseWatcher.ts 93 introduced LOC · 20 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- baseWatcher.ts
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 { watchFile, unwatchFile, Stats } from 'fs';
7 > import { Disposable, DisposableMap, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
8 > import { ILogMessage, IRecursiveWatcherWithSubscribe, IUniversalWatchRequest, IWatchRequestWithCorrelation, IWatcher, IWatcherErrorEvent, isWatchRequestWithCorrelation, requestFilterToString } from '../../common/watcher.js';
9 > import { Emitter, Event } from '../../../../base/common/event.js';
10 > import { FileChangeType, IFileChange } from '../../common/files.js';
11 > import { URI } from '../../../../base/common/uri.js';
12 > import { DeferredPromise, ThrottledDelayer } from '../../../../base/common/async.js';
13 > import { hash } from '../../../../base/common/hash.js';
14 > import { onUnexpectedError } from '../../../../base/common/errors.js';
15 >
16 > interface ISuspendedWatchRequest {
17 > readonly id: number;
18 > readonly correlationId: number | undefined;
19 > readonly path: string;
20 > }
21 >
22 > export abstract class BaseWatcher extends Disposable implements IWatcher {
23 >
24 > protected readonly _onDidChangeFile = this._register(new Emitter<IFileChange[]>());
25 > readonly onDidChangeFile = this._onDidChangeFile.event;
26 >
27 > protected readonly _onDidLogMessage = this._register(new Emitter<ILogMessage>());
28 > readonly onDidLogMessage = this._onDidLogMessage.event;
29 >
30 > protected readonly _onDidWatchFail = this._register(new Emitter<IUniversalWatchRequest>());
31 > private readonly onDidWatchFail = this._onDidWatchFail.event;
32 >
33 > private readonly correlatedWatchRequests = new Map<number /* request ID */, IWatchRequestWithCorrelation>();
34 > private readonly nonCorrelatedWatchRequests = new Map<number /* request ID */, IUniversalWatchRequest>();
35 >
36 > private readonly suspendedWatchRequests = this._register(new DisposableMap<number /* request ID */>());
37 > private readonly suspendedWatchRequestsWithPolling = new Set<number /* request ID */>();
38 >
39 > private readonly updateWatchersDelayer = this._register(new ThrottledDelayer<void>(this.getUpdateWatchersDelay()));
40 >
41 > protected readonly suspendedWatchRequestPollingInterval: number = 5007; // node.js default
42 >
43 > private joinWatch = new DeferredPromise<void>();
44 >
45 > constructor() {
46 super();
47
52 })));
53 }
55 > protected isCorrelated(request: IUniversalWatchRequest): request is IWatchRequestWithCorrelation {
56 return isWatchRequestWithCorrelation(request);
57 }
59 > private computeId(request: IUniversalWatchRequest): number {
60 if (this.isCorrelated(request)) {
61 return request.correlationId;
67 }
68 }
70 > async watch(requests: IUniversalWatchRequest[]): Promise<void> {
71 if (!this.joinWatch.isSettled) {
72 this.joinWatch.complete();
100 }
101 }
103 > private updateWatchers(delayed: boolean): Promise<void> {
104 const nonSuspendedRequests: IUniversalWatchRequest[] = [];
105 for (const [id, request] of [...this.nonCorrelatedWatchRequests, ...this.correlatedWatchRequests]) {
111 return this.updateWatchersDelayer.trigger(() => this.doWatch(nonSuspendedRequests), delayed ? this.getUpdateWatchersDelay() : 0).catch(error => onUnexpectedError(error));
112 }
114 > protected getUpdateWatchersDelay(): number {
115 return 800;
116 }
118 > isSuspended(request: IUniversalWatchRequest): 'polling' | boolean {
119 const id = this.computeId(request);
120 return this.suspendedWatchRequestsWithPolling.has(id) ? 'polling' : this.suspendedWatchRequests.has(id);
121 }
123 > private async suspendWatchRequest(request: ISuspendedWatchRequest): Promise<void> {
124 if (this.suspendedWatchRequests.has(request.id)) {
125 return; // already suspended
144 this.updateWatchers(true /* delay this call as we might accumulate many failing watch requests on startup */);
145 }
147 > private resumeWatchRequest(request: ISuspendedWatchRequest): void {
148 this.suspendedWatchRequests.deleteAndDispose(request.id);
149 this.suspendedWatchRequestsWithPolling.delete(request.id);
151 this.updateWatchers(false);
152 }
154 > private monitorSuspendedWatchRequest(request: ISuspendedWatchRequest, disposables: DisposableStore): void {
155 if (this.doMonitorWithExistingWatcher(request, disposables)) {
156 this.trace(`reusing an existing recursive watcher to monitor ${request.path}`);
161 }
162 }
164 > private doMonitorWithExistingWatcher(request: ISuspendedWatchRequest, disposables: DisposableStore): boolean {
165 const subscription = this.recursiveWatcher?.subscribe(request.path, (error, change) => {
166 if (disposables.isDisposed) {
183 return false;
184 }
186 > private doMonitorWithNodeJS(request: ISuspendedWatchRequest, disposables: DisposableStore): void {
187 let pathNotFound = false;
188
220 }));
221 }
223 > private onMonitoredPathAdded(request: ISuspendedWatchRequest): void {
224 this.trace(`detected ${request.path} exists again, resuming watcher (correlationId: ${request.correlationId})`);
225
232 this.resumeWatchRequest(request);
233 }
235 > private isPathNotFound(stats: Stats): boolean {
236 return stats.ctimeMs === 0 && stats.ino === 0;
237 }
239 > async stop(): Promise<void> {
240 this.suspendedWatchRequests.clearAndDisposeAll();
241 this.suspendedWatchRequestsWithPolling.clear();
242 }
244 > protected traceEvent(event: IFileChange, request: IUniversalWatchRequest | ISuspendedWatchRequest): void {
245 if (this.verboseLogging) {
246 const traceMsg = ` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`;
248 }
249 }
251 > protected traceWithCorrelation(message: string, request: IUniversalWatchRequest | ISuspendedWatchRequest): void {
252 if (this.verboseLogging) {
253 this.trace(`${message}${typeof request.correlationId === 'number' ? ` <${request.correlationId}> ` : ``}`);
254 }
255 }
257 > protected requestToString(request: IUniversalWatchRequest): string {
258 return `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'}, filter: ${requestFilterToString(request.filter)}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : '<none>'})`;
259 }
261 > protected abstract doWatch(requests: IUniversalWatchRequest[]): Promise<void>;
262 >
263 > protected abstract readonly recursiveWatcher: IRecursiveWatcherWithSubscribe | undefined;
264 >
265 > protected abstract trace(message: string): void;
266 > protected abstract warn(message: string): void;
267 >
268 > abstract onDidError: Event<IWatcherErrorEvent>;
269 >
270 > protected verboseLogging = false;
271 >
272 > async setVerboseLogging(enabled: boolean): Promise<void> {
273 this.verboseLogging = enabled;
274 }
275 > } baseWatcher.ts
src/vs/platform/files/common/diskFileSystemProvider.ts 68 introduced LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- diskFileSystemProvider.ts
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 { insert } from '../../../base/common/arrays.js';
7 > import { ThrottledDelayer } from '../../../base/common/async.js';
8 > import { onUnexpectedError } from '../../../base/common/errors.js';
9 > import { Emitter } from '../../../base/common/event.js';
10 > import { removeTrailingPathSeparator } from '../../../base/common/extpath.js';
11 > import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
12 > import { normalize } from '../../../base/common/path.js';
13 > import { URI } from '../../../base/common/uri.js';
14 > import { IFileChange, IFileSystemProvider, IWatchOptions } from './files.js';
15 > import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage, INonRecursiveWatchRequest, IRecursiveWatcherOptions, isRecursiveWatchRequest, IUniversalWatchRequest, reviveFileChanges } from './watcher.js';
16 > import { ILogService, LogLevel } from '../../log/common/log.js';
17 >
18 > export interface IDiskFileSystemProviderOptions {
19 > watcher?: {
20 >
21 > /**
22 > * Extra options for the recursive file watching.
23 > */
24 > recursive?: IRecursiveWatcherOptions;
25 >
26 > /**
27 > * Forces all file watch requests to run through a
28 > * single universal file watcher, both recursive
29 > * and non-recursively.
30 > *
31 > * Enabling this option might cause some overhead,
32 > * specifically the universal file watcher will run
33 > * in a separate process given its complexity. Only
34 > * enable it when you understand the consequences.
35 > */
36 > forceUniversal?: boolean;
37 > };
38 > }
39 >
40 > export abstract class AbstractDiskFileSystemProvider extends Disposable implements
41 > Pick<IFileSystemProvider, 'watch'>,
42 > Pick<IFileSystemProvider, 'onDidChangeFile'>,
43 > Pick<IFileSystemProvider, 'onDidWatchError'> {
44 >
45 > constructor(
46 protected readonly logService: ILogService,
47 private readonly options?: IDiskFileSystemProviderOptions
168 private readonly nonRecursiveWatchRequests: INonRecursiveWatchRequest[] = [];
169 private readonly nonRecursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(this.getRefreshWatchersDelay(this.nonRecursiveWatchRequests.length)));
171 > private watchNonRecursive(resource: URI, opts: IWatchOptions): IDisposable {
172
173 // Add to list of paths to watch non-recursively
194 });
195 }
197 > private refreshNonRecursiveWatchers(): void {
198 this.nonRecursiveWatchRequestDelayer.trigger(() => {
199 return this.doRefreshNonRecursiveWatchers();
200 }, this.getRefreshWatchersDelay(this.nonRecursiveWatchRequests.length)).catch(error => onUnexpectedError(error));
201 }
203 > private doRefreshNonRecursiveWatchers(): Promise<void> {
204
205 // Create watcher if this is the first time
220 return this.nonRecursiveWatcher.watch(this.nonRecursiveWatchRequests);
221 }
223 > protected abstract createNonRecursiveWatcher(
224 > onChange: (changes: IFileChange[]) => void,
225 > onLogMessage: (msg: ILogMessage) => void,
226 > verboseLogging: boolean
227 > ): AbstractNonRecursiveWatcherClient;
228 >
229 > //#endregion
230 >
231 > private onWatcherLogMessage(msg: ILogMessage): void {
232 if (msg.type === 'error') {
233 this._onDidWatchError.fire(msg.message);
236 this.logWatcherMessage(msg);
237 }
239 > protected logWatcherMessage(msg: ILogMessage): void {
240 this.logService[msg.type](msg.message);
241 }
243 > protected toFilePath(resource: URI): string {
244 return normalize(resource.fsPath);
245 }
247 > private toWatchPath(resource: URI): string {
248 const filePath = this.toFilePath(resource);
249
src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts 62 introduced LOC · 14 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- nodejsWatcher.ts
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 }
41 > protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise<void> {
42
43 // Figure out duplicates to remove from the requests
77 this.createWatchWorker().work(requestsToStart);
78 }
80 > private createWatchWorker(): ThrottledWorker<INonRecursiveWatchRequest> {
81
82 // We see very large amount of non-recursive file watcher requests
97 return this.worker.value;
98 }
100 > private requestToWatcherKey(request: INonRecursiveWatchRequest): string | number {
101 return typeof request.correlationId === 'number' ? request.correlationId : this.pathToWatcherKey(request.path);
102 }
104 > private pathToWatcherKey(path: string): string {
105 return isLinux ? path : path.toLowerCase() /* ignore path casing */;
106 }
108 > private startWatching(request: INonRecursiveWatchRequest): void {
109
110 // Start via node.js lib
115 this._watchers.set(this.requestToWatcherKey(request), watcher);
116 }
118 > override async stop(): Promise<void> {
119 await super.stop();
120
123 }
124 }
126 > private stopWatching(watcher: INodeJSWatcherInstance): void {
127 this.trace(`stopping file watcher`, watcher);
128
131 watcher.instance.dispose();
132 }
134 > private removeDuplicateRequests(requests: INonRecursiveWatchRequest[]): INonRecursiveWatchRequest[] {
135 const mapCorrelationtoRequests = new Map<number | undefined /* correlation */, Map<string, INonRecursiveWatchRequest>>();
136
154 return Array.from(mapCorrelationtoRequests.values()).flatMap(requests => Array.from(requests.values()));
155 }
157 > override async setVerboseLogging(enabled: boolean): Promise<void> {
158 super.setVerboseLogging(enabled);
159
162 }
163 }
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 }
171 > protected warn(message: string): void {
172 this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message) });
173 }
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 }
src/vs/platform/files/node/watcher/watcherClient.ts 18 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- watcherClient.ts
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 { DisposableStore } from '../../../../base/common/lifecycle.js';
7 > import { FileAccess } from '../../../../base/common/network.js';
8 > import { getNextTickChannel, ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js';
9 > import { Client } from '../../../../base/parts/ipc/node/ipc.cp.js';
10 > import { IFileChange } from '../../common/files.js';
11 > import { AbstractUniversalWatcherClient, ILogMessage, IUniversalWatcher } from '../../common/watcher.js';
12 >
13 > export class UniversalWatcherClient extends AbstractUniversalWatcherClient {
14 >
15 > constructor(
16 onFileChanges: (changes: IFileChange[]) => void,
17 onLogMessage: (msg: ILogMessage) => void,
22 this.init();
23 }
25 > protected override createWatcher(disposables: DisposableStore): IUniversalWatcher {
26
27 // Fork the universal file watcher and build a client around
45 return ProxyChannel.toService<IUniversalWatcher>(getNextTickChannel(client.getChannel('watcher')));
46 }
src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts 16 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- nodejsClient.ts
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 { DisposableStore } from '../../../../../base/common/lifecycle.js';
7 > import { IFileChange } from '../../../common/files.js';
8 > import { ILogMessage, AbstractNonRecursiveWatcherClient, INonRecursiveWatcher } from '../../../common/watcher.js';
9 > import { NodeJSWatcher } from './nodejsWatcher.js';
10 >
11 > export class NodeJSWatcherClient extends AbstractNonRecursiveWatcherClient {
12 >
13 > constructor(
14 onFileChanges: (changes: IFileChange[]) => void,
15 onLogMessage: (msg: ILogMessage) => void,
20 this.init();
21 }
23 > protected override createWatcher(disposables: DisposableStore): INonRecursiveWatcher {
24 return disposables.add(new NodeJSWatcher(undefined /* no recursive watching support here */)) satisfies INonRecursiveWatcher;
25 }