bottleneck
Version:
Distributed task scheduler and rate limiter
1,507 lines (1,287 loc) • 57.7 kB
JavaScript
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"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 _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
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 Bottleneck,
DEFAULT_PRIORITY,
DLList,
Events,
Local,
NUM_PRIORITIES,
RedisStorage,
States,
Sync,
packagejson,
parser,
splice = [].splice;
NUM_PRIORITIES = 10;
DEFAULT_PRIORITY = 5;
parser = require("./parser");
Local = require("./Local");
RedisStorage = require("./RedisStorage");
Events = require("./Events");
States = require("./States");
DLList = require("./DLList");
Sync = require("./Sync");
packagejson = require("../package.json");
Bottleneck = function () {
class Bottleneck {
constructor(options = {}, ...invalid) {
var sDefaults;
this.ready = this.ready.bind(this);
this.clients = this.clients.bind(this);
this.disconnect = this.disconnect.bind(this);
this.chain = this.chain.bind(this);
this.queued = this.queued.bind(this);
this.running = this.running.bind(this);
this.check = this.check.bind(this);
this._drainOne = this._drainOne.bind(this);
this.submit = this.submit.bind(this);
this.schedule = this.schedule.bind(this);
this.wrap = this.wrap.bind(this);
this.updateSettings = this.updateSettings.bind(this);
this.currentReservoir = this.currentReservoir.bind(this);
this.incrementReservoir = this.incrementReservoir.bind(this);
if (!(options != null && typeof options === "object" && invalid.length === 0)) {
throw new Bottleneck.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1.");
}
parser.load(options, this.instanceDefaults, this);
this._queues = this._makeQueues();
this._scheduled = {};
this._states = new States(["RECEIVED", "QUEUED", "RUNNING", "EXECUTING"].concat(this.trackDoneStatus ? ["DONE"] : []));
this._limiter = null;
this.Events = new Events(this);
this._submitLock = new Sync("submit");
this._registerLock = new Sync("register");
sDefaults = parser.load(options, this.storeDefaults, {});
this._store = function () {
if (this.datastore === "local") {
return new Local(parser.load(options, this.storeInstanceDefaults, sDefaults));
} else if (this.datastore === "redis") {
return new RedisStorage(this, sDefaults, parser.load(options, this.storeInstanceDefaults, {}));
} else {
throw new Bottleneck.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`);
}
}.call(this);
}
ready() {
return this._store.ready;
}
clients() {
return this._store.clients;
}
disconnect(flush = true) {
var _this = this;
return _asyncToGenerator(function* () {
return yield _this._store.disconnect(flush);
})();
}
chain(_limiter) {
this._limiter = _limiter;
return this;
}
queued(priority) {
if (priority != null) {
return this._queues[priority].length;
} else {
return this._queues.reduce(function (a, b) {
return a + b.length;
}, 0);
}
}
empty() {
return this.queued() === 0 && this._submitLock.isEmpty();
}
running() {
var _this2 = this;
return _asyncToGenerator(function* () {
return yield _this2._store.__running__();
})();
}
jobStatus(id) {
return this._states.jobStatus(id);
}
counts() {
return this._states.statusCounts();
}
_makeQueues() {
var i, j, ref, results;
results = [];
for (i = j = 1, ref = NUM_PRIORITIES; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) {
results.push(new DLList());
}
return results;
}
_sanitizePriority(priority) {
var sProperty;
sProperty = ~~priority !== priority ? DEFAULT_PRIORITY : priority;
if (sProperty < 0) {
return 0;
} else if (sProperty > NUM_PRIORITIES - 1) {
return NUM_PRIORITIES - 1;
} else {
return sProperty;
}
}
_find(arr, fn) {
var ref;
return (ref = function () {
var i, j, len, x;
for (i = j = 0, len = arr.length; j < len; i = ++j) {
x = arr[i];
if (fn(x)) {
return x;
}
}
}()) != null ? ref : [];
}
_getFirst(arr) {
return this._find(arr, function (x) {
return x.length > 0;
});
}
_randomIndex() {
return Math.random().toString(36).slice(2);
}
check(weight = 1) {
var _this3 = this;
return _asyncToGenerator(function* () {
return yield _this3._store.__check__(weight);
})();
}
_run(next, wait, index) {
var _this4 = this;
var completed, done;
this.Events.trigger("debug", [`Scheduling ${next.options.id}`, {
args: next.args,
options: next.options
}]);
done = false;
completed = (() => {
var _ref = _asyncToGenerator(function* (...args) {
var e, ref, running;
if (!done) {
try {
done = true;
_this4._states.next(next.options.id); // DONE
clearTimeout(_this4._scheduled[index].expiration);
delete _this4._scheduled[index];
_this4.Events.trigger("debug", [`Completed ${next.options.id}`, {
args: next.args,
options: next.options
}]);
var _ref2 = yield _this4._store.__free__(index, next.options.weight);
running = _ref2.running;
_this4.Events.trigger("debug", [`Freed ${next.options.id}`, {
args: next.args,
options: next.options
}]);
_this4._drainAll().catch(function (e) {
return _this4.Events.trigger("error", [e]);
});
if (running === 0 && _this4.empty()) {
_this4.Events.trigger("idle", []);
}
return (ref = next.cb) != null ? ref.apply({}, args) : void 0;
} catch (error) {
e = error;
return _this4.Events.trigger("error", [e]);
}
}
});
return function completed() {
return _ref.apply(this, arguments);
};
})();
this._states.next(next.options.id); // RUNNING
return this._scheduled[index] = {
timeout: setTimeout(() => {
this.Events.trigger("debug", [`Executing ${next.options.id}`, {
args: next.args,
options: next.options
}]);
this._states.next(next.options.id); // EXECUTING
if (this._limiter != null) {
return this._limiter.submit.apply(this._limiter, Array.prototype.concat(next.options, next.task, next.args, completed));
} else {
return next.task.apply({}, next.args.concat(completed));
}
}, wait),
expiration: next.options.expiration != null ? setTimeout(() => {
return completed(new Bottleneck.prototype.BottleneckError(`This job timed out after ${next.options.expiration} ms.`));
}, wait + next.options.expiration) : void 0,
job: next
};
}
_drainOne(freed) {
return this._registerLock.schedule(() => {
var args, index, options, queue;
if (this.queued() === 0) {
return this.Promise.resolve(false);
}
queue = this._getFirst(this._queues);
var _queue$first = queue.first();
options = _queue$first.options;
args = _queue$first.args;
if (freed != null && options.weight > freed) {
return this.Promise.resolve(false);
}
this.Events.trigger("debug", [`Draining ${options.id}`, { args, options }]);
index = this._randomIndex();
return this._store.__register__(index, options.weight, options.expiration).then(({ success, wait, reservoir }) => {
var empty, next;
this.Events.trigger("debug", [`Drained ${options.id}`, { success, args, options }]);
if (success) {
next = queue.shift();
empty = this.empty();
if (empty) {
this.Events.trigger("empty", []);
}
if (reservoir === 0) {
this.Events.trigger("depleted", [empty]);
}
this._run(next, wait, index);
}
return this.Promise.resolve(success);
});
});
}
_drainAll(freed) {
return this._drainOne(freed).then(success => {
if (success) {
return this._drainAll();
} else {
return this.Promise.resolve(success);
}
}).catch(e => {
return this.Events.trigger("error", [e]);
});
}
_drop(job) {
this._states.remove(job.options.id);
if (this.rejectOnDrop) {
job.cb.apply({}, [new Bottleneck.prototype.BottleneckError("This job has been dropped by Bottleneck")]);
}
return this.Events.trigger("dropped", [job]);
}
submit(...args) {
var _this5 = this;
var cb, job, options, ref, ref1, task;
if (typeof args[0] === "function") {
var _ref3, _ref4, _splice$call, _splice$call2;
ref = args, (_ref3 = ref, _ref4 = _toArray(_ref3), task = _ref4[0], args = _ref4.slice(1), _ref3), (_splice$call = splice.call(args, -1), _splice$call2 = _slicedToArray(_splice$call, 1), cb = _splice$call2[0], _splice$call);
options = this.jobDefaults;
} else {
var _ref5, _ref6, _splice$call3, _splice$call4;
ref1 = args, (_ref5 = ref1, _ref6 = _toArray(_ref5), options = _ref6[0], task = _ref6[1], args = _ref6.slice(2), _ref5), (_splice$call3 = splice.call(args, -1), _splice$call4 = _slicedToArray(_splice$call3, 1), cb = _splice$call4[0], _splice$call3);
options = parser.load(options, this.jobDefaults);
}
job = { options, task, args, cb };
options.priority = this._sanitizePriority(options.priority);
if (options.id === this.jobDefaults.id) {
options.id = `${options.id}-${this._randomIndex()}`;
}
if (this.jobStatus(options.id) != null) {
job.cb(new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${options.id})`));
return false;
}
this._states.start(options.id); // RECEIVED
this.Events.trigger("debug", [`Queueing ${options.id}`, { args, options }]);
return this._submitLock.schedule(_asyncToGenerator(function* () {
var blocked, e, reachedHWM, shifted, strategy;
try {
var _ref8 = yield _this5._store.__submit__(_this5.queued(), options.weight);
reachedHWM = _ref8.reachedHWM;
blocked = _ref8.blocked;
strategy = _ref8.strategy;
_this5.Events.trigger("debug", [`Queued ${options.id}`, { args, options, reachedHWM, blocked }]);
} catch (error) {
e = error;
_this5._states.remove(options.id);
_this5.Events.trigger("debug", [`Could not queue ${options.id}`, {
args,
options,
error: e
}]);
job.cb(e);
return false;
}
if (blocked) {
_this5._queues = _this5._makeQueues();
_this5._drop(job);
return true;
} else if (reachedHWM) {
shifted = strategy === Bottleneck.prototype.strategy.LEAK ? _this5._getFirst(_this5._queues.slice(options.priority).reverse()).shift() : strategy === Bottleneck.prototype.strategy.OVERFLOW_PRIORITY ? _this5._getFirst(_this5._queues.slice(options.priority + 1).reverse()).shift() : strategy === Bottleneck.prototype.strategy.OVERFLOW ? job : void 0;
if (shifted != null) {
_this5._drop(shifted);
}
if (shifted == null || strategy === Bottleneck.prototype.strategy.OVERFLOW) {
if (shifted == null) {
_this5._drop(job);
}
return reachedHWM;
}
}
_this5._states.next(job.options.id); // QUEUED
_this5._queues[options.priority].push(job);
yield _this5._drainAll();
return reachedHWM;
}));
}
schedule(...args) {
var options, task, wrapped;
if (typeof args[0] === "function") {
var _args = args;
var _args2 = _toArray(_args);
task = _args2[0];
args = _args2.slice(1);
options = this.jobDefaults;
} else {
var _args3 = args;
var _args4 = _toArray(_args3);
options = _args4[0];
task = _args4[1];
args = _args4.slice(2);
options = parser.load(options, this.jobDefaults);
}
wrapped = function wrapped(...args) {
var _ref9, _ref10, _splice$call5, _splice$call6;
var cb, ref, returned;
ref = args, (_ref9 = ref, _ref10 = _toArray(_ref9), args = _ref10.slice(0), _ref9), (_splice$call5 = splice.call(args, -1), _splice$call6 = _slicedToArray(_splice$call5, 1), cb = _splice$call6[0], _splice$call5);
returned = task.apply({}, args);
return (!((returned != null ? returned.then : void 0) != null && typeof returned.then === "function") ? Promise.resolve(returned) : returned).then(function (...args) {
return cb.apply({}, Array.prototype.concat(null, args));
}).catch(function (...args) {
return cb.apply({}, args);
});
};
return new this.Promise((resolve, reject) => {
return this.submit.apply({}, Array.prototype.concat(options, wrapped, args, function (...args) {
return (args[0] != null ? reject : (args.shift(), resolve)).apply({}, args);
})).catch(e => {
return this.Events.trigger("error", [e]);
});
});
}
wrap(fn) {
return (...args) => {
return this.schedule.apply({}, Array.prototype.concat(fn, args));
};
}
updateSettings(options = {}) {
var _this6 = this;
return _asyncToGenerator(function* () {
yield _this6._store.__updateSettings__(parser.overwrite(options, _this6.storeDefaults));
parser.overwrite(options, _this6.instanceDefaults, _this6);
_this6._drainAll().catch(function (e) {
return _this6.Events.trigger("error", [e]);
});
return _this6;
})();
}
currentReservoir() {
var _this7 = this;
return _asyncToGenerator(function* () {
return yield _this7._store.__currentReservoir__();
})();
}
incrementReservoir(incr = 0) {
var _this8 = this;
return _asyncToGenerator(function* () {
yield _this8._store.__incrementReservoir__(incr);
_this8._drainAll().catch(function (e) {
return _this8.Events.trigger("error", [e]);
});
return _this8;
})();
}
};
Bottleneck.default = Bottleneck;
Bottleneck.version = Bottleneck.prototype.version = packagejson.version;
Bottleneck.strategy = Bottleneck.prototype.strategy = {
LEAK: 1,
OVERFLOW: 2,
OVERFLOW_PRIORITY: 4,
BLOCK: 3
};
Bottleneck.BottleneckError = Bottleneck.prototype.BottleneckError = require("./BottleneckError");
Bottleneck.Group = Bottleneck.prototype.Group = require("./Group");
Bottleneck.prototype.jobDefaults = {
priority: DEFAULT_PRIORITY,
weight: 1,
expiration: null,
id: "<no-id>"
};
Bottleneck.prototype.storeDefaults = {
maxConcurrent: null,
minTime: 0,
highWater: null,
strategy: Bottleneck.prototype.strategy.LEAK,
penalty: null,
reservoir: null
};
Bottleneck.prototype.storeInstanceDefaults = {
clientOptions: {},
clearDatastore: false,
Promise: Promise,
_groupTimeout: null
};
Bottleneck.prototype.instanceDefaults = {
datastore: "local",
id: "<no-id>",
rejectOnDrop: true,
trackDoneStatus: false,
Promise: Promise
};
return Bottleneck;
}.call(this);
module.exports = Bottleneck;
}).call(undefined);
},{"../package.json":13,"./BottleneckError":2,"./DLList":3,"./Events":4,"./Group":5,"./Local":6,"./RedisStorage":7,"./States":8,"./Sync":9,"./parser":12}],2:[function(require,module,exports){
"use strict";
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError;
BottleneckError = class BottleneckError extends Error {};
module.exports = BottleneckError;
}).call(undefined);
},{}],3:[function(require,module,exports){
"use strict";
// Generated by CoffeeScript 2.2.4
(function () {
var DLList;
DLList = class DLList {
constructor() {
this._first = null;
this._last = null;
this.length = 0;
}
push(value) {
var node;
this.length++;
node = {
value,
next: null
};
if (this._last != null) {
this._last.next = node;
this._last = node;
} else {
this._first = this._last = node;
}
return void 0;
}
shift() {
var ref1, value;
if (this._first == null) {
return void 0;
} else {
this.length--;
}
value = this._first.value;
this._first = (ref1 = this._first.next) != null ? ref1 : this._last = null;
return value;
}
first() {
if (this._first != null) {
return this._first.value;
}
}
getArray() {
var node, ref, results;
node = this._first;
results = [];
while (node != null) {
results.push((ref = node, node = node.next, ref.value));
}
return results;
}
};
module.exports = DLList;
}).call(undefined);
},{}],4:[function(require,module,exports){
"use strict";
// Generated by CoffeeScript 2.2.4
(function () {
var Events;
Events = class Events {
constructor(instance) {
this.instance = instance;
this._events = {};
this.instance.on = (name, cb) => {
return this._addListener(name, "many", cb);
};
this.instance.once = (name, cb) => {
return this._addListener(name, "once", cb);
};
this.instance.removeAllListeners = (name = null) => {
if (name != null) {
return delete this._events[name];
} else {
return this._events = {};
}
};
}
_addListener(name, status, cb) {
var base;
if ((base = this._events)[name] == null) {
base[name] = [];
}
this._events[name].push({ cb, status });
return this.instance;
}
trigger(name, args) {
if (name !== "debug") {
this.trigger("debug", [`Event triggered: ${name}`, args]);
}
if (this._events[name] == null) {
return;
}
this._events[name] = this._events[name].filter(function (listener) {
return listener.status !== "none";
});
return this._events[name].forEach(listener => {
var e, ret;
if (listener.status === "none") {
return;
}
if (listener.status === "once") {
listener.status = "none";
}
try {
ret = listener.cb.apply({}, args);
if (typeof (ret != null ? ret.then : void 0) === "function") {
return ret.then(function () {}).catch(e => {
return this.trigger("error", [e]);
});
}
} catch (error) {
e = error;
if ("name" !== "error") {
return this.trigger("error", [e]);
}
}
});
}
};
module.exports = Events;
}).call(undefined);
},{}],5:[function(require,module,exports){
"use strict";
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 Events, Group, parser;
parser = require("./parser");
Events = require("./Events");
Group = function () {
class Group {
constructor(limiterOptions = {}, groupOptions = {}) {
this.key = this.key.bind(this);
this.deleteKey = this.deleteKey.bind(this);
this.limiters = this.limiters.bind(this);
this.keys = this.keys.bind(this);
this._startAutoCleanup = this._startAutoCleanup.bind(this);
this.updateSettings = this.updateSettings.bind(this);
this.limiterOptions = limiterOptions;
parser.load(groupOptions, this.defaults, this);
this.Events = new Events(this);
this.instances = {};
this.Bottleneck = require("./Bottleneck");
this._startAutoCleanup();
}
key(key = "") {
var ref;
return (ref = this.instances[key]) != null ? ref : (() => {
var limiter;
limiter = this.instances[key] = new this.Bottleneck(Object.assign(this.limiterOptions, {
id: `group-key-${key}`,
_groupTimeout: this.timeout
}));
this.Events.trigger("created", [limiter, key]);
return limiter;
})();
}
deleteKey(key = "") {
var ref;
if ((ref = this.instances[key]) != null) {
ref.disconnect();
}
return delete this.instances[key];
}
limiters() {
var k, ref, results, v;
ref = this.instances;
results = [];
for (k in ref) {
v = ref[k];
results.push({
key: k,
limiter: v
});
}
return results;
}
keys() {
return Object.keys(this.instances);
}
_startAutoCleanup() {
var _this = this;
var base;
clearInterval(this.interval);
return typeof (base = this.interval = setInterval(_asyncToGenerator(function* () {
var e, k, ref, results, time, v;
time = Date.now();
ref = _this.instances;
results = [];
for (k in ref) {
v = ref[k];
try {
if (yield v._store.__groupCheck__(time)) {
results.push(_this.deleteKey(k));
} else {
results.push(void 0);
}
} catch (error) {
e = error;
results.push(v.Events.trigger("error", [e]));
}
}
return results;
}), this.timeout / 2)).unref === "function" ? base.unref() : void 0;
}
updateSettings(options = {}) {
parser.overwrite(options, this.defaults, this);
if (options.timeout != null) {
return this._startAutoCleanup();
}
}
};
Group.prototype.defaults = {
timeout: 1000 * 60 * 5
};
return Group;
}.call(this);
module.exports = Group;
}).call(undefined);
},{"./Bottleneck":1,"./Events":4,"./parser":12}],6:[function(require,module,exports){
"use strict";
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, Local, parser;
parser = require("./parser");
DLList = require("./DLList");
BottleneckError = require("./BottleneckError");
Local = class Local {
constructor(options) {
parser.load(options, options, this);
this._nextRequest = Date.now();
this._running = 0;
this._executing = {};
this._unblockTime = 0;
this.ready = this.yieldLoop();
this.clients = {};
}
disconnect(flush) {
return this;
}
yieldLoop(t = 0) {
return new this.Promise(function (resolve, reject) {
return setTimeout(resolve, t);
});
}
computePenalty() {
var ref;
return (ref = this.penalty) != null ? ref : 15 * this.minTime || 5000;
}
__updateSettings__(options) {
var _this = this;
return _asyncToGenerator(function* () {
yield _this.yieldLoop();
parser.overwrite(options, options, _this);
return true;
})();
}
__running__() {
var _this2 = this;
return _asyncToGenerator(function* () {
yield _this2.yieldLoop();
return _this2._running;
})();
}
__groupCheck__(time) {
var _this3 = this;
return _asyncToGenerator(function* () {
yield _this3.yieldLoop();
return _this3._nextRequest + _this3._groupTimeout < time;
})();
}
conditionsCheck(weight) {
return (this.maxConcurrent == null || this._running + weight <= this.maxConcurrent) && (this.reservoir == null || this.reservoir - weight >= 0);
}
__incrementReservoir__(incr) {
var _this4 = this;
return _asyncToGenerator(function* () {
yield _this4.yieldLoop();
return _this4.reservoir += incr;
})();
}
__currentReservoir__() {
var _this5 = this;
return _asyncToGenerator(function* () {
yield _this5.yieldLoop();
return _this5.reservoir;
})();
}
isBlocked(now) {
return this._unblockTime >= now;
}
check(weight, now) {
return this.conditionsCheck(weight) && this._nextRequest - now <= 0;
}
__check__(weight) {
var _this6 = this;
return _asyncToGenerator(function* () {
var now;
yield _this6.yieldLoop();
now = Date.now();
return _this6.check(weight, now);
})();
}
__register__(index, weight, expiration) {
var _this7 = this;
return _asyncToGenerator(function* () {
var now, wait;
yield _this7.yieldLoop();
now = Date.now();
if (_this7.conditionsCheck(weight)) {
_this7._running += weight;
_this7._executing[index] = {
timeout: expiration != null ? setTimeout(function () {
if (!_this7._executing[index].freed) {
_this7._executing[index].freed = true;
return _this7._running -= weight;
}
}, expiration) : void 0,
freed: false
};
if (_this7.reservoir != null) {
_this7.reservoir -= weight;
}
wait = Math.max(_this7._nextRequest - now, 0);
_this7._nextRequest = now + wait + _this7.minTime;
return {
success: true,
wait,
reservoir: _this7.reservoir
};
} else {
return {
success: false
};
}
})();
}
strategyIsBlock() {
return this.strategy === 3;
}
__submit__(queueLength, weight) {
var _this8 = this;
return _asyncToGenerator(function* () {
var blocked, now, reachedHWM;
yield _this8.yieldLoop();
if (_this8.maxConcurrent != null && weight > _this8.maxConcurrent) {
throw new BottleneckError(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${_this8.maxConcurrent}`);
}
now = Date.now();
reachedHWM = _this8.highWater != null && queueLength === _this8.highWater && !_this8.check(weight, now);
blocked = _this8.strategyIsBlock() && (reachedHWM || _this8.isBlocked(now));
if (blocked) {
_this8._unblockTime = now + _this8.computePenalty();
_this8._nextRequest = _this8._unblockTime + _this8.minTime;
}
return {
reachedHWM,
blocked,
strategy: _this8.strategy
};
})();
}
__free__(index, weight) {
var _this9 = this;
return _asyncToGenerator(function* () {
yield _this9.yieldLoop();
clearTimeout(_this9._executing[index].timeout);
if (!_this9._executing[index].freed) {
_this9._executing[index].freed = true;
_this9._running -= weight;
}
return {
running: _this9._running
};
})();
}
};
module.exports = Local;
}).call(undefined);
},{"./BottleneckError":2,"./DLList":3,"./parser":12}],7:[function(require,module,exports){
"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);
},{"./BottleneckError":2,"./DLList":3,"./lua.json":11,"./parser":12}],8:[function(require,module,exports){
"use strict";
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, States;
BottleneckError = require("./BottleneckError");
States = class States {
constructor(status) {
this.status = status;
this.jobs = {};
this.counts = this.status.map(function () {
return 0;
});
}
next(id) {
var current, next;
current = this.jobs[id];
next = current + 1;
if (current != null && next < this.status.length) {
this.counts[current]--;
this.counts[next]++;
return this.jobs[id]++;
} else if (current != null) {
this.counts[current]--;
return delete this.jobs[id];
}
}
start(id, initial = 0) {
this.jobs[id] = initial;
return this.counts[initial]++;
}
remove(id) {
var current;
current = this.jobs[id];
if (current != null) {
this.counts[current]--;
return delete this.jobs[id];
}
}
jobStatus(id) {
var ref;
return (ref = this.status[this.jobs[id]]) != null ? ref : null;
}
statusCounts() {
return this.counts.reduce((acc, v, i) => {
acc[this.status[i]] = v;
return acc;
}, {});
}
};
module.exports = States;
}).call(undefined);
},{"./BottleneckError":2}],9:[function(require,module,exports){
"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 _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
// Generated by CoffeeScript 2.2.4
(function () {
var DLList,
Sync,
splice = [].splice;
DLList = require("./DLList");
Sync = class Sync {
constructor(name) {
this.submit = this.submit.bind(this);
this.schedule = this.schedule.bind(this);
this.name = name;
this._running = 0;
this._queue = new DLList();
}
isEmpty() {
return this._queue.length === 0;
}
_tryToRun() {
var next;
if (this._running < 1 && this._queue.length > 0) {
this._running++;
next = this._queue.shift();
return next.task.apply({}, next.args.concat((...args) => {
var ref;
this._running--;
this._tryToRun();
return (ref = next.cb) != null ? ref.apply({}, args) : void 0;
}));
}
}
submit(task, ...args) {
var _ref, _ref2, _splice$call, _splice$call2;
var cb, ref;
ref = args, (_ref = ref, _ref2 = _toArray(_ref), args = _ref2.slice(0), _ref), (_splice$call = splice.call(args, -1), _splice$call2 = _slicedToArray(_splice$call, 1), cb = _splice$call2[0], _splice$call);
this._queue.push({ task, args, cb });
return this._tryToRun();
}
schedule(task, ...args) {
var wrapped;
wrapped = function wrapped(...args) {
var _ref3, _ref4, _splice$call3, _splice$call4;
var cb, ref;
ref = args, (_ref3 = ref, _ref4 = _toArray(_ref3), args = _ref4.slice(0), _ref3), (_splice$call3 = splice.call(args, -1), _splice$call4 = _slicedToArray(_splice$call3, 1), cb = _splice$call4[0], _splice$call3);
return task.apply({}, args).then(function (...args) {
return cb.apply({}, Array.prototype.concat(null, args));
}).catch(function (...args) {
return cb.apply({}, args);
});
};
return new Promise((resolve, reject) => {
return this.submit.apply({}, Array.prototype.concat(wrapped, args, function (...args) {
return (args[0] != null ? reject : (args.shift(), resolve)).apply({}, args);
}));
});
}
};
module.exports = Sync;
}).call(undefined);
},{"./DLList":3}],10:[function(require,module,exports){
"use strict";
// Generated by CoffeeScript 2.2.4
(function () {
module.exports = require("./Bottleneck");
}).call(undefined);
},{"./Bottleneck":1}],11:[function(require,module,exports){
module.exports={
"check.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal weight = tonumber(ARGV[1])\nlocal now = tonumber(ARGV[2])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'reservoir',\n 'nextRequest'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal reservoir = tonumber(settings[2])\nlocal nextRequest = tonumber(settings[3])\n\nlocal conditionsCheck = conditions_check(weight, maxConcurrent, running, reservoir)\n\nlocal result = conditionsCheck and nextRequest - now <= 0\n\nreturn result\n",
"conditions_check.lua": "local conditions_check = function (weight, maxConcurrent, running, reservoir)\n return (\n (maxConcurrent == nil or running + weight <= maxConcurrent) and\n (reservoir == nil or reservoir - weight >= 0)\n )\nend\n",
"current_reservoir.lua": "local settings_key = KEYS[1]\n\nreturn tonumber(redis.call('hget', settings_key, 'reservoir'))\n",
"free.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal index = ARGV[1]\nlocal now = ARGV[2]\n\nredis.call('zadd', executing_key, 0, index)\n\nreturn refresh_running(executing_key, running_key, settings_key, now)\n",
"get_time.lua": "redis.replicate_commands()\n\nlocal get_time = function ()\n local time = redis.call('time')\n\n return tonumber(time[1]..string.sub(time[2], 1, 3))\nend\n",
"group_check.lua": "local settings_key = KEYS[1]\n\nreturn not (redis.call('exists', settings_key) == 1)\n",
"increment_reservoir.lua": "local settings_key = KEYS[1]\nlocal incr = ARGV[1]\n\nreturn redis.call('hincrby', settings_key, 'reservoir', incr)\n",
"init.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nloc