mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-17 07:20:51 +08:00
Translate UI, scheduler, validation, preload and maintenance messages dynamically, and propagate locale changes between workers. Harden update checks, downloads and terminal states, verify Debian pnpm, and ignore generated test artifacts.
118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
import { t } from './i18n';
|
|
import { spawn } from 'cross-spawn';
|
|
import taskLimit from './pLimit';
|
|
import Logger from '../loaders/logger';
|
|
import { ICron } from '../protos/cron';
|
|
import { CrontabModel, CrontabStatus } from '../data/cron';
|
|
import { killTask } from '../config/util';
|
|
import { RunningInstanceModel, InstanceStatus } from '../data/runningInstance';
|
|
import dayjs from 'dayjs';
|
|
import { observeChildProcess, asError } from './childProcess';
|
|
|
|
export function runCron(
|
|
cmd: string,
|
|
cron: ICron,
|
|
isCurrent: () => boolean = () => true,
|
|
): Promise<number | void> {
|
|
const execute = async () => {
|
|
try {
|
|
if (!isCurrent()) return;
|
|
// Check if the cron is already running and stop it (only if multiple instances are not allowed)
|
|
try {
|
|
const existingCron = await CrontabModel.findOne({
|
|
where: { id: Number(cron.id) },
|
|
});
|
|
|
|
// A mutation may commit while this queued run is reading the database.
|
|
if (!isCurrent() || !existingCron || existingCron.isDisabled) return;
|
|
|
|
// Default to single instance mode (0) for backward compatibility
|
|
const allowSingleInstances =
|
|
existingCron?.allow_multiple_instances === 0;
|
|
|
|
if (
|
|
allowSingleInstances &&
|
|
existingCron &&
|
|
existingCron.pid &&
|
|
(existingCron.status === CrontabStatus.running ||
|
|
existingCron.status === CrontabStatus.queued)
|
|
) {
|
|
Logger.info(
|
|
t('[schedule][停止已运行任务] 任务ID: %s, PID: %s'),
|
|
cron.id,
|
|
existingCron.pid,
|
|
);
|
|
await killTask(existingCron.pid, true);
|
|
// Mark old running instances as stopped
|
|
const stoppedAt = dayjs().unix();
|
|
await RunningInstanceModel.update(
|
|
{ status: InstanceStatus.stopped, finished_at: stoppedAt },
|
|
{
|
|
where: {
|
|
cron_id: Number(cron.id),
|
|
status: InstanceStatus.running,
|
|
},
|
|
},
|
|
);
|
|
// Update the status to idle after killing
|
|
await CrontabModel.update(
|
|
{ status: CrontabStatus.idle, pid: undefined },
|
|
{ where: { id: Number(cron.id) } },
|
|
);
|
|
}
|
|
} catch (error) {
|
|
Logger.error(
|
|
t('[schedule][检查已运行任务失败] 任务ID: %s, 错误: %s'),
|
|
cron.id,
|
|
error,
|
|
);
|
|
throw error;
|
|
}
|
|
|
|
// In particular, invalidation can happen while stopping a previous run.
|
|
if (!isCurrent()) return;
|
|
Logger.info(
|
|
t('[schedule][开始执行任务] 参数 %s'),
|
|
JSON.stringify({
|
|
...cron,
|
|
command: cmd,
|
|
}),
|
|
);
|
|
const cp = spawn(cmd, { shell: '/bin/bash' });
|
|
|
|
const { completed } = observeChildProcess(cp, {
|
|
onStderr: async (message) => {
|
|
Logger.info(
|
|
t('[schedule][任务标准错误] 命令: %s, 信息: %s'),
|
|
cmd,
|
|
message,
|
|
);
|
|
},
|
|
});
|
|
const result = await completed;
|
|
if (result.error) {
|
|
Logger.error(
|
|
t('[schedule][执行任务失败] 命令: %s, 错误: %s'),
|
|
cmd,
|
|
result.error.message,
|
|
);
|
|
}
|
|
Logger.info(
|
|
t('[schedule][执行任务结束] 任务ID: %s, 退出码: %j'),
|
|
cron.id,
|
|
result.code,
|
|
);
|
|
return { ...cron, command: cmd, pid: cp.pid, ...result } as any;
|
|
} catch (error) {
|
|
Logger.error(
|
|
t('[schedule][创建任务失败] 命令: %s, 错误: %s'),
|
|
cmd,
|
|
asError(error).message,
|
|
);
|
|
} finally {
|
|
taskLimit.removeQueuedCron(cron.id, execute);
|
|
}
|
|
};
|
|
return taskLimit.runWithCronLimit(cron, execute, undefined, isCurrent);
|
|
}
|