@sidequest/core
Version:
@sidequest/core is the core package of SideQuest, a distributed background job queue for Node.js and TypeScript applications.
80 lines (76 loc) • 3.27 kB
JavaScript
;
var logger = require('../logger.cjs');
var parseErrorData = require('../tools/parse-error-data.cjs');
var failTransition = require('./fail-transition.cjs');
var transition = require('./transition.cjs');
/**
* Transition for retrying a failed job.
*
* This transition will:
* - If the job has reached its maximum attempts, it will mark it as failed.
* - If not, it will log the reason for retrying, add the error to the job's errors,
* and set the job state to "waiting" with an available_at time
* based on an exponential backoff strategy.
* - The delay can be specified or calculated using exponential backoff with jitter.
*
* This transition can only be applied to jobs that are currently running.
*/
class RetryTransition extends transition.JobTransition {
/** Optional delay in milliseconds before retrying. */
delay;
/** The reason for retrying. */
reason;
/**
* Creates a new RetryTransition.
* @param reason The reason for retrying.
* @param delay Optional delay in milliseconds before retrying.
*/
constructor(reason, delay) {
super();
this.delay = delay;
this.reason = reason;
}
apply(job) {
if (job.attempt >= job.max_attempts) {
return new failTransition.FailTransition(this.reason).apply(job);
}
logger.logger("Core").error(this.reason);
const reason = parseErrorData.toErrorData(this.reason);
// We use the provided delay (priority), or the job's retry_delay (next priority), or default to 1000ms
let delay = this.delay ?? job.retry_delay ?? 1000;
// If the job uses exponential backoff, we use the base delay to compute it
if (job.backoff_strategy === "exponential") {
logger.logger("Core").debug(`Calculating exponential backoff for job #${job.id} - ${job.class}`);
delay = this.calculateBackoff(job.attempt, delay);
}
logger.logger("Core").info(`Retrying failed job #${job.id} - ${job.class} in ${delay}ms`);
const errData = {
...reason,
attempt: job.attempt,
attempted_at: job.attempted_at,
attempt_by: job.claimed_by,
};
job.errors ??= [];
job.errors.push(errData);
job.state = "waiting";
job.available_at = new Date(Date.now() + delay);
return job;
}
/**
* Calculates the backoff delay for a retry attempt using exponential backoff with jitter.
*
* @param attempt - The current retry attempt number (1-based).
* @param baseDelay - The base delay in milliseconds for the first attempt. Defaults to 1000 ms.
* @param maxDelay - The maximum delay in milliseconds. Defaults to 3,600,000 ms (1 hour).
* @returns The calculated backoff delay in milliseconds, randomized with jitter and capped at maxDelay.
*/
calculateBackoff(attempt, baseDelay = 1000, maxDelay = 3600000) {
const jitter = Math.random() + 0.5;
return Math.round(Math.min(baseDelay * Math.pow(2, attempt - 1) * jitter, maxDelay));
}
shouldRun(job) {
return job.state === "running";
}
}
exports.RetryTransition = RetryTransition;
//# sourceMappingURL=retry-transition.cjs.map