Skip to content

Commit 0cf834c

Browse files
authored
Move fixed chunk size polling as a watch option and move it out of server (#42542)
* Move fixed chunk size polling as a watch option and move it out of server Fixes #41549 * Feedback
1 parent 5dbb110 commit 0cf834c

14 files changed

+572
-179
lines changed

src/compiler/commandLineParser.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,31 +101,34 @@ namespace ts {
101101
fixedpollinginterval: WatchFileKind.FixedPollingInterval,
102102
prioritypollinginterval: WatchFileKind.PriorityPollingInterval,
103103
dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling,
104+
fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling,
104105
usefsevents: WatchFileKind.UseFsEvents,
105106
usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory,
106107
})),
107108
category: Diagnostics.Advanced_Options,
108-
description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_UseFsEvents_UseFsEventsOnParentDirectory,
109+
description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_FixedChunkSizePolling_UseFsEvents_UseFsEventsOnParentDirectory,
109110
},
110111
{
111112
name: "watchDirectory",
112113
type: new Map(getEntries({
113114
usefsevents: WatchDirectoryKind.UseFsEvents,
114115
fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval,
115116
dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling,
117+
fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling,
116118
})),
117119
category: Diagnostics.Advanced_Options,
118-
description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling,
120+
description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling_FixedChunkSizePolling,
119121
},
120122
{
121123
name: "fallbackPolling",
122124
type: new Map(getEntries({
123125
fixedinterval: PollingWatchKind.FixedInterval,
124126
priorityinterval: PollingWatchKind.PriorityInterval,
125127
dynamicpriority: PollingWatchKind.DynamicPriority,
128+
fixedchunksize: PollingWatchKind.FixedChunkSize,
126129
})),
127130
category: Diagnostics.Advanced_Options,
128-
description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority,
131+
description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority_FixedChunkSize,
129132
},
130133
{
131134
name: "synchronousWatchDirectory",

src/compiler/diagnosticMessages.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4701,15 +4701,15 @@
47014701
"category": "Message",
47024702
"code": 6224
47034703
},
4704-
"Specify strategy for watching file: 'FixedPollingInterval' (default), 'PriorityPollingInterval', 'DynamicPriorityPolling', 'UseFsEvents', 'UseFsEventsOnParentDirectory'.": {
4704+
"Specify strategy for watching file: 'FixedPollingInterval' (default), 'PriorityPollingInterval', 'DynamicPriorityPolling', 'FixedChunkSizePolling', 'UseFsEvents', 'UseFsEventsOnParentDirectory'.": {
47054705
"category": "Message",
47064706
"code": 6225
47074707
},
4708-
"Specify strategy for watching directory on platforms that don't support recursive watching natively: 'UseFsEvents' (default), 'FixedPollingInterval', 'DynamicPriorityPolling'.": {
4708+
"Specify strategy for watching directory on platforms that don't support recursive watching natively: 'UseFsEvents' (default), 'FixedPollingInterval', 'DynamicPriorityPolling', 'FixedChunkSizePolling'.": {
47094709
"category": "Message",
47104710
"code": 6226
47114711
},
4712-
"Specify strategy for creating a polling watch when it fails to create using file system events: 'FixedInterval' (default), 'PriorityInterval', 'DynamicPriority'.": {
4712+
"Specify strategy for creating a polling watch when it fails to create using file system events: 'FixedInterval' (default), 'PriorityInterval', 'DynamicPriority', 'FixedChunkSize'.": {
47134713
"category": "Message",
47144714
"code": 6227
47154715
},

src/compiler/sys.ts

Lines changed: 130 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ namespace ts {
5757
/* @internal */
5858
export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time
5959

60+
/* @internal */
61+
export function getModifiedTime(host: { getModifiedTime: NonNullable<System["getModifiedTime"]>; }, fileName: string) {
62+
return host.getModifiedTime(fileName) || missingFileModifiedTime;
63+
}
64+
6065
interface Levels {
6166
Low: number;
6267
Medium: number;
@@ -126,6 +131,64 @@ namespace ts {
126131
}
127132
}
128133

134+
interface WatchedFileWithIsClosed extends WatchedFile {
135+
isClosed?: boolean;
136+
}
137+
function pollWatchedFileQueue<T extends WatchedFileWithIsClosed>(
138+
host: { getModifiedTime: NonNullable<System["getModifiedTime"]>; },
139+
queue: (T | undefined)[],
140+
pollIndex: number, chunkSize: number,
141+
callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void
142+
) {
143+
let definedValueCopyToIndex = pollIndex;
144+
// Max visit would be all elements of the queue
145+
for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) {
146+
const watchedFile = queue[pollIndex];
147+
if (!watchedFile) {
148+
continue;
149+
}
150+
else if (watchedFile.isClosed) {
151+
queue[pollIndex] = undefined;
152+
continue;
153+
}
154+
155+
// Only files polled count towards chunkSize
156+
chunkSize--;
157+
const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName));
158+
if (watchedFile.isClosed) {
159+
// Closed watcher as part of callback
160+
queue[pollIndex] = undefined;
161+
continue;
162+
}
163+
164+
callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged);
165+
// Defragment the queue while we are at it
166+
if (queue[pollIndex]) {
167+
// Copy this file to the non hole location
168+
if (definedValueCopyToIndex < pollIndex) {
169+
queue[definedValueCopyToIndex] = watchedFile;
170+
queue[pollIndex] = undefined;
171+
}
172+
definedValueCopyToIndex++;
173+
}
174+
}
175+
176+
// Return next poll index
177+
return pollIndex;
178+
179+
function nextPollIndex() {
180+
pollIndex++;
181+
if (pollIndex === queue.length) {
182+
if (definedValueCopyToIndex < pollIndex) {
183+
// There are holes from definedValueCopyToIndex to end of queue, change queue size
184+
queue.length = definedValueCopyToIndex;
185+
}
186+
pollIndex = 0;
187+
definedValueCopyToIndex = 0;
188+
}
189+
}
190+
}
191+
129192
/* @internal */
130193
export function createDynamicPriorityPollingWatchFile(host: {
131194
getModifiedTime: NonNullable<System["getModifiedTime"]>;
@@ -154,7 +217,7 @@ namespace ts {
154217
fileName,
155218
callback,
156219
unchangedPolls: 0,
157-
mtime: getModifiedTime(fileName)
220+
mtime: getModifiedTime(host, fileName)
158221
};
159222
watchedFiles.push(file);
160223

@@ -203,26 +266,16 @@ namespace ts {
203266
}
204267

205268
function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) {
206-
// Max visit would be all elements of the queue
207-
let needsVisit = queue.length;
208-
let definedValueCopyToIndex = pollIndex;
209-
for (let polled = 0; polled < chunkSize && needsVisit > 0; nextPollIndex(), needsVisit--) {
210-
const watchedFile = queue[pollIndex];
211-
if (!watchedFile) {
212-
continue;
213-
}
214-
else if (watchedFile.isClosed) {
215-
queue[pollIndex] = undefined;
216-
continue;
217-
}
269+
return pollWatchedFileQueue(
270+
host,
271+
queue,
272+
pollIndex,
273+
chunkSize,
274+
onWatchFileStat
275+
);
218276

219-
polled++;
220-
const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(watchedFile.fileName));
221-
if (watchedFile.isClosed) {
222-
// Closed watcher as part of callback
223-
queue[pollIndex] = undefined;
224-
}
225-
else if (fileChanged) {
277+
function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) {
278+
if (fileChanged) {
226279
watchedFile.unchangedPolls = 0;
227280
// Changed files go to changedFilesInLastPoll queue
228281
if (queue !== changedFilesInLastPoll) {
@@ -244,30 +297,6 @@ namespace ts {
244297
queue[pollIndex] = undefined;
245298
addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High);
246299
}
247-
248-
if (queue[pollIndex]) {
249-
// Copy this file to the non hole location
250-
if (definedValueCopyToIndex < pollIndex) {
251-
queue[definedValueCopyToIndex] = watchedFile;
252-
queue[pollIndex] = undefined;
253-
}
254-
definedValueCopyToIndex++;
255-
}
256-
}
257-
258-
// Return next poll index
259-
return pollIndex;
260-
261-
function nextPollIndex() {
262-
pollIndex++;
263-
if (pollIndex === queue.length) {
264-
if (definedValueCopyToIndex < pollIndex) {
265-
// There are holes from nextDefinedValueIndex to end of queue, change queue size
266-
queue.length = definedValueCopyToIndex;
267-
}
268-
pollIndex = 0;
269-
definedValueCopyToIndex = 0;
270-
}
271300
}
272301
}
273302

@@ -301,10 +330,6 @@ namespace ts {
301330
function scheduleNextPoll(pollingInterval: PollingInterval) {
302331
pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval));
303332
}
304-
305-
function getModifiedTime(fileName: string) {
306-
return host.getModifiedTime(fileName) || missingFileModifiedTime;
307-
}
308333
}
309334

310335
function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile {
@@ -361,6 +386,43 @@ namespace ts {
361386
}
362387
}
363388

389+
function createFixedChunkSizePollingWatchFile(host: {
390+
getModifiedTime: NonNullable<System["getModifiedTime"]>;
391+
setTimeout: NonNullable<System["setTimeout"]>;
392+
}): HostWatchFile {
393+
const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = [];
394+
let pollIndex = 0;
395+
let pollScheduled: any;
396+
return watchFile;
397+
398+
function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher {
399+
const file: WatchedFileWithIsClosed = {
400+
fileName,
401+
callback,
402+
mtime: getModifiedTime(host, fileName)
403+
};
404+
watchedFiles.push(file);
405+
scheduleNextPoll();
406+
return {
407+
close: () => {
408+
file.isClosed = true;
409+
unorderedRemoveItem(watchedFiles, file);
410+
}
411+
};
412+
}
413+
414+
function pollQueue() {
415+
pollScheduled = undefined;
416+
pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]);
417+
scheduleNextPoll();
418+
}
419+
420+
function scheduleNextPoll() {
421+
if (!watchedFiles.length || pollScheduled) return;
422+
pollScheduled = host.setTimeout(pollQueue, PollingInterval.High);
423+
}
424+
}
425+
364426
/* @internal */
365427
export function createSingleFileWatcherPerName(
366428
watchFile: HostWatchFile,
@@ -795,6 +857,7 @@ namespace ts {
795857
tscWatchFile: string | undefined;
796858
useNonPollingWatchers?: boolean;
797859
tscWatchDirectory: string | undefined;
860+
defaultWatchFileKind: System["defaultWatchFileKind"];
798861
}
799862

800863
/*@internal*/
@@ -814,8 +877,10 @@ namespace ts {
814877
tscWatchFile,
815878
useNonPollingWatchers,
816879
tscWatchDirectory,
880+
defaultWatchFileKind,
817881
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
818882
let dynamicPollingWatchFile: HostWatchFile | undefined;
883+
let fixedChunkSizePollingWatchFile: HostWatchFile | undefined;
819884
let nonPollingWatchFile: HostWatchFile | undefined;
820885
let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined;
821886
return {
@@ -833,6 +898,8 @@ namespace ts {
833898
return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined);
834899
case WatchFileKind.DynamicPriorityPolling:
835900
return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined);
901+
case WatchFileKind.FixedChunkSizePolling:
902+
return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined);
836903
case WatchFileKind.UseFsEvents:
837904
return fsWatch(
838905
fileName,
@@ -853,8 +920,11 @@ namespace ts {
853920
}
854921

855922
function ensureDynamicPollingWatchFile() {
856-
return dynamicPollingWatchFile ||
857-
(dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }));
923+
return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout });
924+
}
925+
926+
function ensureFixedChunkSizePollingWatchFile() {
927+
return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout });
858928
}
859929

860930
function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions {
@@ -880,7 +950,7 @@ namespace ts {
880950
// Use notifications from FS to watch with falling back to fs.watchFile
881951
generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) :
882952
// Default to do not use fixed polling interval
883-
{ watchFile: WatchFileKind.FixedPollingInterval };
953+
{ watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval };
884954
}
885955
}
886956

@@ -944,6 +1014,13 @@ namespace ts {
9441014
PollingInterval.Medium,
9451015
/*options*/ undefined
9461016
);
1017+
case WatchDirectoryKind.FixedChunkSizePolling:
1018+
return ensureFixedChunkSizePollingWatchFile()(
1019+
directoryName,
1020+
() => callback(directoryName),
1021+
/* pollingInterval */ undefined!,
1022+
/*options*/ undefined
1023+
);
9471024
case WatchDirectoryKind.UseFsEvents:
9481025
return fsWatch(
9491026
directoryName,
@@ -1131,6 +1208,7 @@ namespace ts {
11311208
// For testing
11321209
/*@internal*/ now?(): Date;
11331210
/*@internal*/ require?(baseDir: string, moduleName: string): RequireResult;
1211+
/*@internal*/ defaultWatchFileKind?(): WatchFileKind | undefined;
11341212
}
11351213

11361214
export interface FileWatcher {
@@ -1219,6 +1297,7 @@ namespace ts {
12191297
tscWatchFile: process.env.TSC_WATCHFILE,
12201298
useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER,
12211299
tscWatchDirectory: process.env.TSC_WATCHDIRECTORY,
1300+
defaultWatchFileKind: () => sys!.defaultWatchFileKind?.(),
12221301
});
12231302
const nodeSystem: System = {
12241303
args: process.argv.slice(2),

src/compiler/tsbuildPublic.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,7 +1334,7 @@ namespace ts {
13341334

13351335
function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined {
13361336
// Check tsconfig time
1337-
const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime;
1337+
const tsconfigTime = getModifiedTime(state.host, configFile);
13381338
if (oldestOutputFileTime < tsconfigTime) {
13391339
return {
13401340
type: UpToDateStatusType.OutOfDateWithSelf,
@@ -1357,7 +1357,7 @@ namespace ts {
13571357
};
13581358
}
13591359

1360-
const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime;
1360+
const inputTime = getModifiedTime(host, inputFile); host.getModifiedTime(inputFile);
13611361
if (inputTime > newestInputFileTime) {
13621362
newestInputFileName = inputFile;
13631363
newestInputFileTime = inputTime;
@@ -1390,7 +1390,7 @@ namespace ts {
13901390
break;
13911391
}
13921392

1393-
const outputTime = host.getModifiedTime(output) || missingFileModifiedTime;
1393+
const outputTime = getModifiedTime(host, output);
13941394
if (outputTime < oldestOutputFileTime) {
13951395
oldestOutputFileTime = outputTime;
13961396
oldestOutputFileName = output;
@@ -1413,7 +1413,7 @@ namespace ts {
14131413
// had its file touched but not had its contents changed - this allows us
14141414
// to skip a downstream typecheck
14151415
if (isDeclarationFile(output)) {
1416-
const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime;
1416+
const outputModifiedTime = getModifiedTime(host, output);
14171417
newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime);
14181418
}
14191419
}
@@ -1571,7 +1571,7 @@ namespace ts {
15711571
}
15721572

15731573
if (isDeclarationFile(file)) {
1574-
priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime);
1574+
priorNewestUpdateTime = newer(priorNewestUpdateTime, getModifiedTime(host, file));
15751575
}
15761576

15771577
host.setModifiedTime(file, now);

0 commit comments

Comments
 (0)