2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2022-10-26 00:03:05 +00:00
|
|
|
import { MINUTE } from '../util/durations';
|
2022-02-25 18:37:15 +00:00
|
|
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
2021-09-27 18:22:46 +00:00
|
|
|
import { explodePromise } from '../util/explodePromise';
|
|
|
|
import { toLogFormat } from '../types/errors';
|
2021-09-17 18:27:53 +00:00
|
|
|
import * as log from '../logging/log';
|
2021-08-26 14:10:58 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
type TaskType = {
|
2022-10-26 00:03:05 +00:00
|
|
|
id: string;
|
|
|
|
startedAt: number | undefined;
|
2021-09-27 18:22:46 +00:00
|
|
|
suspend(): void;
|
|
|
|
resume(): void;
|
|
|
|
};
|
|
|
|
|
|
|
|
const tasks = new Set<TaskType>();
|
2022-01-26 20:39:24 +00:00
|
|
|
let shouldStartTimers = true;
|
2021-09-27 18:22:46 +00:00
|
|
|
|
|
|
|
export function suspendTasksWithTimeout(): void {
|
|
|
|
log.info(`TaskWithTimeout: suspending ${tasks.size} tasks`);
|
2022-01-26 20:39:24 +00:00
|
|
|
shouldStartTimers = false;
|
2021-09-27 18:22:46 +00:00
|
|
|
for (const task of tasks) {
|
|
|
|
task.suspend();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function resumeTasksWithTimeout(): void {
|
|
|
|
log.info(`TaskWithTimeout: resuming ${tasks.size} tasks`);
|
2022-01-26 20:39:24 +00:00
|
|
|
shouldStartTimers = true;
|
2021-09-27 18:22:46 +00:00
|
|
|
for (const task of tasks) {
|
|
|
|
task.resume();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-26 00:03:05 +00:00
|
|
|
export function reportLongRunningTasks(): void {
|
|
|
|
const now = Date.now();
|
|
|
|
for (const task of tasks) {
|
|
|
|
if (task.startedAt === undefined) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const duration = Math.max(0, now - task.startedAt);
|
|
|
|
if (duration > MINUTE) {
|
|
|
|
log.warn(
|
|
|
|
`TaskWithTimeout: ${task.id} has been running for ${duration}ms`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-02 21:11:18 +00:00
|
|
|
export default function createTaskWithTimeout<T, Args extends Array<unknown>>(
|
|
|
|
task: (...args: Args) => Promise<T>,
|
2020-04-13 17:37:29 +00:00
|
|
|
id: string,
|
|
|
|
options: { timeout?: number } = {}
|
2021-08-02 21:11:18 +00:00
|
|
|
): (...args: Args) => Promise<T> {
|
2022-10-26 00:03:05 +00:00
|
|
|
const timeout = options.timeout || 30 * MINUTE;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
const timeoutError = new Error(`${id || ''} task did not complete in time.`);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
return async (...args: Args) => {
|
|
|
|
let complete = false;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
let timer: NodeJS.Timeout | undefined;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
const { promise: timerPromise, reject } = explodePromise<never>();
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
const startTimer = () => {
|
|
|
|
stopTimer();
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
if (complete) {
|
|
|
|
return;
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2021-09-27 18:22:46 +00:00
|
|
|
|
2022-10-26 00:03:05 +00:00
|
|
|
entry.startedAt = Date.now();
|
2021-09-27 18:22:46 +00:00
|
|
|
timer = setTimeout(() => {
|
|
|
|
if (complete) {
|
2023-01-12 22:00:50 +00:00
|
|
|
log.warn(
|
|
|
|
`TaskWithTimeout: ${id} task timed out, but was already complete`
|
|
|
|
);
|
2021-09-27 18:22:46 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
complete = true;
|
2021-09-27 18:22:46 +00:00
|
|
|
tasks.delete(entry);
|
|
|
|
|
|
|
|
log.error(toLogFormat(timeoutError));
|
|
|
|
reject(timeoutError);
|
|
|
|
}, timeout);
|
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
const stopTimer = () => {
|
2022-02-25 18:37:15 +00:00
|
|
|
clearTimeoutIfNecessary(timer);
|
|
|
|
timer = undefined;
|
2021-09-27 18:22:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const entry: TaskType = {
|
2022-10-26 00:03:05 +00:00
|
|
|
id,
|
|
|
|
startedAt: undefined,
|
2023-01-12 22:00:50 +00:00
|
|
|
suspend: () => {
|
|
|
|
log.warn(`TaskWithTimeout: ${id} task suspended`);
|
|
|
|
stopTimer();
|
|
|
|
},
|
|
|
|
resume: () => {
|
|
|
|
log.warn(`TaskWithTimeout: ${id} task resumed`);
|
|
|
|
startTimer();
|
|
|
|
},
|
2021-09-27 18:22:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
tasks.add(entry);
|
2022-01-26 20:39:24 +00:00
|
|
|
if (shouldStartTimers) {
|
|
|
|
startTimer();
|
|
|
|
}
|
2021-09-27 18:22:46 +00:00
|
|
|
|
|
|
|
let result: unknown;
|
|
|
|
|
|
|
|
const run = async (): Promise<void> => {
|
|
|
|
result = await task(...args);
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
await Promise.race([run(), timerPromise]);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-27 18:22:46 +00:00
|
|
|
return result as T;
|
|
|
|
} finally {
|
|
|
|
complete = true;
|
|
|
|
tasks.delete(entry);
|
|
|
|
stopTimer();
|
|
|
|
}
|
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|