@penkov/tasks_queue
Version:
A lightweight PostgreSQL-backed task queue system with scheduling, retries, backoff strategies, and priority handling. Designed for efficiency and observability in modern Node.js applications.
871 lines (857 loc) • 34.8 kB
JavaScript
;
var scats = require('scats');
var log4js = require('log4js');
var applicationMetrics = require('application-metrics');
var tslib = require('tslib');
var common = require('@nestjs/common');
var core = require('@nestjs/core');
exports.TaskStatus = void 0;
(function (TaskStatus) {
TaskStatus["pending"] = "pending";
TaskStatus["in_progress"] = "in_progress";
TaskStatus["finished"] = "finished";
TaskStatus["error"] = "error";
})(exports.TaskStatus || (exports.TaskStatus = {}));
exports.BackoffType = void 0;
(function (BackoffType) {
BackoffType["constant"] = "constant";
BackoffType["linear"] = "linear";
BackoffType["exponential"] = "exponential";
})(exports.BackoffType || (exports.BackoffType = {}));
exports.TaskPeriodType = void 0;
(function (TaskPeriodType) {
TaskPeriodType["fixed_rate"] = "fixed_rate";
TaskPeriodType["fixed_delay"] = "fixed_delay";
})(exports.TaskPeriodType || (exports.TaskPeriodType = {}));
exports.MissedRunStrategy = void 0;
(function (MissedRunStrategy) {
MissedRunStrategy["catch_up"] = "catch_up";
MissedRunStrategy["skip_missed"] = "skip_missed";
})(exports.MissedRunStrategy || (exports.MissedRunStrategy = {}));
class TaskFailed extends Error {
payload;
constructor(message, payload) {
super(message);
this.payload = payload;
}
}
class TimeUtils {
static second = 1000;
static minute = TimeUtils.second * 60;
static hour = TimeUtils.minute * 60;
static day = TimeUtils.hour * 24;
}
const defaultLoopInterval = TimeUtils.minute;
var PipelineNextOperationType;
(function (PipelineNextOperationType) {
PipelineNextOperationType[PipelineNextOperationType["PollNext"] = 0] = "PollNext";
PipelineNextOperationType[PipelineNextOperationType["Sleep"] = 1] = "Sleep";
})(PipelineNextOperationType || (PipelineNextOperationType = {}));
const PipelineNextOperationFactory = {
pollNext: {
type: PipelineNextOperationType.PollNext,
},
sleep: (delayMs = defaultLoopInterval) => ({
type: PipelineNextOperationType.Sleep,
delayMs,
}),
};
const logger$4 = log4js.getLogger("TasksPipeline");
const noop = () => {
};
class TasksPipeline {
maxConcurrentTasks;
pollNextTask;
peekNextStartAfter;
processTask;
loopInterval;
tasksInProcess = 0;
loopRunning = false;
nextLoopTime = 0;
periodicTaskFetcher = null;
stopRequested = false;
tasksCountListener = noop;
loopTimer;
constructor(maxConcurrentTasks, pollNextTask, peekNextStartAfter, processTask, loopInterval = defaultLoopInterval) {
this.maxConcurrentTasks = maxConcurrentTasks;
this.pollNextTask = pollNextTask;
this.peekNextStartAfter = peekNextStartAfter;
this.processTask = processTask;
this.loopInterval = loopInterval;
this.loopTimer = async () => {
this.periodicTaskFetcher = null;
let sleepInterval = this.loopInterval;
try {
sleepInterval = await this.loop();
}
finally {
if (!this.periodicTaskFetcher) {
this.nextLoopTime = Date.now() + sleepInterval;
this.periodicTaskFetcher = setTimeout(() => this.loopTimer(), Math.min(this.loopInterval, sleepInterval));
}
}
};
}
start() {
this.stopRequested = false;
scats.option(this.periodicTaskFetcher).foreach((t) => clearTimeout(t));
this.nextLoopTime = Date.now() + 1;
this.periodicTaskFetcher = setTimeout(() => this.loopTimer(), 1);
}
async stop() {
this.stopRequested = true;
scats.option(this.periodicTaskFetcher).foreach((t) => clearTimeout(t));
if (this.tasksInProcess > 0) {
await new Promise((resolve) => {
this.tasksCountListener = (n) => {
if (n <= 0) {
this.tasksCountListener = noop;
resolve();
}
};
});
}
}
triggerLoop() {
if (!this.loopRunning && !this.stopRequested) {
setTimeout(() => this.loop(), 1);
}
}
async loop() {
if (this.loopRunning || this.stopRequested) {
return this.loopInterval;
}
this.loopRunning = true;
let sleepInterval = this.loopInterval;
try {
let nextOp = PipelineNextOperationFactory.pollNext;
while (nextOp.type !== PipelineNextOperationType.Sleep) {
if (this.tasksInProcess < this.maxConcurrentTasks &&
!this.stopRequested) {
nextOp = await this.fetchNextTask();
}
else {
nextOp = PipelineNextOperationFactory.sleep(this.loopInterval);
}
}
sleepInterval = Math.min(nextOp.delayMs, this.loopInterval);
}
finally {
this.loopRunning = false;
}
const nextTriggerTime = Date.now() + sleepInterval;
if (this.periodicTaskFetcher && this.nextLoopTime > nextTriggerTime) {
logger$4.trace(`Resetting loop timer to closer time: from ${new Date(this.nextLoopTime)} to new ${new Date(nextTriggerTime)}`);
clearTimeout(this.periodicTaskFetcher);
this.nextLoopTime = nextTriggerTime;
this.periodicTaskFetcher = setTimeout(() => this.loopTimer(), Math.min(this.loopInterval, sleepInterval));
}
return sleepInterval;
}
async fetchNextTask() {
const task = await this.pollNextTask();
return task.match({
some: async (t) => {
this.tasksInProcess++;
setImmediate(() => this.processTaskInLoop(t));
return this.tasksInProcess < this.maxConcurrentTasks
? PipelineNextOperationFactory.pollNext
: PipelineNextOperationFactory.sleep(this.loopInterval);
},
none: async () => {
const nextTimeOpt = await this.peekNextStartAfter();
const delayMs = nextTimeOpt
.map((startAfter) => {
const delay = startAfter.getTime() - Date.now();
return Math.max(0, delay);
})
.getOrElseValue(this.loopInterval);
return PipelineNextOperationFactory.sleep(delayMs);
},
});
}
async processTaskInLoop(t) {
try {
logger$4.debug(`Starting task (id=${t.id}) in queue ${t.queue} (active ${this.tasksInProcess} of ${this.maxConcurrentTasks})`);
await this.processTask(t);
}
catch (error) {
logger$4.warn(`Failed to process task ${t.id} in queue ${t.queue} `, error);
}
finally {
this.taskIsDone();
logger$4.debug(`Finished working with task ${t.id} in queue ${t.queue} (active ${this.tasksInProcess} of ${this.maxConcurrentTasks})`);
setImmediate(() => this.loop());
}
}
taskIsDone() {
this.tasksInProcess--;
this.tasksCountListener(this.tasksInProcess);
}
}
const logger$3 = log4js.getLogger("TasksQueueWorker");
class TasksQueueWorker {
tasksQueueDao;
workers = new scats.mutable.HashMap();
pipeline;
constructor(tasksQueueDao, concurrency = 4, loopInterval = TimeUtils.minute) {
this.tasksQueueDao = tasksQueueDao;
this.pipeline = new TasksPipeline(concurrency, () => this.tasksQueueDao.nextPending(this.workers.keySet), () => this.tasksQueueDao.peekNextStartAfter(this.workers.keySet), (t) => this.processNextTask(t), loopInterval);
}
start() {
this.pipeline.start();
}
async stop() {
return this.pipeline.stop();
}
registerWorker(queueName, worker) {
if (this.workers.containsKey(queueName)) {
logger$3.warn(`Replacing existing worker for queue: ${queueName}`);
}
this.workers.put(queueName, worker);
}
tasksScheduled(queueName) {
if (this.workers.containsKey(queueName)) {
this.pipeline.triggerLoop();
}
}
async processNextTask(task) {
applicationMetrics.MetricsService.counter("tasks_queue_started").inc();
await this.workers.get(task.queue).match({
some: async (worker) => {
try {
scats.Try(() => worker.starting(task.id, task.payload)).tapFailure((e) => logger$3.warn(`Failed to invoke 'starting' callback for task (id=${task.id}) in queue=${task.queue}`, e));
await worker.process(task.payload, {
maxAttempts: task.maxAttempts,
currentAttempt: task.currentAttempt,
});
if (scats.option(task.repeatType).isEmpty) {
await this.tasksQueueDao.finish(task.id);
}
else {
await this.tasksQueueDao.rescheduleIfPeriodic(task.id);
}
applicationMetrics.MetricsService.counter("tasks_queue_processed").inc();
scats.Try(() => worker.completed(task.id, task.payload)).tapFailure((e) => logger$3.warn(`Failed to invoke 'completed' callback for task (id=${task.id}) in queue=${task.queue}`, e));
}
catch (e) {
const finalStatus = await this.tasksQueueDao.fail(task.id, e["message"] || e, e instanceof TaskFailed ? e.payload : task.payload);
scats.Try(() => worker.failed(task.id, task.payload, finalStatus, e)).tapFailure((e) => logger$3.warn(`Failed to invoke 'failed' callback for task (id=${task.id}) in queue=${task.queue}`, e));
applicationMetrics.MetricsService.counter("tasks_queue_failed").inc();
logger$3.warn(`Failed to process task (id=${task.id}) in queue=${task.queue}`, e);
}
},
none: async () => {
applicationMetrics.MetricsService.counter("tasks_queue_skipped_no_worker").inc();
logger$3.info(`Failed to process task (id=${task.id}) in queue=${task.queue}: no suitable worker found`);
},
});
}
}
const logger$2 = log4js.getLogger("TasksAuxiliaryWorker");
class TasksAuxiliaryWorker {
tasksQueueDao;
workerTimer = null;
constructor(tasksQueueDao) {
this.tasksQueueDao = tasksQueueDao;
}
start() {
try {
const run = () => {
this.runAuxiliaryJobs();
};
this.workerTimer = setInterval(() => run(), TimeUtils.second * 30);
run();
}
catch (e) {
logger$2.warn("Failed to process stalled tasks", e);
}
}
runAuxiliaryJobs() {
try {
this.tasksQueueDao
.failStalled()
.then((res) => {
if (res.nonEmpty) {
logger$2.info(`Marked stalled as failed: ${res.mkString(", ")}`);
}
})
.catch((e) => {
logger$2.warn("Failed to process stalled tasks", e);
});
this.tasksQueueDao.resetFailed().catch((e) => {
logger$2.warn("Failed to reset failed tasks", e);
});
this.tasksQueueDao.clearFinished().catch((e) => {
logger$2.warn("Failed to clear finished tasks", e);
});
}
catch (e) {
logger$2.warn("Failed to process stalled tasks", e);
}
}
async stop() {
if (this.workerTimer) {
clearTimeout(this.workerTimer);
}
}
pendingCount(queue) {
return this.tasksQueueDao.statusCount(queue, exports.TaskStatus.pending);
}
inProgressCount(queue) {
return this.tasksQueueDao.statusCount(queue, exports.TaskStatus.in_progress);
}
}
const logger$1 = log4js.getLogger("TasksQueueService");
class TasksQueueService {
tasksQueueDao;
worker;
auxiliaryWorker;
constructor(tasksQueueDao, config) {
this.tasksQueueDao = tasksQueueDao;
this.worker = new TasksQueueWorker(this.tasksQueueDao, config.concurrency, config.loopInterval);
if (config.runAuxiliaryWorker) {
this.auxiliaryWorker = scats.some(new TasksAuxiliaryWorker(tasksQueueDao));
}
else {
this.auxiliaryWorker = scats.none;
}
}
async schedule(task) {
await this.tasksQueueDao.schedule(task);
this.taskScheduled(task.queue);
}
async scheduleAtFixedRate(task) {
await this.tasksQueueDao.schedulePeriodic(task, exports.TaskPeriodType.fixed_rate);
this.taskScheduled(task.queue);
}
async scheduleAtFixedDelay(task) {
await this.tasksQueueDao.schedulePeriodic(task, exports.TaskPeriodType.fixed_delay);
this.taskScheduled(task.queue);
}
taskScheduled(queueName) {
this.worker.tasksScheduled(queueName);
}
registerWorker(queueName, worker) {
this.worker.registerWorker(queueName, worker);
}
start() {
try {
this.worker.start();
this.auxiliaryWorker.foreach((w) => w.start());
}
catch (e) {
logger$1.warn("Failed to process stalled tasks", e);
}
}
async stop() {
await this.auxiliaryWorker.mapPromise((w) => w.stop());
await this.worker.stop();
}
}
const DEFAULT_POOL = "default";
const logger = log4js.getLogger("TasksPoolsService");
class TasksPoolsService {
dao;
pools;
queuesPool = new scats.mutable.HashMap();
auxiliaryWorker;
constructor(dao, pools = [
{
name: DEFAULT_POOL,
concurrency: 1,
loopInterval: 60000,
},
]) {
this.dao = dao;
const poolsCollection = scats.Collection.from(pools);
const poolNames = poolsCollection.map((p) => p.name).toSet;
if (poolsCollection.size !== poolNames.size) {
throw new Error("Duplicate pool names detected");
}
this.pools = poolsCollection.toMap((p) => [
p.name,
new TasksQueueService(dao, {
concurrency: p.concurrency,
runAuxiliaryWorker: false,
loopInterval: p.loopInterval,
}),
]);
this.auxiliaryWorker = new TasksAuxiliaryWorker(dao);
}
start() {
logger.info(`Starting TasksPoolsService with ${this.pools.size} pools`);
this.auxiliaryWorker.start();
this.pools.values.foreach((p) => p.start());
}
async stop(timeoutMs = 30000) {
logger.info("Stopping TasksPoolsService");
try {
await Promise.race([
Promise.all([
this.auxiliaryWorker.stop(),
this.pools.values.mapPromise((p) => p.stop()),
]),
new Promise((_, reject) => setTimeout(() => reject(new Error("Stop timeout")), timeoutMs)),
]);
logger.info("TasksPoolsService stopped successfully");
}
catch (e) {
logger.error("Failed to stop TasksPoolsService gracefully", e);
throw e;
}
}
registerWorker(queueName, worker, poolName = DEFAULT_POOL) {
if (this.queuesPool.containsKey(queueName)) {
throw new Error(`Queue '${queueName}' is already registered in pool '${this.queuesPool.get(queueName).getOrElseValue("unknown")}'`);
}
this.pools.get(poolName).match({
some: (pool) => {
pool.registerWorker(queueName, worker);
this.queuesPool.put(queueName, poolName);
logger.info(`Registered worker for queue '${queueName}' in pool '${poolName}'`);
},
none: () => {
throw new Error(`Pool '${poolName}' not registered`);
},
});
}
async schedule(task) {
const taskId = await this.dao.schedule(task);
this.taskScheduled(task.queue, taskId);
}
async scheduleAtFixedRate(task) {
const taskId = await this.dao.schedulePeriodic(task, exports.TaskPeriodType.fixed_rate);
this.taskScheduled(task.queue, taskId);
}
async scheduleAtFixedDelay(task) {
const taskId = await this.dao.schedulePeriodic(task, exports.TaskPeriodType.fixed_delay);
this.taskScheduled(task.queue, taskId);
}
taskScheduled(queue, taskId) {
this.queuesPool.get(queue).match({
some: (poolName) => {
this.pools.get(poolName).foreach((pool) => {
pool.taskScheduled(queue);
});
},
none: () => {
logger.info(`No worker registered for a queue '${queue}'. ` +
`Task (id=${taskId.getOrElseValue(-1)}) will remain in pending state`);
},
});
}
}
class TasksQueueDao {
pool;
constructor(pool) {
this.pool = pool;
}
async withClient(cb) {
const client = await this.pool.connect();
let res;
try {
res = await cb(client);
}
finally {
client.release();
}
return res;
}
async schedule(task) {
const now = new Date();
return await this.withClient(async (cl) => {
const res = await cl.query(`insert into tasks_queue (queue, created, status, priority, payload, timeout, max_attempts,
start_after, initial_start, backoff, backoff_type)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
returning id`, [
task.queue,
now,
exports.TaskStatus.pending,
scats.option(task.priority).getOrElseValue(0),
scats.option(task.payload).orNull,
scats.option(task.timeout).getOrElseValue(TimeUtils.hour),
scats.option(task.retries).getOrElseValue(1),
scats.option(task.startAfter).orNull,
scats.option(task.startAfter).getOrElseValue(now),
scats.option(task.backoff).getOrElseValue(TimeUtils.minute),
scats.option(task.backoffType).getOrElseValue(exports.BackoffType.linear),
]);
return scats.Collection.from(res.rows).headOption.map((r) => r.id);
});
}
async schedulePeriodic(task, periodType) {
const now = new Date();
return await this.withClient(async (cl) => {
const res = await cl.query(`insert into tasks_queue (queue, created, status, priority, payload, timeout, max_attempts,
start_after, initial_start, name,
repeat_interval, repeat_type, backoff, backoff_type, missed_runs_strategy)
values ($1, $2, $3, $4, $5, $6, $7, $8, $8, $9, $10, $11, $12, $13, $14)
on conflict (name) do nothing
returning id`, [
task.queue,
now,
exports.TaskStatus.pending,
scats.option(task.priority).getOrElseValue(0),
scats.option(task.payload).orNull,
scats.option(task.timeout).getOrElseValue(TimeUtils.hour),
scats.option(task.retries).getOrElseValue(1),
scats.option(task.startAfter).getOrElseValue(now),
task.name,
task.period,
periodType,
scats.option(task.backoff).getOrElseValue(TimeUtils.minute),
scats.option(task.backoffType).getOrElseValue(exports.BackoffType.linear),
scats.option(task.missedRunStrategy).getOrElseValue(exports.MissedRunStrategy.skip_missed),
]);
return scats.Collection.from(res.rows).headOption.map((r) => r.id);
});
}
async nextPending(queueNames) {
if (queueNames.isEmpty) {
return scats.none;
}
const now = new Date();
const placeholders = queueNames.zipWithIndex
.map(([_, idx]) => `$${idx + 4}`)
.mkString(",");
const paramsStatic = [
exports.TaskStatus.in_progress,
now,
exports.TaskStatus.pending,
];
const params = paramsStatic.concat(queueNames.toArray);
return await this.withClient(async (cl) => {
const query = `
WITH selected AS (SELECT id, payload, queue
FROM tasks_queue
WHERE status = $3
AND queue IN (${placeholders})
AND max_attempts > attempt
AND (start_after IS NULL or start_after <= $2)
ORDER BY priority DESC, id ASC
FOR UPDATE SKIP LOCKED
LIMIT 1)
UPDATE tasks_queue
SET status = $1,
started = $2,
finished = null,
attempt = attempt + 1
FROM selected
WHERE tasks_queue.id = selected.id
RETURNING
tasks_queue.id,
tasks_queue.payload,
tasks_queue.queue,
tasks_queue.repeat_type,
tasks_queue.attempt,
tasks_queue.max_attempts
`;
const res = await cl.query(query, params);
return scats.Collection.from(res.rows).headOption.map((r) => {
return {
id: r["id"],
payload: scats.option(r["payload"]).orUndefined,
queue: r["queue"],
repeatType: scats.option(r["repeat_type"]).orUndefined,
currentAttempt: r["attempt"],
maxAttempts: r["max_attempts"],
};
});
});
}
async peekNextStartAfter(queueNames) {
if (queueNames.isEmpty) {
return scats.none;
}
const now = new Date();
const placeholders = queueNames.zipWithIndex
.map(([_, idx]) => `$${idx + 4}`)
.mkString(",");
return this.withClient(async (cl) => {
const paramsStatic = [exports.TaskStatus.pending, exports.TaskStatus.error, now];
const params = paramsStatic.concat(queueNames.toArray);
const res = await cl.query(`SELECT MIN(start_after) AS min_start
FROM tasks_queue
WHERE status IN ($1, $2)
AND queue IN (${placeholders})
AND start_after > $3`, params);
return scats.Collection.from(res.rows)
.headOption.flatMap((r) => scats.option(r["min_start"]))
.map((ts) => new Date(ts));
});
}
async finish(taskId) {
const now = new Date();
await this.withClient(async (cl) => {
await cl.query(`update tasks_queue
set status=$1,
finished=$2,
error=null
where id = $3
and status = $4`, [exports.TaskStatus.finished, now, taskId, exports.TaskStatus.in_progress]);
});
}
async rescheduleIfPeriodic(taskId) {
await this.withClient(async (cl) => {
const now = new Date();
await cl.query(`
UPDATE tasks_queue
SET status = $1,
start_after = CASE
WHEN repeat_type = 'fixed_rate' AND missed_runs_strategy = 'catch_up' THEN
start_after + (repeat_interval * interval '1 millisecond')
WHEN repeat_type = 'fixed_rate' AND missed_runs_strategy = 'skip_missed' THEN
initial_start +
(CEIL(EXTRACT(EPOCH FROM ($2 - initial_start)) * 1000 / repeat_interval) *
repeat_interval * interval '1 millisecond')
ELSE
$2 + (repeat_interval * interval '1 millisecond')
END,
finished = $2,
error = NULL,
attempt = 0
WHERE id = $3
AND status = $4
AND repeat_interval IS NOT NULL
AND missed_runs_strategy IS NOT NULL
AND repeat_type IN ('${exports.TaskPeriodType.fixed_rate}', '${exports.TaskPeriodType.fixed_delay}');
`, [exports.TaskStatus.pending, now, taskId, exports.TaskStatus.in_progress]);
});
}
async fail(taskId, error, nextPayload) {
const now = new Date();
return await this.withClient(async (cl) => {
const res = await cl.query(`
UPDATE tasks_queue
SET finished = $1,
error = $2,
status = CASE
WHEN attempt < max_attempts THEN $3 -- pending
ELSE $4 -- error
END,
start_after = CASE
WHEN attempt < max_attempts THEN
$1::timestamp + (
CASE backoff_type
WHEN 'constant' THEN backoff
WHEN 'linear' THEN (backoff * attempt)
WHEN 'exponential'
THEN (backoff * POWER(2, (attempt - 1)))
ELSE backoff
END
) * interval '1 millisecond'
ELSE NULL
END,
payload = $7
WHERE id = $5
AND status = $6
returning status
`, [
now,
error,
exports.TaskStatus.pending,
exports.TaskStatus.error,
taskId,
exports.TaskStatus.in_progress,
scats.option(nextPayload).orNull,
]);
return scats.Collection.from(res.rows)
.headOption.map((r) => exports.TaskStatus[r.status])
.getOrElseValue(exports.TaskStatus.error);
});
}
async failStalled() {
const now = new Date();
return await this.withClient(async (cl) => {
const res = await cl.query(`update tasks_queue
set status=$1,
finished=$2,
error=$3
where status = $4
and timeout is not null
and started + timeout * interval '1 ms' < $5
returning id`, [exports.TaskStatus.error, now, "Timeout", exports.TaskStatus.in_progress, now]);
return scats.Collection.from(res.rows).map((r) => r.id);
});
}
async resetFailed() {
await this.withClient(async (cl) => {
await cl.query(`update tasks_queue
set status=$1,
finished=null
where status = $2
and timeout is not null
and max_attempts > COALESCE(attempt, 0)`, [exports.TaskStatus.pending, exports.TaskStatus.error]);
});
}
async clearFinished(timeout = TimeUtils.day) {
const expired = new Date(Date.now() - timeout);
await this.withClient(async (cl) => {
await cl.query(`delete
from tasks_queue
where status = $1
and finished < $2`, [exports.TaskStatus.finished, expired]);
});
}
async statusCount(queue, status) {
return await this.withClient(async (cl) => {
const res = await cl.query(`select count(id) as cnt
from tasks_queue
where queue = $1
and status = $2`, [queue, status]);
return scats.Collection.from(res.rows)
.headOption.map((r) => r["cnt"])
.getOrElseValue(0);
});
}
}
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [Object]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "schedule", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [Object, String]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "schedulePeriodic", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [scats.HashSet]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "nextPending", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [scats.HashSet]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "peekNextStartAfter", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [Number]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "finish", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [Number]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "rescheduleIfPeriodic", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [Number, String, Object]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "fail", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", []),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "failStalled", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", []),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "resetFailed", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [Number]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "clearFinished", null);
tslib.__decorate([
applicationMetrics.Metric(),
tslib.__metadata("design:type", Function),
tslib.__metadata("design:paramtypes", [String, String]),
tslib.__metadata("design:returntype", Promise)
], TasksQueueDao.prototype, "statusCount", null);
const TASKS_QUEUE_OPTIONS = Symbol("TASKS_QUEUE_OPTIONS");
var TasksQueueModule_1;
exports.TasksQueueModule = TasksQueueModule_1 = class TasksQueueModule {
moduleRef;
constructor(moduleRef) {
this.moduleRef = moduleRef;
}
static forRootAsync(options) {
const asyncProviders = this.createAsyncProviders(options);
return {
module: TasksQueueModule_1,
imports: options.imports,
providers: [
...asyncProviders,
{
provide: TasksQueueDao,
inject: [TASKS_QUEUE_OPTIONS],
useFactory: (opts) => new TasksQueueDao(opts.db),
},
{
provide: TasksPoolsService,
inject: [TasksQueueDao, TASKS_QUEUE_OPTIONS],
useFactory: (dao, opts) => new TasksPoolsService(dao, opts.pools),
},
],
exports: [TasksPoolsService],
};
}
static createAsyncProviders(options) {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
const useClass = options.useClass;
return [
this.createAsyncOptionsProvider(options),
{
provide: useClass,
useClass,
},
];
}
static createAsyncOptionsProvider(options) {
if (options.useFactory) {
return {
provide: TASKS_QUEUE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}
const inject = [
(options.useClass ||
options.useExisting),
];
return {
provide: TASKS_QUEUE_OPTIONS,
useFactory: async (optionsFactory) => await optionsFactory.createTelegrafOptions(),
inject,
};
}
onApplicationBootstrap() {
const poolsService = this.moduleRef.get(TasksPoolsService);
poolsService.start();
}
async onApplicationShutdown() {
const poolsService = this.moduleRef.get(TasksPoolsService);
await poolsService.stop(TimeUtils.minute);
}
};
exports.TasksQueueModule = TasksQueueModule_1 = tslib.__decorate([
common.Module({}),
tslib.__metadata("design:paramtypes", [core.ModuleRef])
], exports.TasksQueueModule);
class TasksWorker {
starting(taskId, payload) { }
completed(taskId, payload) { }
failed(taskId, payload, finalStatus, error) { }
}
exports.DEFAULT_POOL = DEFAULT_POOL;
exports.TASKS_QUEUE_OPTIONS = TASKS_QUEUE_OPTIONS;
exports.TaskFailed = TaskFailed;
exports.TasksPoolsService = TasksPoolsService;
exports.TasksQueueService = TasksQueueService;
exports.TasksWorker = TasksWorker;