bottleneck
Version:
Distributed task scheduler and rate limiter
378 lines (326 loc) • 12.2 kB
JavaScript
"use strict";
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, DLList, RedisStorage, libraries, lua, parser, scriptTemplates;
parser = require("./parser");
DLList = require("./DLList");
BottleneckError = require("./BottleneckError");
lua = require("./lua.json");
libraries = {
get_time: lua["get_time.lua"],
refresh_running: lua["refresh_running.lua"],
conditions_check: lua["conditions_check.lua"],
refresh_expiration: lua["refresh_expiration.lua"],
validate_keys: lua["validate_keys.lua"]
};
scriptTemplates = function scriptTemplates(id) {
return {
init: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["refresh_expiration"],
code: lua["init.lua"]
},
update_settings: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["validate_keys", "refresh_expiration"],
code: lua["update_settings.lua"]
},
running: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["validate_keys", "refresh_running"],
code: lua["running.lua"]
},
group_check: {
keys: [`b_${id}_settings`],
libs: [],
code: lua["group_check.lua"]
},
check: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["validate_keys", "refresh_running", "conditions_check"],
code: lua["check.lua"]
},
submit: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["validate_keys", "refresh_running", "conditions_check", "refresh_expiration"],
code: lua["submit.lua"]
},
register: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["validate_keys", "refresh_running", "conditions_check", "refresh_expiration"],
code: lua["register.lua"]
},
free: {
keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`],
libs: ["validate_keys", "refresh_running"],
code: lua["free.lua"]
},
current_reservoir: {
keys: [`b_${id}_settings`],
libs: ["validate_keys"],
code: lua["current_reservoir.lua"]
},
increment_reservoir: {
keys: [`b_${id}_settings`],
libs: ["validate_keys"],
code: lua["increment_reservoir.lua"]
}
};
};
RedisStorage = class RedisStorage {
constructor(instance, initSettings, options) {
var redis;
this.loadAll = this.loadAll.bind(this);
this.instance = instance;
this.initSettings = initSettings;
redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module
this.originalId = this.instance.id;
this.scripts = scriptTemplates(this.originalId);
parser.load(options, options, this);
this.client = redis.createClient(this.clientOptions);
this.subClient = redis.createClient(this.clientOptions);
this.shas = {};
this.clients = {
client: this.client,
subscriber: this.subClient
};
this.isReady = false;
this.ready = new this.Promise((resolve, reject) => {
var count, done, errorListener;
errorListener = function errorListener(e) {
return reject(e);
};
count = 0;
done = () => {
count++;
if (count === 2) {
[this.client, this.subClient].forEach(client => {
client.removeListener("error", errorListener);
return client.on("error", e => {
return this.instance.Events.trigger("error", [e]);
});
});
return resolve();
}
};
this.client.on("error", errorListener);
this.client.on("ready", function () {
return done();
});
this.subClient.on("error", errorListener);
return this.subClient.on("ready", () => {
this.subClient.on("subscribe", function () {
return done();
});
return this.subClient.subscribe(`b_${this.originalId}`);
});
}).then(this.loadAll).then(() => {
var args;
this.subClient.on("message", (channel, message) => {
var info, type;
var _message$split = message.split(":");
var _message$split2 = _slicedToArray(_message$split, 2);
type = _message$split2[0];
info = _message$split2[1];
if (type === "freed") {
return this.instance._drainAll(~~info);
}
});
args = this.prepareInitSettings(options.clearDatastore);
this.isReady = true;
return this.runScript("init", args);
}).then(results => {
return this.clients;
});
}
disconnect(flush) {
this.client.end(flush);
this.subClient.end(flush);
return this;
}
loadScript(name) {
return new this.Promise((resolve, reject) => {
var payload;
payload = this.scripts[name].libs.map(function (lib) {
return libraries[lib];
}).join("\n") + this.scripts[name].code;
return this.client.multi([["script", "load", payload]]).exec((err, replies) => {
if (err != null) {
return reject(err);
}
this.shas[name] = replies[0];
return resolve(replies[0]);
});
});
}
loadAll() {
var k, v;
return this.Promise.all(function () {
var ref, results1;
ref = this.scripts;
results1 = [];
for (k in ref) {
v = ref[k];
results1.push(this.loadScript(k));
}
return results1;
}.call(this));
}
prepareArray(arr) {
return arr.map(function (x) {
if (x != null) {
return x.toString();
} else {
return "";
}
});
}
prepareObject(obj) {
var arr, k, v;
arr = [];
for (k in obj) {
v = obj[k];
arr.push(k, v != null ? v.toString() : "");
}
return arr;
}
prepareInitSettings(clear) {
var args;
args = this.prepareObject(Object.assign({}, this.initSettings, {
id: this.originalId,
nextRequest: Date.now(),
running: 0,
unblockTime: 0,
version: this.instance.version,
groupTimeout: this._groupTimeout
}));
args.unshift(clear ? 1 : 0);
return args;
}
runScript(name, args) {
var script;
if (!this.isReady) {
return this.Promise.reject(new BottleneckError("This limiter is not done connecting to Redis yet. Wait for the '.ready()' promise to resolve before submitting requests."));
} else {
script = this.scripts[name];
return new this.Promise((resolve, reject) => {
var arr;
arr = [this.shas[name], script.keys.length].concat(script.keys, args, function (err, replies) {
if (err != null) {
return reject(err);
}
return resolve(replies);
});
this.instance.Events.trigger("debug", [`Calling Redis script: ${name}.lua`, args]);
return this.client.evalsha.bind(this.client).apply({}, arr);
}).catch(e => {
if (e.message === "SETTINGS_KEY_NOT_FOUND") {
return this.runScript("init", this.prepareInitSettings(false)).then(() => {
return this.runScript(name, args);
});
} else {
return this.Promise.reject(e);
}
});
}
}
convertBool(b) {
return !!b;
}
__updateSettings__(options) {
var _this = this;
return _asyncToGenerator(function* () {
return yield _this.runScript("update_settings", _this.prepareObject(options));
})();
}
__running__() {
var _this2 = this;
return _asyncToGenerator(function* () {
return yield _this2.runScript("running", [Date.now()]);
})();
}
__groupCheck__() {
var _this3 = this;
return _asyncToGenerator(function* () {
return _this3.convertBool((yield _this3.runScript("group_check", [])));
})();
}
__incrementReservoir__(incr) {
var _this4 = this;
return _asyncToGenerator(function* () {
return yield _this4.runScript("increment_reservoir", [incr]);
})();
}
__currentReservoir__() {
var _this5 = this;
return _asyncToGenerator(function* () {
return yield _this5.runScript("current_reservoir", []);
})();
}
__check__(weight) {
var _this6 = this;
return _asyncToGenerator(function* () {
return _this6.convertBool((yield _this6.runScript("check", _this6.prepareArray([weight, Date.now()]))));
})();
}
__register__(index, weight, expiration) {
var _this7 = this;
return _asyncToGenerator(function* () {
var reservoir, success, wait;
var _ref = yield _this7.runScript("register", _this7.prepareArray([index, weight, expiration, Date.now()]));
var _ref2 = _slicedToArray(_ref, 3);
success = _ref2[0];
wait = _ref2[1];
reservoir = _ref2[2];
return {
success: _this7.convertBool(success),
wait,
reservoir
};
})();
}
__submit__(queueLength, weight) {
var _this8 = this;
return _asyncToGenerator(function* () {
var blocked, e, maxConcurrent, overweight, reachedHWM, strategy;
try {
var _ref3 = yield _this8.runScript("submit", _this8.prepareArray([queueLength, weight, Date.now()]));
var _ref4 = _slicedToArray(_ref3, 3);
reachedHWM = _ref4[0];
blocked = _ref4[1];
strategy = _ref4[2];
return {
reachedHWM: _this8.convertBool(reachedHWM),
blocked: _this8.convertBool(blocked),
strategy
};
} catch (error) {
e = error;
if (e.message.indexOf("OVERWEIGHT") === 0) {
var _e$message$split = e.message.split(":");
var _e$message$split2 = _slicedToArray(_e$message$split, 3);
overweight = _e$message$split2[0];
weight = _e$message$split2[1];
maxConcurrent = _e$message$split2[2];
throw new BottleneckError(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${maxConcurrent}`);
} else {
throw e;
}
}
})();
}
__free__(index, weight) {
var _this9 = this;
return _asyncToGenerator(function* () {
var result;
result = yield _this9.runScript("free", _this9.prepareArray([index, Date.now()]));
return {
running: result
};
})();
}
};
module.exports = RedisStorage;
}).call(undefined);