introduce ThrottledOperations

This commit is contained in:
Vladimir Matveev 2016-06-29 12:23:25 -07:00
parent cad8049482
commit 641c2ffd5e
5 changed files with 126 additions and 104 deletions

View File

@ -108,7 +108,6 @@ namespace ts.server {
currentPath = parentPath;
parentPath = getDirectoryPath(parentPath);
}
}
}
@ -139,21 +138,21 @@ namespace ts.server {
/**
* list of open files
*/
openFiles: ScriptInfo[] = [];
readonly openFiles: ScriptInfo[] = [];
private readonly directoryWatchers: DirectoryWatchers;
private readonly throttledOperations: ThrottledOperations;
private readonly hostConfiguration: HostConfiguration;
private timerForDetectingProjectFileListChanges: Map<any> = {};
constructor(public readonly host: ServerHost,
public readonly logger: Logger,
public readonly cancellationToken: HostCancellationToken,
private readonly useOneInferredProject: boolean,
private readonly useSingleInferredProject: boolean,
private readonly eventHandler?: ProjectServiceEventHandler) {
this.directoryWatchers = new DirectoryWatchers(this);
this.throttledOperations = new ThrottledOperations(host);
// ts.disableIncrementalParsing = true;
this.hostConfiguration = {
@ -243,14 +242,10 @@ namespace ts.server {
}
this.log(`Detected source file changes: ${fileName}`);
const timeoutId = this.timerForDetectingProjectFileListChanges[project.configFileName];
if (timeoutId) {
this.host.clearTimeout(timeoutId);
}
this.timerForDetectingProjectFileListChanges[project.configFileName] = this.host.setTimeout(
() => this.handleChangeInSourceFileForConfiguredProject(project),
250
);
this.throttledOperations.schedule(
project.configFileName,
250,
() => this.handleChangeInSourceFileForConfiguredProject(project));
}
private handleChangeInSourceFileForConfiguredProject(project: ConfiguredProject) {
@ -357,7 +352,7 @@ namespace ts.server {
// create new inferred project p with the newly opened file as root
// or add root to existing inferred project if 'useOneInferredProject' is true
const inferredProject = this.createInferredProjectWithRootFileIfNecessary(info);
if (!this.useOneInferredProject) {
if (!this.useSingleInferredProject) {
// if useOneInferredProject is not set then try to fixup ownership of open files
for (const f of this.openFiles) {
@ -755,7 +750,7 @@ namespace ts.server {
}
createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) {
const useExistingProject = this.useOneInferredProject && this.inferredProjects.length;
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
const project = useExistingProject
? this.inferredProjects[0]
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true);
@ -1004,42 +999,42 @@ namespace ts.server {
// this.openFilesReferenced.push(rootFile);
// }
// }
// if (rootFile.containingProjects.some(p => p.projectKind !== ProjectKind.Inferred)) {
// // file was included in non-inferred project - drop old inferred project
// if (rootFile.containingProjects.some(p => p.projectKind !== ProjectKind.Inferred)) {
// // file was included in non-inferred project - drop old inferred project
// }
// else {
// openFileRoots.push(rootFile);
// }
// let inferredProjectsToRemove: Project[];
// for (const p of rootFile.containingProjects) {
// if (p.projectKind !== ProjectKind.Inferred) {
// // file was included in non-inferred project - drop old inferred project
// }
// }
// }
// else {
// openFileRoots.push(rootFile);
// }
// let inferredProjectsToRemove: Project[];
// for (const p of rootFile.containingProjects) {
// if (p.projectKind !== ProjectKind.Inferred) {
// // file was included in non-inferred project - drop old inferred project
// }
// }
// const rootedProject = rootFile.defaultProject;
// const referencingProjects = this.findReferencingProjects(rootFile, rootedProject);
// const rootedProject = rootFile.defaultProject;
// const referencingProjects = this.findReferencingProjects(rootFile, rootedProject);
// if (rootFile.defaultProject && rootFile.defaultProject.projectKind !== ProjectKind.Inferred) {
// // If the root file has already been added into a configured project,
// // meaning the original inferred project is gone already.
// if (rootedProject.projectKind === ProjectKind.Inferred) {
// this.removeProject(rootedProject);
// }
// this.openFileRootsConfigured.push(rootFile);
// }
// else {
// if (referencingProjects.length === 0) {
// rootFile.defaultProject = rootedProject;
// openFileRoots.push(rootFile);
// }
// else {
// // remove project from inferred projects list because root captured
// this.removeProject(rootedProject);
// this.openFilesReferenced.push(rootFile);
// }
// }
// if (rootFile.defaultProject && rootFile.defaultProject.projectKind !== ProjectKind.Inferred) {
// // If the root file has already been added into a configured project,
// // meaning the original inferred project is gone already.
// if (rootedProject.projectKind === ProjectKind.Inferred) {
// this.removeProject(rootedProject);
// }
// this.openFileRootsConfigured.push(rootFile);
// }
// else {
// if (referencingProjects.length === 0) {
// rootFile.defaultProject = rootedProject;
// openFileRoots.push(rootFile);
// }
// else {
// // remove project from inferred projects list because root captured
// this.removeProject(rootedProject);
// this.openFilesReferenced.push(rootFile);
// }
// }
// }
// this.openFileRoots = openFileRoots;

View File

@ -91,8 +91,8 @@ namespace ts.server {
}
class IOSession extends Session {
constructor(host: ServerHost, cancellationToken: HostCancellationToken, useOneInferredProject: boolean, logger: ts.server.Logger) {
super(host, cancellationToken, useOneInferredProject, Buffer.byteLength, process.hrtime, logger);
constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: ts.server.Logger) {
super(host, cancellationToken, useSingleInferredProject, Buffer.byteLength, process.hrtime, logger);
}
exit() {
@ -304,8 +304,8 @@ namespace ts.server {
};
};
const useOneInferredProject = sys.args.some(arg => arg === "--useOneInferredProject");
const ioSession = new IOSession(sys, cancellationToken, useOneInferredProject, logger);
const useSingleInferredProject = sys.args.some(arg => arg === "--useSingleInferredProject");
const ioSession = new IOSession(sys, cancellationToken, useSingleInferredProject, logger);
process.on("uncaughtException", function(err: Error) {
ioSession.logError(err, "unknown");
});

View File

@ -179,12 +179,12 @@ namespace ts.server {
constructor(
private host: ServerHost,
cancellationToken: HostCancellationToken,
useOneInferredProject: boolean,
useSingleInferredProject: boolean,
private byteLength: (buf: string, encoding?: string) => number,
private hrtime: (start?: number[]) => number[],
private logger: Logger) {
this.projectService =
new ProjectService(host, logger, cancellationToken, useOneInferredProject, (eventName, project, fileName) => {
new ProjectService(host, logger, cancellationToken, useSingleInferredProject, (eventName, project, fileName) => {
this.handleEvent(eventName, project, fileName);
});
}

View File

@ -1,7 +1,5 @@
/// <reference path="..\services\services.ts" />
/* tslint:disable:no-null-keyword */
namespace ts.server {
export interface Logger {
close(): void;
@ -74,14 +72,16 @@ namespace ts.server {
}
export interface NormalizedPathMap<T> {
get (path: NormalizedPath): T;
set (path: NormalizedPath, value: T): void;
get(path: NormalizedPath): T;
set(path: NormalizedPath, value: T): void;
contains(path: NormalizedPath): boolean;
remove(path: NormalizedPath): void;
}
export function createNormalizedPathMap<T>(): NormalizedPathMap<T> {
/* tslint:disable:no-null-keyword */
const map: Map<T> = Object.create(null);
/* tslint:enable:no-null-keyword */
return {
get(path) {
return map[path];
@ -97,48 +97,49 @@ namespace ts.server {
}
};
}
function throwLanguageServiceIsDisabledError() {;
function throwLanguageServiceIsDisabledError() {
;
throw new Error("LanguageService is disabled");
}
export const nullLanguageService: LanguageService = {
cleanupSemanticCache: (): any => throwLanguageServiceIsDisabledError(),
getSyntacticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
getSemanticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
getCompilerOptionsDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
getSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
cleanupSemanticCache: (): any => throwLanguageServiceIsDisabledError(),
getSyntacticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
getSemanticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
getCompilerOptionsDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
getSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
getEncodedSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
getSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
getEncodedSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
getCompletionsAtPosition: (): any => throwLanguageServiceIsDisabledError(),
findReferences: (): any => throwLanguageServiceIsDisabledError(),
getCompletionEntryDetails: (): any => throwLanguageServiceIsDisabledError(),
getQuickInfoAtPosition: (): any => throwLanguageServiceIsDisabledError(),
findRenameLocations: (): any => throwLanguageServiceIsDisabledError(),
getNameOrDottedNameSpan: (): any => throwLanguageServiceIsDisabledError(),
getBreakpointStatementAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getBraceMatchingAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getSignatureHelpItems: (): any => throwLanguageServiceIsDisabledError(),
getDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getRenameInfo: (): any => throwLanguageServiceIsDisabledError(),
getTypeDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getReferencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getDocumentHighlights: (): any => throwLanguageServiceIsDisabledError(),
getOccurrencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getNavigateToItems: (): any => throwLanguageServiceIsDisabledError(),
getNavigationBarItems: (): any => throwLanguageServiceIsDisabledError(),
getOutliningSpans: (): any => throwLanguageServiceIsDisabledError(),
getTodoComments: (): any => throwLanguageServiceIsDisabledError(),
getIndentationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getFormattingEditsForRange: (): any => throwLanguageServiceIsDisabledError(),
getFormattingEditsForDocument: (): any => throwLanguageServiceIsDisabledError(),
getFormattingEditsAfterKeystroke: (): any => throwLanguageServiceIsDisabledError(),
getDocCommentTemplateAtPosition: (): any => throwLanguageServiceIsDisabledError(),
isValidBraceCompletionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getEmitOutput: (): any => throwLanguageServiceIsDisabledError(),
getProgram: (): any => throwLanguageServiceIsDisabledError(),
getNonBoundSourceFile: (): any => throwLanguageServiceIsDisabledError(),
dispose: (): any => throwLanguageServiceIsDisabledError(),
getSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
getEncodedSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
getCompletionsAtPosition: (): any => throwLanguageServiceIsDisabledError(),
findReferences: (): any => throwLanguageServiceIsDisabledError(),
getCompletionEntryDetails: (): any => throwLanguageServiceIsDisabledError(),
getQuickInfoAtPosition: (): any => throwLanguageServiceIsDisabledError(),
findRenameLocations: (): any => throwLanguageServiceIsDisabledError(),
getNameOrDottedNameSpan: (): any => throwLanguageServiceIsDisabledError(),
getBreakpointStatementAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getBraceMatchingAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getSignatureHelpItems: (): any => throwLanguageServiceIsDisabledError(),
getDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getRenameInfo: (): any => throwLanguageServiceIsDisabledError(),
getTypeDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getReferencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getDocumentHighlights: (): any => throwLanguageServiceIsDisabledError(),
getOccurrencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getNavigateToItems: (): any => throwLanguageServiceIsDisabledError(),
getNavigationBarItems: (): any => throwLanguageServiceIsDisabledError(),
getOutliningSpans: (): any => throwLanguageServiceIsDisabledError(),
getTodoComments: (): any => throwLanguageServiceIsDisabledError(),
getIndentationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getFormattingEditsForRange: (): any => throwLanguageServiceIsDisabledError(),
getFormattingEditsForDocument: (): any => throwLanguageServiceIsDisabledError(),
getFormattingEditsAfterKeystroke: (): any => throwLanguageServiceIsDisabledError(),
getDocCommentTemplateAtPosition: (): any => throwLanguageServiceIsDisabledError(),
isValidBraceCompletionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getEmitOutput: (): any => throwLanguageServiceIsDisabledError(),
getProgram: (): any => throwLanguageServiceIsDisabledError(),
getNonBoundSourceFile: (): any => throwLanguageServiceIsDisabledError(),
dispose: (): any => throwLanguageServiceIsDisabledError(),
};
export interface ServerLanguageServiceHost {
@ -172,4 +173,24 @@ namespace ts.server {
export function makeInferredProjectName(counter: number) {
return `/dev/null/inferredProject${counter}*`;
}
export class ThrottledOperations {
private pendingTimeouts: Map<any> = {};
constructor(private readonly host: ServerHost) {
}
public schedule(operationId: string, delay: number, cb: () => void) {
if (hasProperty(this.pendingTimeouts, operationId)) {
// another operation was already scheduled for this id - cancel it
this.host.clearTimeout(this.pendingTimeouts[operationId]);
}
// schedule new operation, pass arguments
this.pendingTimeouts[operationId] = this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb);
}
private static run(self: ThrottledOperations, operationId: string, cb: () => void) {
delete self.pendingTimeouts[operationId];
cb();
}
}
}

View File

@ -145,7 +145,10 @@ namespace ts {
private fs: ts.FileMap<FSEntry>;
private getCanonicalFileName: (s: string) => string;
private toPath: (f: string) => Path;
private callbackQueue: TimeOutCallback[] = [];
private nextTimeoutId = 0;
private callbacks: { [n: number]: TimeOutCallback } = {};
readonly watchedDirectories: Map<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {};
readonly watchedFiles: Map<FileWatcherCallback[]> = {};
@ -283,25 +286,28 @@ namespace ts {
}
// TOOD: record and invoke callbacks to simulate timer events
readonly setTimeout = (callback: TimeOutCallback, time: number) => {
this.callbackQueue.push(callback);
return this.callbackQueue.length - 1;
readonly setTimeout = (callback: TimeOutCallback, time: number, ...args: any[]) => {
const timeoutId = this.nextTimeoutId;
this.nextTimeoutId++;
this.callbacks[timeoutId] = callback.bind(undefined, ...args);
return timeoutId;
};
readonly clearTimeout = (timeoutId: any): void => {
if (typeof timeoutId === "number") {
this.callbackQueue.splice(timeoutId, 1);
delete this.callbacks[timeoutId];
}
};
checkTimeoutQueueLength(expected: number) {
assert.equal(this.callbackQueue.length, expected, `expected ${expected} timeout callbacks queued but found ${this.callbackQueue.length}.`);
const callbacksCount = sizeOfMap(this.callbacks);
assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`);
}
runQueuedTimeoutCallbacks() {
for (const callback of this.callbackQueue) {
callback();
for (const id in this.callbacks) {
this.callbacks[id]();
}
this.callbackQueue = [];
this.callbacks = [];
}
readonly readFile = (s: string) => (<File>this.fs.get(this.toPath(s))).content;