1import { binarySearch, Comparer, getBaseFileName, identity, Map, perfLogger, SortedArray } from "./_namespaces/ts"; 2import { Logger, LogLevel, NormalizedPath, ServerHost } from "./_namespaces/ts.server"; 3 4/** @internal */ 5export class ThrottledOperations { 6 private readonly pendingTimeouts = new Map<string, any>(); 7 private readonly logger?: Logger | undefined; 8 constructor(private readonly host: ServerHost, logger: Logger) { 9 this.logger = logger.hasLevel(LogLevel.verbose) ? logger : undefined; 10 } 11 12 /** 13 * Wait `number` milliseconds and then invoke `cb`. If, while waiting, schedule 14 * is called again with the same `operationId`, cancel this operation in favor 15 * of the new one. (Note that the amount of time the canceled operation had been 16 * waiting does not affect the amount of time that the new operation waits.) 17 */ 18 public schedule(operationId: string, delay: number, cb: () => void) { 19 const pendingTimeout = this.pendingTimeouts.get(operationId); 20 if (pendingTimeout) { 21 // another operation was already scheduled for this id - cancel it 22 this.host.clearTimeout(pendingTimeout); 23 } 24 // schedule new operation, pass arguments 25 this.pendingTimeouts.set(operationId, this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb)); 26 if (this.logger) { 27 this.logger.info(`Scheduled: ${operationId}${pendingTimeout ? ", Cancelled earlier one" : ""}`); 28 } 29 } 30 31 public cancel(operationId: string) { 32 const pendingTimeout = this.pendingTimeouts.get(operationId); 33 if (!pendingTimeout) return false; 34 this.host.clearTimeout(pendingTimeout); 35 return this.pendingTimeouts.delete(operationId); 36 } 37 38 private static run(self: ThrottledOperations, operationId: string, cb: () => void) { 39 perfLogger.logStartScheduledOperation(operationId); 40 self.pendingTimeouts.delete(operationId); 41 if (self.logger) { 42 self.logger.info(`Running: ${operationId}`); 43 } 44 cb(); 45 perfLogger.logStopScheduledOperation(); 46 } 47} 48 49/** @internal */ 50export class GcTimer { 51 private timerId: any; 52 constructor(private readonly host: ServerHost, private readonly delay: number, private readonly logger: Logger) { 53 } 54 55 public scheduleCollect() { 56 if (!this.host.gc || this.timerId !== undefined) { 57 // no global.gc or collection was already scheduled - skip this request 58 return; 59 } 60 this.timerId = this.host.setTimeout(GcTimer.run, this.delay, this); 61 } 62 63 private static run(self: GcTimer) { 64 self.timerId = undefined; 65 66 perfLogger.logStartScheduledOperation("GC collect"); 67 const log = self.logger.hasLevel(LogLevel.requestTime); 68 const before = log && self.host.getMemoryUsage!(); // TODO: GH#18217 69 70 self.host.gc!(); // TODO: GH#18217 71 if (log) { 72 const after = self.host.getMemoryUsage!(); // TODO: GH#18217 73 self.logger.perftrc(`GC::before ${before}, after ${after}`); 74 } 75 perfLogger.logStopScheduledOperation(); 76 } 77} 78 79/** @internal */ 80export function getBaseConfigFileName(configFilePath: NormalizedPath): "tsconfig.json" | "jsconfig.json" | undefined { 81 const base = getBaseFileName(configFilePath); 82 return base === "tsconfig.json" || base === "jsconfig.json" ? base : undefined; 83} 84 85/** @internal */ 86export function removeSorted<T>(array: SortedArray<T>, remove: T, compare: Comparer<T>): void { 87 if (!array || array.length === 0) { 88 return; 89 } 90 91 if (array[0] === remove) { 92 array.splice(0, 1); 93 return; 94 } 95 96 const removeIndex = binarySearch(array, remove, identity, compare); 97 if (removeIndex >= 0) { 98 array.splice(removeIndex, 1); 99 } 100} 101 102const indentStr = "\n "; 103 104/** @internal */ 105export function indent(str: string): string { 106 return indentStr + str.replace(/\n/g, indentStr); 107} 108 109/** 110 * Put stringified JSON on the next line, indented. 111 * 112 * @internal 113 */ 114export function stringifyIndented(json: {}): string { 115 return indentStr + JSON.stringify(json); 116} 117