node-resque
Version:
an opinionated implementation of resque in node
209 lines (208 loc) • 7.49 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiWorker = void 0;
const events_1 = require("events");
const os = require("os");
const worker_1 = require("./worker");
const eventLoopDelay_1 = require("../utils/eventLoopDelay");
class MultiWorker extends events_1.EventEmitter {
constructor(options, jobs) {
var _a, _b, _c, _d, _e, _f;
super();
options.name = (_a = options.name) !== null && _a !== void 0 ? _a : os.hostname();
options.minTaskProcessors = (_b = options.minTaskProcessors) !== null && _b !== void 0 ? _b : 1;
options.maxTaskProcessors = (_c = options.maxTaskProcessors) !== null && _c !== void 0 ? _c : 10;
options.timeout = (_d = options.timeout) !== null && _d !== void 0 ? _d : 5000;
options.checkTimeout = (_e = options.checkTimeout) !== null && _e !== void 0 ? _e : 500;
options.maxEventLoopDelay = (_f = options.maxEventLoopDelay) !== null && _f !== void 0 ? _f : 10;
if (options.connection.redis &&
typeof options.connection.redis.setMaxListeners === "function") {
options.connection.redis.setMaxListeners(options.connection.redis.getMaxListeners() + options.maxTaskProcessors);
}
this.workers = [];
this.options = options;
this.jobs = jobs;
this.running = false;
this.working = false;
this.name = this.options.name;
this.eventLoopBlocked = true;
this.eventLoopDelay = Infinity;
this.eventLoopCheckCounter = 0;
this.stopInProcess = false;
this.checkTimer = null;
this.PollEventLoopDelay();
}
PollEventLoopDelay() {
(0, eventLoopDelay_1.EventLoopDelay)(this.options.maxEventLoopDelay, this.options.checkTimeout, (blocked, ms) => {
this.eventLoopBlocked = blocked;
this.eventLoopDelay = ms;
this.eventLoopCheckCounter++;
});
}
async startWorker() {
const id = this.workers.length + 1;
const worker = new worker_1.Worker({
connection: this.options.connection,
queues: this.options.queues,
timeout: this.options.timeout,
name: this.options.name + ":" + process.pid + "+" + id,
}, this.jobs);
worker.id = id;
worker.on("start", () => {
this.emit("start", worker.id);
});
worker.on("end", () => {
this.emit("end", worker.id);
});
worker.on("cleaning_worker", (worker, pid) => {
this.emit("cleaning_worker", worker.id, worker, pid);
});
worker.on("poll", (queue) => {
this.emit("poll", worker.id, queue);
});
worker.on("ping", (time) => {
this.emit("ping", worker.id, time);
});
worker.on("job", (queue, job) => {
this.emit("job", worker.id, queue, job);
});
worker.on("reEnqueue", (queue, job, plugin) => {
this.emit("reEnqueue", worker.id, queue, job, plugin);
});
worker.on("success", (queue, job, result, duration) => {
this.emit("success", worker.id, queue, job, result, duration);
});
worker.on("failure", (queue, job, failure, duration) => {
this.emit("failure", worker.id, queue, job, failure, duration);
});
worker.on("error", (error, queue, job) => {
this.emit("error", error, worker.id, queue, job);
});
worker.on("pause", () => {
this.emit("pause", worker.id);
});
this.workers.push(worker);
await worker.connect();
await worker.start();
}
async checkWorkers() {
let verb;
let worker;
let workingCount = 0;
this.workers.forEach((worker) => {
if (worker.working === true) {
workingCount++;
}
});
this.working = workingCount > 0;
if (this.running === false && this.workers.length > 0) {
verb = "--";
}
else if (this.running === false && this.workers.length === 0) {
verb = "x";
}
else if (this.eventLoopBlocked &&
this.workers.length > this.options.minTaskProcessors) {
verb = "-";
}
else if (this.eventLoopBlocked &&
this.workers.length === this.options.minTaskProcessors) {
verb = "x";
}
else if (!this.eventLoopBlocked &&
this.workers.length < this.options.minTaskProcessors) {
verb = "+";
}
else if (!this.eventLoopBlocked &&
this.workers.length < this.options.maxTaskProcessors &&
(this.workers.length === 0 || workingCount / this.workers.length > 0.5)) {
verb = "+";
}
else if (!this.eventLoopBlocked &&
this.workers.length > this.options.minTaskProcessors &&
workingCount / this.workers.length < 0.5) {
verb = "-";
}
else {
verb = "x";
}
if (verb === "x") {
return { verb, eventLoopDelay: this.eventLoopDelay };
}
if (verb === "-") {
worker = this.workers.pop();
await worker.end();
await this.cleanupWorker(worker);
return { verb, eventLoopDelay: this.eventLoopDelay };
}
if (verb === "--") {
this.stopInProcess = true;
const promises = [];
this.workers.forEach((worker) => {
promises.push(new Promise(async (resolve) => {
await worker.end();
await this.cleanupWorker(worker);
return resolve(null);
}));
});
await Promise.all(promises);
this.stopInProcess = false;
this.workers = [];
return { verb, eventLoopDelay: this.eventLoopDelay };
}
if (verb === "+") {
await this.startWorker();
return { verb, eventLoopDelay: this.eventLoopDelay };
}
}
async cleanupWorker(worker) {
[
"start",
"end",
"cleaning_worker",
"poll",
"ping",
"job",
"reEnqueue",
"success",
"failure",
"error",
"pause",
"multiWorkerAction",
].forEach((e) => {
worker.removeAllListeners(e);
});
}
async checkWrapper() {
clearTimeout(this.checkTimer);
const { verb, eventLoopDelay } = await this.checkWorkers();
this.emit("multiWorkerAction", verb, eventLoopDelay);
this.checkTimer = setTimeout(() => {
this.checkWrapper();
}, this.options.checkTimeout);
}
start() {
this.running = true;
this.checkWrapper();
}
async stop() {
this.running = false;
await this.stopWait();
}
async end() {
return this.stop();
}
async stopWait() {
if (this.workers.length === 0 &&
this.working === false &&
!this.stopInProcess) {
clearTimeout(this.checkTimer);
return;
}
await new Promise((resolve) => {
setTimeout(resolve, this.options.checkTimeout);
});
return this.stopWait();
}
}
exports.MultiWorker = MultiWorker;