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