UNPKG

node-resque

Version:

an opinionated implementation of resque in node

138 lines (137 loc) 4.82 kB
"use strict"; // If a job fails, retry it N times before finally placing it into the failed queue // a port of some of the features in https://github.com/lantins/resque-retry Object.defineProperty(exports, "__esModule", { value: true }); exports.Retry = void 0; const os = require("os"); const index_1 = require("./../index"); class Retry extends index_1.Plugin { constructor(worker, func, queue, job, args, options) { super(worker, func, queue, job, args, options); if (!this.options.retryLimit) { this.options.retryLimit = 1; } if (!this.options.retryDelay) { this.options.retryDelay = 1000 * 5; } if (!this.options.backoffStrategy) { this.options.backoffStrategy = null; } } beforeEnqueue() { return true; } afterEnqueue() { return true; } beforePerform() { return true; } async afterPerform() { if (!this.worker.error) { await this.cleanup(); return true; } const remaining = await this.attemptUp(); await this.saveLastError(); if (remaining <= 0) { await this.cleanup(); throw this.worker.error; } let nextTryDelay = this.options.retryDelay; if (Array.isArray(this.options.backoffStrategy)) { let index = this.options.retryLimit - remaining - 1; if (index > this.options.backoffStrategy.length - 1) { index = this.options.backoffStrategy.length - 1; } nextTryDelay = this.options.backoffStrategy[index]; } await this.queueObject.enqueueIn(nextTryDelay, this.queue, this.func, this.args); this.job.args = this.args; this.worker.emit("reEnqueue", this.queue, this.job, { delay: nextTryDelay, remainingAttempts: remaining, err: this.worker.error, }); await this.redis().decr(this.queueObject.connection.key("stat", "processed")); await this.redis().decr(this.queueObject.connection.key("stat", "processed", this.worker.name)); await this.redis().incr(this.queueObject.connection.key("stat", "failed")); await this.redis().incr(this.queueObject.connection.key("stat", "failed", this.worker.name)); delete this.worker.error; return true; } argsKey() { if (!this.args || this.args.length === 0) { return ""; } return this.args .map(function (elem) { return typeof elem === "object" ? JSON.stringify(elem) : elem; }) .join("-"); } retryKey() { return this.queueObject.connection .key("resque-retry", this.func, this.argsKey()) .replace(/\s/, ""); } failureKey() { return this.queueObject.connection .key("failure-resque-retry:" + this.func + ":" + this.argsKey()) .replace(/\s/, ""); } maxDelay() { let maxDelay = this.options.retryDelay || 1; if (Array.isArray(this.options.backoffStrategy)) { this.options.backoffStrategy.forEach(function (d) { if (d > maxDelay) { maxDelay = d; } }); } return maxDelay; } redis() { return this.queueObject.connection.redis; } async attemptUp() { const key = this.retryKey(); await this.redis().setnx(key, -1); const retryCount = await this.redis().incr(key); await this.redis().expire(key, this.maxDelay()); return this.options.retryLimit - retryCount - 1; } async saveLastError() { const now = new Date(); const failedAt = "" + now.getFullYear() + "/" + ("0" + (now.getMonth() + 1)).slice(-2) + "/" + ("0" + now.getDate()).slice(-2) + " " + ("0" + now.getHours()).slice(-2) + ":" + ("0" + now.getMinutes()).slice(-2) + ":" + ("0" + now.getSeconds()).slice(-2); const backtrace = this.worker.error.stack ? this.worker.error.stack.split(os.EOL) || [] : []; const data = { failed_at: failedAt, payload: this.args, exception: String(this.worker.error), error: String(this.worker.error), backtrace: backtrace, worker: this.func, queue: this.queue, }; await this.redis().setex(this.failureKey(), this.maxDelay(), JSON.stringify(data)); } async cleanup() { await this.redis().del(this.retryKey()); await this.redis().del(this.failureKey()); } } exports.Retry = Retry;