• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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