Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 13.7k
Make useFsEvents as default strategy for the watching#50366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
88e574b266fbc5956ee0010a7b4e4d753d361a99bf4c3a17fb9fc81c71140c7b0f7c88File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Diff view
Diff view
Uh oh!
There was an error while loading. Please reload this page.
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -35,8 +35,7 @@ namespace ts { | ||
| export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) => void; | ||
| export type DirectoryWatcherCallback = (fileName: string) => void; | ||
| /*@internal*/ | ||
| export interface WatchedFile { | ||
| interface WatchedFile { | ||
| readonly fileName: string; | ||
| readonly callback: FileWatcherCallback; | ||
| mtime: Date; | ||
| @@ -81,8 +80,7 @@ namespace ts { | ||
| /* @internal */ | ||
| export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); | ||
| /* @internal */ | ||
| export function setCustomPollingValues(system: System) { | ||
| function setCustomPollingValues(system: System) { | ||
| if (!system.getEnvironmentVariable) { | ||
| return; | ||
| } | ||
| @@ -189,31 +187,28 @@ namespace ts { | ||
| } | ||
| } | ||
| /* @internal */ | ||
| export function createDynamicPriorityPollingWatchFile(host: { | ||
| interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed { | ||
| unchangedPolls: number; | ||
| } | ||
| function createDynamicPriorityPollingWatchFile(host: { | ||
| getModifiedTime: NonNullable<System["getModifiedTime"]>; | ||
| setTimeout: NonNullable<System["setTimeout"]>; | ||
| }): HostWatchFile { | ||
| interface WatchedFile extends ts.WatchedFile { | ||
| isClosed?: boolean; | ||
| unchangedPolls: number; | ||
| } | ||
| interface PollingIntervalQueue extends Array<WatchedFile> { | ||
| interface PollingIntervalQueue extends Array<WatchedFileWithUnchangedPolls> { | ||
| pollingInterval: PollingInterval; | ||
| pollIndex: number; | ||
| pollScheduled: boolean; | ||
| } | ||
| const watchedFiles: WatchedFile[] = []; | ||
| const changedFilesInLastPoll: WatchedFile[] = []; | ||
| const watchedFiles: WatchedFileWithUnchangedPolls[] = []; | ||
| const changedFilesInLastPoll: WatchedFileWithUnchangedPolls[] = []; | ||
| const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); | ||
| const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); | ||
| const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); | ||
| return watchFile; | ||
| function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { | ||
| const file: WatchedFile = { | ||
| const file: WatchedFileWithUnchangedPolls = { | ||
| fileName, | ||
| callback, | ||
| unchangedPolls: 0, | ||
| @@ -233,7 +228,7 @@ namespace ts { | ||
| } | ||
| function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { | ||
| const queue = [] as WatchedFile[] as PollingIntervalQueue; | ||
| const queue = [] as WatchedFileWithUnchangedPolls[] as PollingIntervalQueue; | ||
| queue.pollingInterval = pollingInterval; | ||
| queue.pollIndex = 0; | ||
| queue.pollScheduled = false; | ||
| @@ -265,7 +260,7 @@ namespace ts { | ||
| } | ||
| } | ||
| function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { | ||
| function pollQueue(queue: (WatchedFileWithUnchangedPolls | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { | ||
| return pollWatchedFileQueue( | ||
| host, | ||
| queue, | ||
| @@ -274,7 +269,7 @@ namespace ts { | ||
| onWatchFileStat | ||
| ); | ||
| function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) { | ||
| function onWatchFileStat(watchedFile: WatchedFileWithUnchangedPolls, pollIndex: number, fileChanged: boolean) { | ||
| if (fileChanged) { | ||
| watchedFile.unchangedPolls = 0; | ||
| // Changed files go to changedFilesInLastPoll queue | ||
| @@ -311,12 +306,12 @@ namespace ts { | ||
| } | ||
| } | ||
| function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) { | ||
| function addToPollingIntervalQueue(file: WatchedFileWithUnchangedPolls, pollingInterval: PollingInterval) { | ||
| pollingIntervalQueue(pollingInterval).push(file); | ||
| scheduleNextPollIfNotAlreadyScheduled(pollingInterval); | ||
| } | ||
| function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) { | ||
| function addChangedFileToLowPollingIntervalQueue(file: WatchedFileWithUnchangedPolls) { | ||
| changedFilesInLastPoll.push(file); | ||
| scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); | ||
| } | ||
| @@ -423,59 +418,50 @@ namespace ts { | ||
| } | ||
| } | ||
| /* @internal */ | ||
| export function createSingleFileWatcherPerName( | ||
| watchFile: HostWatchFile, | ||
| useCaseSensitiveFileNames: boolean | ||
| ): HostWatchFile { | ||
| interface SingleFileWatcher { | ||
| watcher: FileWatcher; | ||
| refCount: number; | ||
| } | ||
| const cache = new Map<string, SingleFileWatcher>(); | ||
| const callbacksCache = createMultiMap<FileWatcherCallback>(); | ||
| interface SingleFileWatcher<T extends FileWatcherCallback | FsWatchCallback>{ | ||
| watcher: FileWatcher; | ||
| callbacks: T[]; | ||
| } | ||
| function createSingleWatcherPerName<T extends FileWatcherCallback | FsWatchCallback>( | ||
| cache: Map<SingleFileWatcher<T>>, | ||
| useCaseSensitiveFileNames: boolean, | ||
| name: string, | ||
| callback: T, | ||
| createWatcher: (callback: T) => FileWatcher, | ||
| ): FileWatcher { | ||
| const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); | ||
| const path = toCanonicalFileName(name); | ||
| const existing = cache.get(path); | ||
| if (existing) { | ||
| existing.callbacks.push(callback); | ||
| } | ||
| else { | ||
| cache.set(path, { | ||
| watcher: createWatcher(( | ||
| // Cant infer types correctly so lets satisfy checker | ||
| (param1: any, param2: never, param3: any) => cache.get(path)?.callbacks.slice().forEach(cb => cb(param1, param2, param3)) | ||
sheetalkamat marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ) as T), | ||
| callbacks: [callback] | ||
| }); | ||
| } | ||
| return (fileName, callback, pollingInterval, options) => { | ||
| const path = toCanonicalFileName(fileName); | ||
| const existing = cache.get(path); | ||
| if (existing) { | ||
| existing.refCount++; | ||
| } | ||
| else { | ||
| cache.set(path, { | ||
| watcher: watchFile( | ||
| fileName, | ||
| (fileName, eventKind, modifiedTime) => forEach( | ||
| callbacksCache.get(path), | ||
| cb => cb(fileName, eventKind, modifiedTime) | ||
| ), | ||
| pollingInterval, | ||
| options | ||
| ), | ||
| refCount: 1 | ||
| }); | ||
| return { | ||
| close: () => { | ||
| const watcher = cache.get(path); | ||
| // Watcher is not expected to be undefined, but if it is normally its because | ||
| // exception was thrown somewhere else and watch state is not what it should be | ||
| if (!watcher) return; | ||
sheetalkamat marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (!orderedRemoveItem(watcher.callbacks, callback) || watcher.callbacks.length) return; | ||
sheetalkamat marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. sheetalkamat marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| cache.delete(path); | ||
| closeFileWatcherOf(watcher); | ||
| } | ||
| callbacksCache.add(path, callback); | ||
| return { | ||
| close: () => { | ||
| const watcher = Debug.checkDefined(cache.get(path)); | ||
| callbacksCache.remove(path, callback); | ||
| watcher.refCount--; | ||
| if (watcher.refCount) return; | ||
| cache.delete(path); | ||
| closeFileWatcherOf(watcher); | ||
| } | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Returns true if file status changed | ||
| */ | ||
| /*@internal*/ | ||
| export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { | ||
| function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { | ||
| const oldTime = watchedFile.mtime.getTime(); | ||
| const newTime = modifiedTime.getTime(); | ||
| if (oldTime !== newTime) { | ||
| @@ -512,8 +498,7 @@ namespace ts { | ||
| curSysLog = logger; | ||
| } | ||
| /*@internal*/ | ||
| export interface RecursiveDirectoryWatcherHost { | ||
| interface RecursiveDirectoryWatcherHost { | ||
| watchDirectory: HostWatchDirectory; | ||
| useCaseSensitiveFileNames: boolean; | ||
| getCurrentDirectory: System["getCurrentDirectory"]; | ||
| @@ -529,8 +514,7 @@ namespace ts { | ||
| * that means if this is recursive watcher, watch the children directories as well | ||
| * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) | ||
| */ | ||
| /*@internal*/ | ||
| export function createDirectoryWatcherSupportingRecursive({ | ||
| function createDirectoryWatcherSupportingRecursive({ | ||
| watchDirectory, | ||
| useCaseSensitiveFileNames, | ||
| getCurrentDirectory, | ||
| @@ -792,8 +776,7 @@ namespace ts { | ||
| Directory, | ||
| } | ||
| /*@internal*/ | ||
| export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { | ||
| function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { | ||
| return (_fileName, eventKind, modifiedTime) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "", modifiedTime); | ||
| } | ||
| @@ -854,7 +837,7 @@ namespace ts { | ||
| /*@internal*/ | ||
| export interface CreateSystemWatchFunctions { | ||
| // Polling watch file | ||
| pollingWatchFile: HostWatchFile; | ||
| pollingWatchFileWorker: HostWatchFile; | ||
| // For dynamic polling watch file | ||
| getModifiedTime: NonNullable<System["getModifiedTime"]>; | ||
| setTimeout: NonNullable<System["setTimeout"]>; | ||
| @@ -878,7 +861,7 @@ namespace ts { | ||
| /*@internal*/ | ||
| export function createSystemWatchFunctions({ | ||
| pollingWatchFile, | ||
| pollingWatchFileWorker, | ||
| getModifiedTime, | ||
| setTimeout, | ||
| clearTimeout, | ||
| @@ -896,6 +879,9 @@ namespace ts { | ||
| inodeWatching, | ||
| sysLog, | ||
| }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { | ||
| const pollingWatches = new Map<string, SingleFileWatcher<FileWatcherCallback>>(); | ||
| const fsWatches = new Map<string, SingleFileWatcher<FsWatchCallback>>(); | ||
| const fsWatchesRecursive = new Map<string, SingleFileWatcher<FsWatchCallback>>(); | ||
| let dynamicPollingWatchFile: HostWatchFile | undefined; | ||
| let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; | ||
| let nonPollingWatchFile: HostWatchFile | undefined; | ||
| @@ -968,7 +954,7 @@ namespace ts { | ||
| // Use notifications from FS to watch with falling back to fs.watchFile | ||
| generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : | ||
| // Default to do not use fixed polling interval | ||
| { watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval }; | ||
| { watchFile: defaultWatchFileKind?.() || WatchFileKind.UseFsEvents }; | ||
| } | ||
| } | ||
| @@ -1073,13 +1059,39 @@ namespace ts { | ||
| } | ||
| } | ||
| function pollingWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) { | ||
| return createSingleWatcherPerName( | ||
| pollingWatches, | ||
| useCaseSensitiveFileNames, | ||
| fileName, | ||
| callback, | ||
| cb => pollingWatchFileWorker(fileName, cb, pollingInterval, options), | ||
| ); | ||
| } | ||
| function fsWatch( | ||
| fileOrDirectory: string, | ||
| entryKind: FileSystemEntryKind, | ||
| callback: FsWatchCallback, | ||
| recursive: boolean, | ||
| fallbackPollingInterval: PollingInterval, | ||
| fallbackOptions: WatchOptions | undefined | ||
| ): FileWatcher { | ||
| return createSingleWatcherPerName( | ||
| recursive ? fsWatchesRecursive : fsWatches, | ||
| useCaseSensitiveFileNames, | ||
| fileOrDirectory, | ||
| callback, | ||
| cb => fsWatchHandlingExistenceOnHost(fileOrDirectory, entryKind, cb, recursive, fallbackPollingInterval, fallbackOptions), | ||
| ); | ||
| } | ||
| function fsWatchHandlingExistenceOnHost( | ||
| fileOrDirectory: string, | ||
| entryKind: FileSystemEntryKind, | ||
| callback: FsWatchCallback, | ||
| recursive: boolean, | ||
| fallbackPollingInterval: PollingInterval, | ||
| fallbackOptions: WatchOptions | undefined | ||
| ): FileWatcher { | ||
| let lastDirectoryPartWithDirectorySeparator: string | undefined; | ||
| let lastDirectoryPart: string | undefined; | ||
| @@ -1445,7 +1457,7 @@ namespace ts { | ||
| const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); | ||
| const getCurrentDirectory = memoize(() => process.cwd()); | ||
| const { watchFile, watchDirectory } = createSystemWatchFunctions({ | ||
| pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames), | ||
| pollingWatchFileWorker: fsWatchFileWorker, | ||
| getModifiedTime, | ||
| setTimeout, | ||
| clearTimeout, | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.