bottleneck
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -10,3 +10,4 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
LEAK: 1, | ||
OVERFLOW: 2 | ||
OVERFLOW: 2, | ||
BLOCK: 3 | ||
}; | ||
@@ -23,2 +24,4 @@ | ||
this._timeouts = []; | ||
this._unblockTime = 0; | ||
this.penalty = 5 * this.minTime; | ||
} | ||
@@ -55,3 +58,6 @@ | ||
reachedHighWaterMark = this.highWater > 0 && this._queue.length === this.highWater; | ||
if (reachedHighWaterMark) { | ||
if (this.strategy === Bottleneck.prototype.strategy.BLOCK && (reachedHighWaterMark || this._unblockTime >= Date.now())) { | ||
this._unblockTime = Date.now() + this.penalty; | ||
return true; | ||
} else if (reachedHighWaterMark) { | ||
if (this.strategy === Bottleneck.prototype.strategy.LEAK) { | ||
@@ -80,2 +86,7 @@ this._queue.shift(); | ||
Bottleneck.prototype.changePenalty = function(penalty) { | ||
this.penalty = penalty != null ? penalty : this.penalty; | ||
return this; | ||
}; | ||
Bottleneck.prototype.stopAll = function() { | ||
@@ -82,0 +93,0 @@ var a, _i, _len, _ref; |
@@ -1,1 +0,1 @@ | ||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){(function(){var Bottleneck,__slice=[].slice;Bottleneck=function(){Bottleneck.strategy=Bottleneck.prototype.strategy={LEAK:1,OVERFLOW:2};function Bottleneck(maxNb,minTime,highWater,strategy){this.maxNb=maxNb!=null?maxNb:0;this.minTime=minTime!=null?minTime:0;this.highWater=highWater!=null?highWater:0;this.strategy=strategy!=null?strategy:Bottleneck.prototype.strategy.LEAK;this._nextRequest=Date.now();this._nbRunning=0;this._queue=[];this._timeouts=[]}Bottleneck.prototype._tryToRun=function(){var done,index,next,wait;if((this._nbRunning<this.maxNb||this.maxNb<=0)&&this._queue.length>0){this._nbRunning++;wait=Math.max(this._nextRequest-Date.now(),0);this._nextRequest=Date.now()+wait+this.minTime;next=this._queue.shift();done=false;return index=-1+this._timeouts.push(setTimeout(function(_this){return function(){return next.task.apply({},next.args.concat(function(){var _ref;if(!done){done=true;delete _this._timeouts[index];_this._nbRunning--;_this._tryToRun();return(_ref=next.cb)!=null?_ref.apply({},Array.prototype.slice.call(arguments,0)):void 0}}))}}(this),wait))}};Bottleneck.prototype.submit=function(){var args,cb,reachedHighWaterMark,task,_i;task=arguments[0],args=3<=arguments.length?__slice.call(arguments,1,_i=arguments.length-1):(_i=1,[]),cb=arguments[_i++];reachedHighWaterMark=this.highWater>0&&this._queue.length===this.highWater;if(reachedHighWaterMark){if(this.strategy===Bottleneck.prototype.strategy.LEAK){this._queue.shift()}else if(this.strategy===Bottleneck.prototype.strategy.OVERFLOW){return reachedHighWaterMark}}this._queue.push({task:task,args:args,cb:cb});this._tryToRun();return reachedHighWaterMark};Bottleneck.prototype.changeSettings=function(maxNb,minTime,highWater,strategy){this.maxNb=maxNb!=null?maxNb:this.maxNb;this.minTime=minTime!=null?minTime:this.minTime;this.highWater=highWater!=null?highWater:this.highWater;this.strategy=strategy!=null?strategy:this.strategy;return this};Bottleneck.prototype.stopAll=function(){var a,_i,_len,_ref;_ref=this._timeouts;for(_i=0,_len=_ref.length;_i<_len;_i++){a=_ref[_i];clearTimeout(a)}this._tryToRun=function(){};return this.submit=function(){}};return Bottleneck}();module.exports=Bottleneck}).call(this)},{}],2:[function(require,module,exports){(function(global){(function(){module.exports=require("./Bottleneck");if(global.window!=null){global.window.Bottleneck=module.exports}}).call(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./Bottleneck":1}]},{},[2]); | ||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){(function(){var Bottleneck,__slice=[].slice;Bottleneck=function(){Bottleneck.strategy=Bottleneck.prototype.strategy={LEAK:1,OVERFLOW:2,BLOCK:3};function Bottleneck(maxNb,minTime,highWater,strategy){this.maxNb=maxNb!=null?maxNb:0;this.minTime=minTime!=null?minTime:0;this.highWater=highWater!=null?highWater:0;this.strategy=strategy!=null?strategy:Bottleneck.prototype.strategy.LEAK;this._nextRequest=Date.now();this._nbRunning=0;this._queue=[];this._timeouts=[];this._unblockTime=0;this.penalty=5*this.minTime}Bottleneck.prototype._tryToRun=function(){var done,index,next,wait;if((this._nbRunning<this.maxNb||this.maxNb<=0)&&this._queue.length>0){this._nbRunning++;wait=Math.max(this._nextRequest-Date.now(),0);this._nextRequest=Date.now()+wait+this.minTime;next=this._queue.shift();done=false;return index=-1+this._timeouts.push(setTimeout(function(_this){return function(){return next.task.apply({},next.args.concat(function(){var _ref;if(!done){done=true;delete _this._timeouts[index];_this._nbRunning--;_this._tryToRun();return(_ref=next.cb)!=null?_ref.apply({},Array.prototype.slice.call(arguments,0)):void 0}}))}}(this),wait))}};Bottleneck.prototype.submit=function(){var args,cb,reachedHighWaterMark,task,_i;task=arguments[0],args=3<=arguments.length?__slice.call(arguments,1,_i=arguments.length-1):(_i=1,[]),cb=arguments[_i++];reachedHighWaterMark=this.highWater>0&&this._queue.length===this.highWater;if(this.strategy===Bottleneck.prototype.strategy.BLOCK&&(reachedHighWaterMark||this._unblockTime>=Date.now())){this._unblockTime=Date.now()+this.penalty;return true}else if(reachedHighWaterMark){if(this.strategy===Bottleneck.prototype.strategy.LEAK){this._queue.shift()}else if(this.strategy===Bottleneck.prototype.strategy.OVERFLOW){return reachedHighWaterMark}}this._queue.push({task:task,args:args,cb:cb});this._tryToRun();return reachedHighWaterMark};Bottleneck.prototype.changeSettings=function(maxNb,minTime,highWater,strategy){this.maxNb=maxNb!=null?maxNb:this.maxNb;this.minTime=minTime!=null?minTime:this.minTime;this.highWater=highWater!=null?highWater:this.highWater;this.strategy=strategy!=null?strategy:this.strategy;return this};Bottleneck.prototype.changePenalty=function(penalty){this.penalty=penalty!=null?penalty:this.penalty;return this};Bottleneck.prototype.stopAll=function(){var a,_i,_len,_ref;_ref=this._timeouts;for(_i=0,_len=_ref.length;_i<_len;_i++){a=_ref[_i];clearTimeout(a)}this._tryToRun=function(){};return this.submit=function(){}};return Bottleneck}();module.exports=Bottleneck}).call(this)},{}],2:[function(require,module,exports){(function(global){(function(){module.exports=require("./Bottleneck");if(global.window!=null){global.window.Bottleneck=module.exports}}).call(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./Bottleneck":1}]},{},[2]); |
@@ -9,3 +9,4 @@ // Generated by CoffeeScript 1.7.1 | ||
LEAK: 1, | ||
OVERFLOW: 2 | ||
OVERFLOW: 2, | ||
BLOCK: 3 | ||
}; | ||
@@ -22,2 +23,4 @@ | ||
this._timeouts = []; | ||
this._unblockTime = 0; | ||
this.penalty = 5 * this.minTime; | ||
} | ||
@@ -54,3 +57,6 @@ | ||
reachedHighWaterMark = this.highWater > 0 && this._queue.length === this.highWater; | ||
if (reachedHighWaterMark) { | ||
if (this.strategy === Bottleneck.prototype.strategy.BLOCK && (reachedHighWaterMark || this._unblockTime >= Date.now())) { | ||
this._unblockTime = Date.now() + this.penalty; | ||
return true; | ||
} else if (reachedHighWaterMark) { | ||
if (this.strategy === Bottleneck.prototype.strategy.LEAK) { | ||
@@ -79,2 +85,7 @@ this._queue.shift(); | ||
Bottleneck.prototype.changePenalty = function(penalty) { | ||
this.penalty = penalty != null ? penalty : this.penalty; | ||
return this; | ||
}; | ||
Bottleneck.prototype.stopAll = function() { | ||
@@ -81,0 +92,0 @@ var a, _i, _len, _ref; |
{ | ||
"name": "bottleneck", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Async rate limiter", | ||
@@ -18,7 +18,6 @@ "main": "lib/index.js", | ||
"rate limiter", | ||
"queues", | ||
"timing", | ||
"load", | ||
"limiter", | ||
"load", | ||
"synchronize" | ||
"ddos" | ||
], | ||
@@ -25,0 +24,0 @@ "author": { |
@@ -6,5 +6,3 @@ bottleneck | ||
Databases, file systems, network access, APIs, etc. are all services that can easily be overwhelmed. | ||
#Install | ||
@@ -28,4 +26,4 @@ | ||
// Never more than 1 request running at a time. | ||
// Wait at least 2000ms between each request. | ||
// Never more than 1 request running at a time. | ||
var limiter = new Bottleneck(1, 2000); | ||
@@ -49,26 +47,31 @@ ``` | ||
###Constructor | ||
```new Bottleneck(maxNb, minTime, highWater, strategy);``` | ||
```new Bottleneck(maxConcurrent, minTime, highWater, strategy);``` | ||
* maxNb : How many requests can be running at the same time. Default: 0 (unlimited) | ||
* minTime : How long to wait after launching a request before launching another one. Default: 0ms | ||
* highWater : How long can the queue get? Default: 0 (unlimited) | ||
* strategy : Which strategy use if the queue gets longer than the high water mark. Default: Bottleneck.strategy.LEAK. | ||
* maxConcurrent : How many requests can be running at the same time. *Default: 0 (unlimited)* | ||
* minTime : How long to wait after launching a request before launching another one. *Default: 0ms* | ||
* highWater : How long can the queue get? *Default: 0 (unlimited)* | ||
* strategy : Which strategy use if the queue gets longer than the high water mark. *Default: `Bottleneck.strategy.LEAK`.* | ||
###submit() | ||
This adds a request to the queue. | ||
This adds a request to the queue, see the example above. | ||
It returns true if the queue's length is under the high water mark, otherwise it returns false. | ||
It returns `true` if the strategy was executed. Therefore it will always return `false` if `highWater` is set to 0. | ||
If a callback isn't necessary, you must pass ```null``` instead. | ||
If a callback isn't necessary, you must pass `null` or an empty function instead. | ||
Make sure that all the requests will eventually complete! This is very important if you are using a `maxConcurrent` value that isn't 0 (unlimited), otherwise those uncompleted requests will be clogging up the limiter and no new requests will be getting through. A way to do this is to use a timer that will always call the callback. It's safe to call the callback more than once, subsequent calls are ignored. | ||
###strategies | ||
####Bottleneck.strategy.LEAK | ||
When submitting a new request, if the highWater mark is reached, drop the oldest request in the queue. This is useful when requests that have been waiting for too long are not important anymore. | ||
When submitting a new request, if the queue length reaches `highWater`, drop the oldest request in the queue. This is useful when requests that have been waiting for too long are not important anymore. | ||
####Bottleneck.strategy.OVERFLOW | ||
When submitting a new request, if the highWater mark is reached, do not add that request. The ```submit``` call did nothing. | ||
When submitting a new request, if the queue length reaches `highWater`, do not add the new request. | ||
####Bottleneck.strategy.BLOCK | ||
When submitting a new request, if the queue length reaches `highWater`, the limiter falls into "blocked mode". No new requests will be accepted until it unblocks. It will unblock after `penalty` milliseconds have passed without receiving a new request. `penalty` is equal to `5 * minTime` by default and can be changed by calling `changePenalty()`. This strategy is ideal when bruteforce attacks are to be expected. | ||
###stopAll() | ||
@@ -82,4 +85,21 @@ ```javascript | ||
```javascript | ||
limiter.changeSettings(maxNb, minTime, highWater, strategy) | ||
limiter.changeSettings(maxConcurrent, minTime, highWater, strategy); | ||
``` | ||
Same parameters as the constructor, pass ```null``` to skip a parameter and keep it to its current value. | ||
###changePenalty() | ||
```javascript | ||
limiter.changePenalty(penalty); | ||
``` | ||
This changes the `penalty` value used by the BLOCK strategy. | ||
# Thoughts | ||
The main design goal for Bottleneck is to be extremely small and transparent for the developer. In services that see a lot of abuse such as DNS servers, it's not unreasonable to create one instance of Bottleneck for each origin IP. The `BLOCK` strategy will then easily lock out abusers and prevent the server from being used for a [DNS amplification attack](http://blog.cloudflare.com/65gbps-ddos-no-problem). | ||
Other times, the application acts as a client and Bottleneck is used to not overload the server. In those cases, it's often better to not set any `highWater` mark so that no request is ever lost. | ||
As long as `highWater` is 0, all requests are assured to be executed at some point. Once again, when using a `maxConcurrent` value greater than 0, make sure that all requests will call the callback eventually. | ||
Pull requests and suggestions are welcome. |
Sorry, the diff of this file is not supported yet
19761
205
102