Skip to content

Move fixed chunk size polling as a watch option and move it out of server #42542

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

Merged
merged 3 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,31 +101,34 @@ namespace ts {
fixedpollinginterval: WatchFileKind.FixedPollingInterval,
prioritypollinginterval: WatchFileKind.PriorityPollingInterval,
dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling,
fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling,
usefsevents: WatchFileKind.UseFsEvents,
usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory,
})),
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_UseFsEvents_UseFsEventsOnParentDirectory,
description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_FixedChunkSizePolling_UseFsEvents_UseFsEventsOnParentDirectory,
},
{
name: "watchDirectory",
type: new Map(getEntries({
usefsevents: WatchDirectoryKind.UseFsEvents,
fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval,
dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling,
fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling,
})),
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling,
description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling_FixedChunkSizePolling,
},
{
name: "fallbackPolling",
type: new Map(getEntries({
fixedinterval: PollingWatchKind.FixedInterval,
priorityinterval: PollingWatchKind.PriorityInterval,
dynamicpriority: PollingWatchKind.DynamicPriority,
fixedchunksize: PollingWatchKind.FixedChunkSize,
})),
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority,
description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority_FixedChunkSize,
},
{
name: "synchronousWatchDirectory",
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4701,15 +4701,15 @@
"category": "Message",
"code": 6224
},
"Specify strategy for watching file: 'FixedPollingInterval' (default), 'PriorityPollingInterval', 'DynamicPriorityPolling', 'UseFsEvents', 'UseFsEventsOnParentDirectory'.": {
"Specify strategy for watching file: 'FixedPollingInterval' (default), 'PriorityPollingInterval', 'DynamicPriorityPolling', 'FixedChunkSizePolling', 'UseFsEvents', 'UseFsEventsOnParentDirectory'.": {
"category": "Message",
"code": 6225
},
"Specify strategy for watching directory on platforms that don't support recursive watching natively: 'UseFsEvents' (default), 'FixedPollingInterval', 'DynamicPriorityPolling'.": {
"Specify strategy for watching directory on platforms that don't support recursive watching natively: 'UseFsEvents' (default), 'FixedPollingInterval', 'DynamicPriorityPolling', 'FixedChunkSizePolling'.": {
"category": "Message",
"code": 6226
},
"Specify strategy for creating a polling watch when it fails to create using file system events: 'FixedInterval' (default), 'PriorityInterval', 'DynamicPriority'.": {
"Specify strategy for creating a polling watch when it fails to create using file system events: 'FixedInterval' (default), 'PriorityInterval', 'DynamicPriority', 'FixedChunkSize'.": {
"category": "Message",
"code": 6227
},
Expand Down
181 changes: 130 additions & 51 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ namespace ts {
/* @internal */
export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time

/* @internal */
export function getModifiedTime(host: { getModifiedTime: NonNullable<System["getModifiedTime"]>; }, fileName: string) {
return host.getModifiedTime(fileName) || missingFileModifiedTime;
}

interface Levels {
Low: number;
Medium: number;
Expand Down Expand Up @@ -126,6 +131,64 @@ namespace ts {
}
}

interface WatchedFileWithIsClosed extends WatchedFile {
isClosed?: boolean;
}
function pollWatchedFileQueue<T extends WatchedFileWithIsClosed>(
host: { getModifiedTime: NonNullable<System["getModifiedTime"]>; },
queue: (T | undefined)[],
pollIndex: number, chunkSize: number,
callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void
) {
let definedValueCopyToIndex = pollIndex;
// Max visit would be all elements of the queue
for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) {
const watchedFile = queue[pollIndex];
if (!watchedFile) {
continue;
}
else if (watchedFile.isClosed) {
queue[pollIndex] = undefined;
continue;
}

// Only files polled count towards chunkSize
chunkSize--;
const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where actual polling happens. and this is the file that gets counted towards the polling. The code was existing just pulled out to helper so that it can be shared.

if (watchedFile.isClosed) {
// Closed watcher as part of callback
queue[pollIndex] = undefined;
continue;
}

callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged);
// Defragment the queue while we are at it
if (queue[pollIndex]) {
// Copy this file to the non hole location
if (definedValueCopyToIndex < pollIndex) {
queue[definedValueCopyToIndex] = watchedFile;
queue[pollIndex] = undefined;
}
definedValueCopyToIndex++;
}
}

// Return next poll index
return pollIndex;

function nextPollIndex() {
pollIndex++;
if (pollIndex === queue.length) {
if (definedValueCopyToIndex < pollIndex) {
// There are holes from definedValueCopyToIndex to end of queue, change queue size
queue.length = definedValueCopyToIndex;
}
pollIndex = 0;
definedValueCopyToIndex = 0;
}
}
}

/* @internal */
export function createDynamicPriorityPollingWatchFile(host: {
getModifiedTime: NonNullable<System["getModifiedTime"]>;
Expand Down Expand Up @@ -154,7 +217,7 @@ namespace ts {
fileName,
callback,
unchangedPolls: 0,
mtime: getModifiedTime(fileName)
mtime: getModifiedTime(host, fileName)
};
watchedFiles.push(file);

Expand Down Expand Up @@ -203,26 +266,16 @@ namespace ts {
}

function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) {
// Max visit would be all elements of the queue
let needsVisit = queue.length;
let definedValueCopyToIndex = pollIndex;
for (let polled = 0; polled < chunkSize && needsVisit > 0; nextPollIndex(), needsVisit--) {
const watchedFile = queue[pollIndex];
if (!watchedFile) {
continue;
}
else if (watchedFile.isClosed) {
queue[pollIndex] = undefined;
continue;
}
return pollWatchedFileQueue(
host,
queue,
pollIndex,
chunkSize,
onWatchFileStat
);

polled++;
const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(watchedFile.fileName));
if (watchedFile.isClosed) {
// Closed watcher as part of callback
queue[pollIndex] = undefined;
}
else if (fileChanged) {
function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) {
if (fileChanged) {
watchedFile.unchangedPolls = 0;
// Changed files go to changedFilesInLastPoll queue
if (queue !== changedFilesInLastPoll) {
Expand All @@ -244,30 +297,6 @@ namespace ts {
queue[pollIndex] = undefined;
addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High);
}

if (queue[pollIndex]) {
// Copy this file to the non hole location
if (definedValueCopyToIndex < pollIndex) {
queue[definedValueCopyToIndex] = watchedFile;
queue[pollIndex] = undefined;
}
definedValueCopyToIndex++;
}
}

// Return next poll index
return pollIndex;

function nextPollIndex() {
pollIndex++;
if (pollIndex === queue.length) {
if (definedValueCopyToIndex < pollIndex) {
// There are holes from nextDefinedValueIndex to end of queue, change queue size
queue.length = definedValueCopyToIndex;
}
pollIndex = 0;
definedValueCopyToIndex = 0;
}
}
}

Expand Down Expand Up @@ -301,10 +330,6 @@ namespace ts {
function scheduleNextPoll(pollingInterval: PollingInterval) {
pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval));
}

function getModifiedTime(fileName: string) {
return host.getModifiedTime(fileName) || missingFileModifiedTime;
}
}

function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile {
Expand Down Expand Up @@ -361,6 +386,43 @@ namespace ts {
}
}

function createFixedChunkSizePollingWatchFile(host: {
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
}): HostWatchFile {
const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = [];
let pollIndex = 0;
let pollScheduled: any;
return watchFile;

function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher {
const file: WatchedFileWithIsClosed = {
fileName,
callback,
mtime: getModifiedTime(host, fileName)
};
watchedFiles.push(file);
scheduleNextPoll();
return {
close: () => {
file.isClosed = true;
unorderedRemoveItem(watchedFiles, file);
}
};
}

function pollQueue() {
pollScheduled = undefined;
pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]);
scheduleNextPoll();
}

function scheduleNextPoll() {
if (!watchedFiles.length || pollScheduled) return;
pollScheduled = host.setTimeout(pollQueue, PollingInterval.High);
}
}

/* @internal */
export function createSingleFileWatcherPerName(
watchFile: HostWatchFile,
Expand Down Expand Up @@ -795,6 +857,7 @@ namespace ts {
tscWatchFile: string | undefined;
useNonPollingWatchers?: boolean;
tscWatchDirectory: string | undefined;
defaultWatchFileKind: System["defaultWatchFileKind"];
}

/*@internal*/
Expand All @@ -814,8 +877,10 @@ namespace ts {
tscWatchFile,
useNonPollingWatchers,
tscWatchDirectory,
defaultWatchFileKind,
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
let dynamicPollingWatchFile: HostWatchFile | undefined;
let fixedChunkSizePollingWatchFile: HostWatchFile | undefined;
let nonPollingWatchFile: HostWatchFile | undefined;
let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined;
return {
Expand All @@ -833,6 +898,8 @@ namespace ts {
return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined);
case WatchFileKind.DynamicPriorityPolling:
return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined);
case WatchFileKind.FixedChunkSizePolling:
return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined);
case WatchFileKind.UseFsEvents:
return fsWatch(
fileName,
Expand All @@ -853,8 +920,11 @@ namespace ts {
}

function ensureDynamicPollingWatchFile() {
return dynamicPollingWatchFile ||
(dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }));
return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout });
}

function ensureFixedChunkSizePollingWatchFile() {
return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout });
}

function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions {
Expand All @@ -880,7 +950,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: WatchFileKind.FixedPollingInterval };
{ watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval };
}
}

Expand Down Expand Up @@ -944,6 +1014,13 @@ namespace ts {
PollingInterval.Medium,
/*options*/ undefined
);
case WatchDirectoryKind.FixedChunkSizePolling:
return ensureFixedChunkSizePollingWatchFile()(
directoryName,
() => callback(directoryName),
/* pollingInterval */ undefined!,
/*options*/ undefined
);
case WatchDirectoryKind.UseFsEvents:
return fsWatch(
directoryName,
Expand Down Expand Up @@ -1131,6 +1208,7 @@ namespace ts {
// For testing
/*@internal*/ now?(): Date;
/*@internal*/ require?(baseDir: string, moduleName: string): RequireResult;
/*@internal*/ defaultWatchFileKind?(): WatchFileKind | undefined;
}

export interface FileWatcher {
Expand Down Expand Up @@ -1219,6 +1297,7 @@ namespace ts {
tscWatchFile: process.env.TSC_WATCHFILE,
useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER,
tscWatchDirectory: process.env.TSC_WATCHDIRECTORY,
defaultWatchFileKind: () => sys!.defaultWatchFileKind?.(),
});
const nodeSystem: System = {
args: process.argv.slice(2),
Expand Down
10 changes: 5 additions & 5 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,7 @@ namespace ts {

function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined {
// Check tsconfig time
const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime;
const tsconfigTime = getModifiedTime(state.host, configFile);
if (oldestOutputFileTime < tsconfigTime) {
return {
type: UpToDateStatusType.OutOfDateWithSelf,
Expand All @@ -1357,7 +1357,7 @@ namespace ts {
};
}

const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime;
const inputTime = getModifiedTime(host, inputFile); host.getModifiedTime(inputFile);
if (inputTime > newestInputFileTime) {
newestInputFileName = inputFile;
newestInputFileTime = inputTime;
Expand Down Expand Up @@ -1390,7 +1390,7 @@ namespace ts {
break;
}

const outputTime = host.getModifiedTime(output) || missingFileModifiedTime;
const outputTime = getModifiedTime(host, output);
if (outputTime < oldestOutputFileTime) {
oldestOutputFileTime = outputTime;
oldestOutputFileName = output;
Expand All @@ -1413,7 +1413,7 @@ namespace ts {
// had its file touched but not had its contents changed - this allows us
// to skip a downstream typecheck
if (isDeclarationFile(output)) {
const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime;
const outputModifiedTime = getModifiedTime(host, output);
newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime);
}
}
Expand Down Expand Up @@ -1571,7 +1571,7 @@ namespace ts {
}

if (isDeclarationFile(file)) {
priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime);
priorNewestUpdateTime = newer(priorNewestUpdateTime, getModifiedTime(host, file));
}

host.setModifiedTime(file, now);
Expand Down
Loading