diff --git a/back/schedule/delCron.ts b/back/schedule/delCron.ts index 6e35c080..f711deea 100644 --- a/back/schedule/delCron.ts +++ b/back/schedule/delCron.ts @@ -13,7 +13,20 @@ const delCron = ( '[schedule][取消定时任务] 任务ID: %s', id, ); - scheduleStacks.get(id)?.forEach(x => x.cancel()); + // 过滤掉 nodeSchedule.scheduleJob() 对无效表达式返回的 null, + // 否则对 null 调 cancel() 会让整个取消流程抛出 UNKNOWN 错误, + // 进而导致 HTTP 端的 remove() 跳过 setCrontab(),造成 crontab.list 残留。 + scheduleStacks.get(id)?.filter((x) => x != null).forEach((x) => { + try { + x.cancel(); + } catch (error: any) { + Logger.warn( + '[schedule][取消任务失败] 任务ID: %s, 错误: %s', + id, + error?.message || error, + ); + } + }); scheduleStacks.delete(id); } } diff --git a/back/services/cron.ts b/back/services/cron.ts index 16e24693..518fa364 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -106,15 +106,24 @@ export default class CronService { } if (this.shouldUseCronClient(doc)) { - await cronClient.addCron([ - { - name: doc.name || '', - id: String(doc.id), - schedule: doc.schedule!, - command: this.makeCommand(doc), - extra_schedules: doc.extra_schedules || [], - }, - ]); + try { + await cronClient.addCron([ + { + name: doc.name || '', + id: String(doc.id), + schedule: doc.schedule!, + command: this.makeCommand(doc), + extra_schedules: doc.extra_schedules || [], + }, + ]); + } catch (error: any) { + // 调度器注册为尽力而为,失败时不阻断 crontab.list 与系统 crontab 的同步, + // 调度器重启后会重新注册(见 autosave_crontab) + this.logger.warn( + '[crontab] Failed to register cron job in scheduler:', + error?.message || error, + ); + } } await this.setCrontab(); @@ -136,18 +145,32 @@ export default class CronService { return newDoc; } - await cronClient.delCron([String(newDoc.id)]); + try { + await cronClient.delCron([String(newDoc.id)]); + } catch (error: any) { + this.logger.warn( + '[crontab] Failed to unregister cron job in scheduler:', + error?.message || error, + ); + } if (this.shouldUseCronClient(newDoc)) { - await cronClient.addCron([ - { - name: doc.name || '', - id: String(newDoc.id), - schedule: newDoc.schedule!, - command: this.makeCommand(newDoc), - extra_schedules: newDoc.extra_schedules || [], - }, - ]); + try { + await cronClient.addCron([ + { + name: doc.name || '', + id: String(newDoc.id), + schedule: newDoc.schedule!, + command: this.makeCommand(newDoc), + extra_schedules: newDoc.extra_schedules || [], + }, + ]); + } catch (error: any) { + this.logger.warn( + '[crontab] Failed to register cron job in scheduler:', + error?.message || error, + ); + } } await this.setCrontab(); @@ -233,7 +256,14 @@ export default class CronService { public async remove(ids: number[]) { await CrontabModel.destroy({ where: { id: ids } }); - await cronClient.delCron(ids.map(String)); + try { + await cronClient.delCron(ids.map(String)); + } catch (error: any) { + this.logger.warn( + '[crontab] Failed to unregister cron job in scheduler:', + error?.message || error, + ); + } await this.setCrontab(); } @@ -687,7 +717,14 @@ export default class CronService { public async disabled(ids: number[]) { await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } }); - await cronClient.delCron(ids.map(String)); + try { + await cronClient.delCron(ids.map(String)); + } catch (error: any) { + this.logger.warn( + '[crontab] Failed to unregister cron job in scheduler:', + error?.message || error, + ); + } await this.setCrontab(); } @@ -708,7 +745,14 @@ export default class CronService { return; } - await cronClient.addCron(crons); + try { + await cronClient.addCron(crons); + } catch (error: any) { + this.logger.warn( + '[crontab] Failed to register cron job in scheduler:', + error?.message || error, + ); + } await this.setCrontab(); } @@ -886,8 +930,19 @@ export default class CronService { await writeFileWithLock(config.crontabFile, ''); return; } - await cronClient.addCron(regularCrons); - this.setCrontab(tabs); + + // 先同步 crontab.list 与系统 crontab,确保其始终反映数据库真实状态。 + // gRPC 调度注册为尽力而为:失败时不阻断文件同步,调度器重启后会重新注册。 + // 这避免了因调度器短暂不可用导致 crontab.list 与数据库脱节(订阅更新误判任务已存在)。 + await this.setCrontab(tabs); + try { + await cronClient.addCron(regularCrons); + } catch (error: any) { + this.logger.warn( + '[crontab] Failed to register cron job in scheduler:', + error?.message || error, + ); + } } public async bootTask() {