Logging of the watch add/remove/event

This commit is contained in:
Sheetal Nandi 2017-07-13 20:08:57 -07:00
parent 0365901381
commit 404aa8f0be
4 changed files with 264 additions and 143 deletions

View File

@ -303,6 +303,31 @@ namespace ts.server {
}
}
/* @internal */
export const enum WatchType {
ConfigFilePath = "Config file for the program",
MissingFilePath = "Missing file from program",
WildCardDirectories = "Wild card directory",
TypeRoot = "Type root of the project",
ClosedScriptInfo = "Closed Script info"
}
/* @internal */
export const enum WatcherCloseReason {
ProjectClose = "Project close",
NotNeeded = "After project update isnt required any more",
FileCreated = "File got created",
RecursiveChanged = "Recursive changed for the watch",
ProjectReloadHitMaxSize = "Project reloaded and hit the max file size capacity",
OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more",
OrphanScriptInfo = "Removing Orphan script info as part of cleanup",
FileDeleted = "File was deleted",
FileOpened = "File opened"
}
/* @internal */
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void;
export interface ProjectServiceOptions {
host: ServerHost;
logger: Logger;
@ -599,7 +624,7 @@ namespace ts.server {
if (!info.isScriptOpen()) {
if (info.containingProjects.length === 0) {
// Orphan script info, remove it as we can always reload it on next open file request
info.stopWatcher();
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange);
this.filenameToScriptInfo.delete(info.path);
}
else {
@ -613,9 +638,7 @@ namespace ts.server {
}
private handleDeletedFile(info: ScriptInfo) {
this.logger.info(`${info.fileName} deleted`);
info.stopWatcher();
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileDeleted);
// TODO: handle isOpen = true case
@ -644,7 +667,8 @@ namespace ts.server {
}
}
private onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) {
/* @internal */
onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) {
this.logger.info(`Type root file ${fileName} changed`);
project.getCachedServerHost().addOrDeleteFileOrFolder(fileName);
project.updateTypes();
@ -667,10 +691,10 @@ namespace ts.server {
return;
}
this.logger.info(`Detected source file changes: ${fileName}`);
const configFilename = project.getConfigFilePath();
this.logger.info(`Project: ${configFilename} Detected source file add/remove: ${fileName}`);
const configFileSpecs = project.configFileSpecs;
const configFilename = normalizePath(project.getConfigFilePath());
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions);
const errors = project.getAllProjectErrors();
if (result.fileNames.length === 0) {
@ -693,9 +717,7 @@ namespace ts.server {
}
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
const configFileName = project.getConfigFilePath();
if (eventKind === FileWatcherEventKind.Deleted) {
this.logger.info(`Config file deleted: ${configFileName}`);
this.removeProject(project);
// Reload the configured projects for these open files in the project as
// they could be held up by another config file somewhere in the parent directory
@ -704,7 +726,6 @@ namespace ts.server {
this.delayInferredProjectsRefresh();
}
else {
this.logger.info(`Config file changed: ${configFileName}`);
project.pendingReload = true;
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
}
@ -894,7 +915,7 @@ namespace ts.server {
this.filenameToScriptInfo.forEach(info => {
if (!info.isScriptOpen() && info.containingProjects.length === 0) {
// if there are not projects that include this script info - delete it
info.stopWatcher();
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo);
this.filenameToScriptInfo.delete(info.path);
}
});
@ -1155,22 +1176,26 @@ namespace ts.server {
}
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) {
const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
const project = new ConfiguredProject(
configFileName,
this,
this.documentRegistry,
projectOptions.configHasFilesProperty,
projectOptions.compilerOptions,
/*languageServiceEnabled*/ !sizeLimitExceeded,
languageServiceEnabled,
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave,
cachedServerHost);
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
project.configFileSpecs = configFileSpecs;
project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind));
project.watchWildcards(projectOptions.wildcardDirectories);
project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path));
project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project,
configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)
);
if (languageServiceEnabled) {
project.watchWildcards(projectOptions.wildcardDirectories);
project.watchTypeRoots();
}
this.configuredProjects.push(project);
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions);
@ -1314,11 +1339,13 @@ namespace ts.server {
private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) {
if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) {
project.disableLanguageService();
project.stopWatchingWildCards();
project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize);
project.stopWatchingTypeRoots(WatcherCloseReason.ProjectReloadHitMaxSize);
}
else {
project.enableLanguageService();
project.watchWildcards(projectOptions.wildcardDirectories);
project.watchTypeRoots();
}
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors);
}
@ -1357,11 +1384,21 @@ namespace ts.server {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
}
watchClosedScriptInfo(info: ScriptInfo) {
private watchClosedScriptInfo(info: ScriptInfo) {
Debug.assert(!info.fileWatcher);
// do not watch files with mixed content - server doesn't know how to interpret it
if (!info.hasMixedContent) {
const { fileName } = info;
info.setWatcher(this.host.watchFile(fileName, (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind)));
info.fileWatcher = this.addFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, fileName,
(_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind)
);
}
}
private stopWatchingScriptInfo(info: ScriptInfo, reason: WatcherCloseReason) {
if (info.fileWatcher) {
this.closeFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, info.fileName, info.fileWatcher, reason);
info.fileWatcher = undefined;
}
}
@ -1387,7 +1424,7 @@ namespace ts.server {
}
if (info) {
if (openedByClient && !info.isScriptOpen()) {
info.stopWatcher();
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileOpened);
info.open(fileContent);
if (hasMixedContent) {
info.registerFileUpdate();
@ -1408,7 +1445,6 @@ namespace ts.server {
return this.filenameToScriptInfo.get(fileName);
}
setHostConfiguration(args: protocol.ConfigureRequestArguments) {
if (args.file) {
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file));
@ -1436,6 +1472,37 @@ namespace ts.server {
}
}
/* @internal */
closeFileWatcher(watchType: WatchType, project: Project, file: string, watcher: FileWatcher, reason: WatcherCloseReason) {
this.logger.info(`FileWatcher:: Close: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType} Reason: ${reason}`);
watcher.close();
}
/* @internal */
addFileWatcher(watchType: WatchType, project: Project, file: string, cb: FileWatcherCallback) {
this.logger.info(`FileWatcher:: Added: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`);
return this.host.watchFile(file, (fileName, eventKind) => {
this.logger.info(`FileWatcher:: File ${FileWatcherEventKind[eventKind]}: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`);
cb(fileName, eventKind);
});
}
/* @internal */
closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, recursive: boolean, reason: WatcherCloseReason) {
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Close: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType} Reason: ${reason}`);
watcher.close();
}
/* @internal */
addDirectoryWatcher(watchType: WatchType, project: Project, directory: string, cb: ServerDirectoryWatcherCallback, recursive: boolean) {
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Added: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType}`);
return this.host.watchDirectory(directory, fileName => {
const path = toNormalizedPath(getNormalizedAbsolutePath(fileName, directory));
this.logger.info(`DirectoryWatcher:: EventOn: ${directory} Trigger: ${fileName} Path: ${path} Project: ${project.getProjectName()} WatchType: ${watchType}`);
cb(path);
}, recursive);
}
closeLog() {
this.logger.close();
}

View File

@ -117,7 +117,7 @@ namespace ts.server {
private rootFilesMap: Map<ProjectRoot> = createMap<ProjectRoot>();
private program: Program;
private externalFiles: SortedReadonlyArray<string>;
private missingFilesMap: Map<FileWatcher> = createMap<FileWatcher>();
private missingFilesMap: Map<FileWatcher>;
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
@ -327,7 +327,9 @@ namespace ts.server {
this.lsHost = undefined;
// Clean up file watchers waiting for missing files
this.missingFilesMap.forEach(fileWatcher => fileWatcher.close());
cleanExistingMap(this.missingFilesMap, (missingFilePath, fileWatcher) => {
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose);
});
this.missingFilesMap = undefined;
// signal language service to release source files acquired from document registry
@ -639,38 +641,38 @@ namespace ts.server {
}
const missingFilePaths = this.program.getMissingFilePaths();
const missingFilePathsSet = arrayToSet(missingFilePaths);
const newMissingFilePathMap = arrayToSet(missingFilePaths);
// Update the missing file paths watcher
this.missingFilesMap = mutateExistingMapWithNewSet(
this.missingFilesMap, newMissingFilePathMap,
// Watch the missing files
missingFilePath => {
const fileWatcher = this.projectService.addFileWatcher(
WatchType.MissingFilePath, this, missingFilePath,
(filename: string, eventKind: FileWatcherEventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
this.missingFilesMap.forEach((fileWatcher, missingFilePath) => {
if (!missingFilePathsSet.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
fileWatcher.close();
}
});
if (this.projectKind === ProjectKind.Configured) {
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
}
// Missing files that are not yet watched should be added to the map.
for (const missingFilePath of missingFilePaths) {
if (!this.missingFilesMap.has(missingFilePath)) {
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (filename: string, eventKind: FileWatcherEventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
fileWatcher.close();
this.missingFilesMap.delete(missingFilePath);
if (this.projectKind === ProjectKind.Configured) {
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
}
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
}
});
this.missingFilesMap.set(missingFilePath, fileWatcher);
);
return fileWatcher;
},
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
(missingFilePath, fileWatcher) => {
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded);
}
}
);
}
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
@ -694,7 +696,7 @@ namespace ts.server {
}
isWatchedMissingFile(path: Path) {
return this.missingFilesMap.has(path);
return this.missingFilesMap && this.missingFilesMap.has(path);
}
getScriptInfoLSHost(fileName: string) {
@ -976,9 +978,10 @@ namespace ts.server {
*/
export class ConfiguredProject extends Project {
private typeAcquisition: TypeAcquisition;
private configFileWatcher: FileWatcher;
/* @internal */
configFileWatcher: FileWatcher;
private directoriesWatchedForWildcards: Map<WildCardDirectoryWatchers>;
private typeRootsWatchers: FileWatcher[];
private typeRootsWatchers: Map<FileWatcher>;
readonly canonicalConfigFilePath: NormalizedPath;
/* @internal */
@ -1023,7 +1026,7 @@ namespace ts.server {
}
getConfigFilePath() {
return this.getProjectName();
return asNormalizedPath(this.getProjectName());
}
enablePlugins() {
@ -1131,94 +1134,83 @@ namespace ts.server {
}));
}
watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) {
this.configFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind));
}
watchTypeRoots(callback: (project: ConfiguredProject, path: NormalizedPath) => void) {
const roots = this.getEffectiveTypeRoots();
const watchers: FileWatcher[] = [];
for (const root of roots) {
this.projectService.logger.info(`Add type root watcher for: ${root}`);
watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, toNormalizedPath(getNormalizedAbsolutePath(path, root))), /*recursive*/ false));
}
this.typeRootsWatchers = watchers;
}
private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) {
if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) {
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`);
this.directoriesWatchedForWildcards.set(directory, {
watcher: this.projectService.host.watchDirectory(
directory,
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toNormalizedPath(getNormalizedAbsolutePath(path, directory))),
recursive
),
recursive
});
}
}
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
if (wildcardDirectories) {
if (this.directoriesWatchedForWildcards) {
this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => {
const currentFlag = wildcardDirectories.get(directory);
// Remove already watching wild card if it isnt in updated map
if (currentFlag === undefined) {
this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`);
watcher.close();
this.directoriesWatchedForWildcards.delete(directory);
}
// Or if the recursive doesnt match (add the updated one here)
else {
const currentRecursive = (currentFlag & WatchDirectoryFlags.Recursive) !== 0;
if (currentRecursive !== recursive) {
this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`);
watcher.close();
this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true);
}
}
});
}
else {
this.directoriesWatchedForWildcards = createMap<WildCardDirectoryWatchers>();
}
wildcardDirectories.forEach((flag, directory) =>
this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false));
}
else {
this.stopWatchingWildCards();
}
this.directoriesWatchedForWildcards = mutateExistingMap(
this.directoriesWatchedForWildcards, wildcardDirectories,
// Watcher is same if the recursive flags match
({ recursive: existingRecursive }, flag) => {
// If the recursive dont match, it needs update
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
return existingRecursive !== recursive;
},
// Create new watch
(directory, flag) => {
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
return {
watcher: this.projectService.addDirectoryWatcher(
WatchType.WildCardDirectories, this, directory,
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
recursive
),
recursive
};
},
// Close existing watch thats not needed any more
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded
),
// Close existing watch that doesnt match in recursive flag
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged
)
);
}
stopWatchingWildCards() {
if (this.directoriesWatchedForWildcards) {
this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => {
this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`);
watcher.close();
});
this.directoriesWatchedForWildcards = undefined;
}
stopWatchingWildCards(reason: WatcherCloseReason) {
cleanExistingMap(
this.directoriesWatchedForWildcards,
(directory, { watcher, recursive }) =>
this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this,
directory, watcher, recursive, reason)
);
this.directoriesWatchedForWildcards = undefined;
}
watchTypeRoots() {
const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir));
this.typeRootsWatchers = mutateExistingMapWithNewSet(
this.typeRootsWatchers, newTypeRoots,
// Create new watch
root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root,
path => this.projectService.onTypeRootFileChanged(this, path), /*recursive*/ false
),
// Close existing watch thats not needed any more
(directory, watcher) => this.projectService.closeDirectoryWatcher(
WatchType.TypeRoot, this, directory, watcher, /*recursive*/ false, WatcherCloseReason.NotNeeded
)
);
}
stopWatchingTypeRoots(reason: WatcherCloseReason) {
cleanExistingMap(
this.typeRootsWatchers,
(directory, watcher) =>
this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this,
directory, watcher, /*recursive*/ false, reason)
);
this.typeRootsWatchers = undefined;
}
close() {
super.close();
if (this.configFileWatcher) {
this.configFileWatcher.close();
this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose);
this.configFileWatcher = undefined;
}
if (this.typeRootsWatchers) {
for (const watcher of this.typeRootsWatchers) {
watcher.close();
}
this.typeRootsWatchers = undefined;
}
this.stopWatchingWildCards();
this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose);
this.stopWatchingWildCards(WatcherCloseReason.ProjectClose);
}
addOpenRef() {

View File

@ -149,7 +149,8 @@ namespace ts.server {
readonly containingProjects: Project[] = [];
private formatCodeSettings: FormatCodeSettings;
private fileWatcher: FileWatcher;
/* @internal */
fileWatcher: FileWatcher;
private textStorage: TextStorage;
private isOpen: boolean;
@ -291,18 +292,6 @@ namespace ts.server {
}
}
setWatcher(watcher: FileWatcher): void {
this.stopWatcher();
this.fileWatcher = watcher;
}
stopWatcher() {
if (this.fileWatcher) {
this.fileWatcher.close();
this.fileWatcher = undefined;
}
}
getLatestVersion() {
return this.textStorage.getVersion();
}

View File

@ -226,6 +226,79 @@ namespace ts.server {
}
}
export function cleanExistingMap<T>(
existingMap: Map<T>,
onDeleteExistingValue: (key: string, existingValue: T) => void) {
if (existingMap) {
// Remove all
existingMap.forEach((existingValue, key) => {
existingMap.delete(key);
onDeleteExistingValue(key, existingValue);
});
}
}
export function mutateExistingMapWithNewSet<T>(
existingMap: Map<T>, newMap: Map<true>,
createNewValue: (key: string) => T,
onDeleteExistingValue: (key: string, existingValue: T) => void
): Map<T> {
return mutateExistingMap(
existingMap, newMap,
// Same value if the value is set in the map
/*isSameValue*/(_existingValue, _valueInNewMap) => true,
/*createNewValue*/(key, _valueInNewMap) => createNewValue(key),
onDeleteExistingValue,
// Should never be called since we say yes to same values all the time
/*OnDeleteExistingMismatchValue*/(_key, _existingValue) => notImplemented()
);
}
export function mutateExistingMap<T, U>(
existingMap: Map<T>, newMap: Map<U>,
isSameValue: (existingValue: T, valueInNewMap: U) => boolean,
createNewValue: (key: string, valueInNewMap: U) => T,
onDeleteExistingValue: (key: string, existingValue: T) => void,
OnDeleteExistingMismatchValue: (key: string, existingValue: T) => void
): Map<T> {
// If there are new values update them
if (newMap) {
if (existingMap) {
// Needs update
existingMap.forEach((existingValue, key) => {
const valueInNewMap = newMap.get(key);
// Existing value - remove it
if (valueInNewMap === undefined) {
existingMap.delete(key);
onDeleteExistingValue(key, existingValue);
}
// different value - remove it
else if (!isSameValue(existingValue, valueInNewMap)) {
existingMap.delete(key);
OnDeleteExistingMismatchValue(key, existingValue);
}
});
}
else {
// Create new
existingMap = createMap<T>();
}
// Add new values that are not already present
newMap.forEach((valueInNewMap, key) => {
if (!existingMap.has(key)) {
// New values
existingMap.set(key, createNewValue(key, valueInNewMap));
}
});
return existingMap;
}
cleanExistingMap(existingMap, onDeleteExistingValue);
return undefined;
}
export class ThrottledOperations {
private pendingTimeouts: Map<any> = createMap<any>();
constructor(private readonly host: ServerHost) {