adonisjs6-scheduler
Version:
Task scheduler for AdonisJS
117 lines (116 loc) • 4.96 kB
JavaScript
import cron from 'node-cron';
import AsyncLock from 'async-lock';
import { FsLoader, Kernel } from '@adonisjs/core/ace';
const lock = new AsyncLock();
const run = async (cb, options, app) => {
if (options.isOneService) {
const redis = await app.container.make('redis');
const lockKey = 'scheduler:lock:' + options.key;
const ttl = options.redisTTL; // 锁的有效期(秒)
const acquired = await redis.set(lockKey, '1', 'EX', ttl, 'NX');
if (!acquired)
return;
}
if (!options.enabled)
return await cb();
if (lock.isBusy(options.key)) {
if (options.onBusy) {
await options.onBusy();
}
return;
}
await lock.acquire(options.key, cb, { maxPending: 1, timeout: options.timeout });
};
export class Worker {
app;
tasks = [];
loaders = [];
booted = false;
constructor(app) {
this.app = app;
}
async boot() {
if (this.booted)
return;
const schedule = await this.app.container.make('scheduler');
await schedule.boot();
const fsLoader = new FsLoader(this.app.commandsPath());
this.loaders = [fsLoader];
this.app.rcFile.commands.forEach((commandModule) => {
this.loaders.push(() => typeof commandModule === 'function' ? commandModule() : this.app.import(commandModule));
});
this.booted = true;
}
async start() {
await this.boot();
const schedule = await this.app.container.make('scheduler');
const logger = await this.app.container.make('logger');
if (schedule.onStartingCallback) {
await schedule.onStartingCallback();
}
for (let index = 0; index < schedule.items.length; index++) {
const command = schedule.items[index];
this.tasks.push(cron.schedule(command.expression, async () => {
try {
switch (command.type) {
case 'command':
const ace = new Kernel(this.app);
for (const loader of this.loaders) {
ace.addLoader(loader);
}
for (const callback of command.beforeCallbacks) {
await callback();
}
await run(() => ace.exec(command.commandName, command.commandArgs), {
isOneService: command.config.isOneService,
redisTTL: command.config.redisTTL,
enabled: command.config.withoutOverlapping,
timeout: command.config.expiresAt,
key: `${index}-${command.commandName}-${command.commandArgs}`,
onBusy: () => {
logger.warn(`Command ${index}-${command.commandName}-${command.commandArgs} is busy`);
},
}, this.app);
for (const callback of command.afterCallbacks) {
await callback();
}
break;
case 'callback':
for (const callback of command.beforeCallbacks) {
await callback();
}
await run(() => command.callback(), {
isOneService: command.config.isOneService,
redisTTL: command.config.redisTTL,
enabled: command.config.withoutOverlapping,
timeout: command.config.expiresAt,
key: `${index}-callback`,
onBusy: () => {
logger.warn(`Callback ${index} is busy`);
},
}, this.app);
for (const callback of command.afterCallbacks) {
await callback();
}
default:
break;
}
}
catch (error) {
logger.error(error);
}
}, {
scheduled: command.config.enabled,
timezone: command.config.timezone,
runOnInit: command.config.enabled && command.config.immediate,
}));
}
logger.info(`Schedule worker started successfully.`);
if (schedule.onStartedCallback) {
await schedule.onStartedCallback();
}
}
async stop() {
await Promise.all(this.tasks.map((task) => task.stop()));
}
}