mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-01 05:13:50 +08:00
fix: invalidate queued cron revisions and enforce task process timeouts
This commit is contained in:
@@ -68,7 +68,11 @@ const addCron = (
|
||||
schedule,
|
||||
async () => {
|
||||
Logger.info('[schedule][准备运行任务] 命令: %s', item.command);
|
||||
await runCron(item.command, item);
|
||||
await runCron(
|
||||
item.command,
|
||||
item,
|
||||
() => scheduleStacks.get(item.id) === jobs,
|
||||
);
|
||||
},
|
||||
{
|
||||
name: `${item.id}: ${item.name || ''}`,
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface IDependencyFn<T> {
|
||||
export interface ICronFn<T> {
|
||||
(): Promise<T>;
|
||||
cron?: TCron;
|
||||
isCurrent?: () => boolean;
|
||||
}
|
||||
|
||||
export interface ISchedule {
|
||||
|
||||
+16
-9
@@ -50,11 +50,9 @@ class TaskLimit {
|
||||
Buffer.from(tlsConfig.clientCert),
|
||||
)
|
||||
: credentials.createInsecure();
|
||||
this._client = new ApiClient(
|
||||
`localhost:${config.grpcPort}`,
|
||||
creds,
|
||||
{ 'grpc.enable_http_proxy': 0 },
|
||||
);
|
||||
this._client = new ApiClient(`localhost:${config.grpcPort}`, creds, {
|
||||
'grpc.enable_http_proxy': 0,
|
||||
});
|
||||
}
|
||||
return this._client;
|
||||
}
|
||||
@@ -113,12 +111,15 @@ class TaskLimit {
|
||||
}
|
||||
}
|
||||
|
||||
public removeQueuedCron(id: string) {
|
||||
public removeQueuedCron(id: string, completed?: ICronFn<any>) {
|
||||
if (this.queuedCrons.has(id)) {
|
||||
const runs = this.queuedCrons.get(id);
|
||||
if (runs && runs.length > 0) {
|
||||
runs.pop();
|
||||
this.queuedCrons.set(id, runs);
|
||||
const remaining = completed
|
||||
? runs.filter((run) => run !== completed)
|
||||
: runs.slice(0, -1);
|
||||
if (remaining.length) this.queuedCrons.set(id, remaining);
|
||||
else this.queuedCrons.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,9 +144,15 @@ class TaskLimit {
|
||||
cron: TCron,
|
||||
fn: ICronFn<T>,
|
||||
options?: Partial<QueueAddOptions>,
|
||||
isCurrent: () => boolean = () => true,
|
||||
): Promise<T | void> {
|
||||
if (!isCurrent()) return;
|
||||
fn.cron = cron;
|
||||
let runs = this.queuedCrons.get(cron.id);
|
||||
fn.isCurrent = isCurrent;
|
||||
// Invalidated snapshots must not consume the new revision's repeat budget.
|
||||
const runs = this.queuedCrons
|
||||
.get(cron.id)
|
||||
?.filter((run) => run.isCurrent?.() !== false);
|
||||
const result = runs?.length ? [...runs, fn] : [fn];
|
||||
const repeatTimes = this.repeatCronNotifyMap.get(cron.id) || 0;
|
||||
if (result?.length > 5) {
|
||||
|
||||
+15
-4
@@ -8,15 +8,23 @@ import { RunningInstanceModel, InstanceStatus } from '../data/runningInstance';
|
||||
import dayjs from 'dayjs';
|
||||
import { observeChildProcess, asError } from './childProcess';
|
||||
|
||||
export function runCron(cmd: string, cron: ICron): Promise<number | void> {
|
||||
return taskLimit.runWithCronLimit(cron, async () => {
|
||||
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;
|
||||
@@ -56,6 +64,8 @@ export function runCron(cmd: string, cron: ICron): Promise<number | void> {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// In particular, invalidation can happen while stopping a previous run.
|
||||
if (!isCurrent()) return;
|
||||
Logger.info(
|
||||
`[schedule][开始执行任务] 参数 ${JSON.stringify({
|
||||
...cron,
|
||||
@@ -94,7 +104,8 @@ export function runCron(cmd: string, cron: ICron): Promise<number | void> {
|
||||
asError(error).message,
|
||||
);
|
||||
} finally {
|
||||
taskLimit.removeQueuedCron(cron.id);
|
||||
taskLimit.removeQueuedCron(cron.id, execute);
|
||||
}
|
||||
});
|
||||
};
|
||||
return taskLimit.runWithCronLimit(cron, execute, undefined, isCurrent);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user