mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-06 07:10:41 +08:00
Use MikroORM's official Node SQLite dialect with bounded database isolation, preserve legacy schema and data for rollback, and require Node 22.17 or newer. Update Debian runtimes and retain ARMv6 musl compatibility without a SQLite addon. Complete short archive writes, stop orphaned SQL workers after owner death, preserve buffered startup requests, and prestart the archived backend on every platform. Cover real package installation, migration, failure recovery, and legacy database compatibility. Keep authentication storage failures distinct from expired sessions with a pinned Keyv runtime, propagated cache errors, and retryable HTTP 503 responses. Preserve genuine 401 failures and verify legacy cache bytes, initialization safety, and session recovery.
116 lines
3.7 KiB
TypeScript
116 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({
|
|
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.nativeUpdate(
|
|
{
|
|
cron_id: Number(cron.id),
|
|
status: InstanceStatus.running,
|
|
},
|
|
{ status: InstanceStatus.stopped, finished_at: stoppedAt },
|
|
);
|
|
// Update the status to idle after killing
|
|
await CrontabModel.nativeUpdate(
|
|
{ id: Number(cron.id) },
|
|
{ status: CrontabStatus.idle, pid: undefined },
|
|
);
|
|
}
|
|
} 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);
|
|
}
|