From d44c51465e5a5c445f7d58dfeb634e5a4af5bf44 Mon Sep 17 00:00:00 2001 From: SGrondin Date: Mon, 14 Jul 2014 12:01:55 -0400 Subject: [PATCH] 1.4.0 check() and interrupt --- README.md | 18 ++++++++++++++---- bottleneck.js | 22 ++++++++++++++++++---- bottleneck.min.js | 2 +- lib/Bottleneck.js | 22 ++++++++++++++++++---- package.json | 2 +- src/Bottleneck.coffee | 16 ++++++++++------ 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5f4eb14..54488aa 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,9 @@ All the submitted requests will be executed *in order*. #Docs ###Constructor -```var limiter = new Bottleneck(maxConcurrent, minTime, highWater, strategy);``` +```javascript +var limiter = new Bottleneck(maxConcurrent, minTime, highWater, strategy); +``` * 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* @@ -70,14 +72,22 @@ When submitting a new request, if the queue length reaches `highWater`, drop the 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 `8 * minTime` by default and can be changed by calling `changePenalty()`. This strategy is ideal when bruteforce attacks are to be expected. +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 `15 * minTime` (or 5000 if `minTime` is 0) by default and can be changed by calling `changePenalty()`. This strategy is ideal when bruteforce attacks are to be expected. + +###check() +```javascript +limiter.check(); +``` +If a task was submitted right now, would it be run immediately? Returns a boolean. ###stopAll() ```javascript -limiter.stopAll(); +limiter.stopAll(interrupt); ``` -Cancels all queued up requests and prevents additonal requests from being submitted. +Cancels all *queued up* requests and prevents additonal requests from being submitted. + +* interrupt : If true, prevent the tasks currently running from calling their callback when they're done. *Default: false* ###changeSettings() ```javascript diff --git a/bottleneck.js b/bottleneck.js index 679e918..3c1a1e3 100644 --- a/bottleneck.js +++ b/bottleneck.js @@ -21,9 +21,14 @@ this._queue = []; this._timeouts = []; this._unblockTime = 0; - this.penalty = 8 * this.minTime; + this.penalty = (15 * this.minTime) || 5000; + this.interrupt = false; } + Bottleneck.prototype.check = function() { + return (this._nbRunning < this.maxNb || this.maxNb <= 0) && (this._nextRequest - Date.now()) <= 0; + }; + Bottleneck.prototype._tryToRun = function() { var done, index, next, wait; if ((this._nbRunning < this.maxNb || this.maxNb <= 0) && this._queue.length > 0) { @@ -41,7 +46,9 @@ delete _this._timeouts[index]; _this._nbRunning--; _this._tryToRun(); - return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; + if (!_this.interrupt) { + return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; + } } })); }; @@ -55,6 +62,7 @@ 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; + this._nextRequest = this._unblockTime + this.minTime; return true; } else if (reachedHighWaterMark) { if (this.strategy === Bottleneck.prototype.strategy.LEAK) { @@ -85,15 +93,21 @@ return this; }; - Bottleneck.prototype.stopAll = function() { + Bottleneck.prototype.stopAll = function(interrupt) { var a, _i, _len, _ref; + this.interrupt = interrupt != null ? interrupt : this.interrupt; _ref = this._timeouts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { a = _ref[_i]; clearTimeout(a); } this._tryToRun = function() {}; - return this.submit = function() {}; + this.submit = function() { + return false; + }; + return this.check = function() { + return false; + }; }; return Bottleneck; diff --git a/bottleneck.min.js b/bottleneck.min.js index 4f35b0d..e92861c 100644 --- a/bottleneck.min.js +++ b/bottleneck.min.js @@ -1 +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;o0){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]); \ No newline at end of file +(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;o0){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();if(!_this.interrupt){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;this._nextRequest=this._unblockTime+this.minTime;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(interrupt){var a,_i,_len,_ref;this.interrupt=interrupt!=null?interrupt:this.interrupt;_ref=this._timeouts;for(_i=0,_len=_ref.length;_i<_len;_i++){a=_ref[_i];clearTimeout(a)}this._tryToRun=function(){};this.submit=function(){return false};return this.check=function(){return false}};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]); \ No newline at end of file diff --git a/lib/Bottleneck.js b/lib/Bottleneck.js index 80623a6..a75cc6c 100644 --- a/lib/Bottleneck.js +++ b/lib/Bottleneck.js @@ -20,9 +20,14 @@ this._queue = []; this._timeouts = []; this._unblockTime = 0; - this.penalty = 8 * this.minTime; + this.penalty = (15 * this.minTime) || 5000; + this.interrupt = false; } + Bottleneck.prototype.check = function() { + return (this._nbRunning < this.maxNb || this.maxNb <= 0) && (this._nextRequest - Date.now()) <= 0; + }; + Bottleneck.prototype._tryToRun = function() { var done, index, next, wait; if ((this._nbRunning < this.maxNb || this.maxNb <= 0) && this._queue.length > 0) { @@ -40,7 +45,9 @@ delete _this._timeouts[index]; _this._nbRunning--; _this._tryToRun(); - return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; + if (!_this.interrupt) { + return (_ref = next.cb) != null ? _ref.apply({}, Array.prototype.slice.call(arguments, 0)) : void 0; + } } })); }; @@ -54,6 +61,7 @@ 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; + this._nextRequest = this._unblockTime + this.minTime; return true; } else if (reachedHighWaterMark) { if (this.strategy === Bottleneck.prototype.strategy.LEAK) { @@ -84,15 +92,21 @@ return this; }; - Bottleneck.prototype.stopAll = function() { + Bottleneck.prototype.stopAll = function(interrupt) { var a, _i, _len, _ref; + this.interrupt = interrupt != null ? interrupt : this.interrupt; _ref = this._timeouts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { a = _ref[_i]; clearTimeout(a); } this._tryToRun = function() {}; - return this.submit = function() {}; + this.submit = function() { + return false; + }; + return this.check = function() { + return false; + }; }; return Bottleneck; diff --git a/package.json b/package.json index 6f07acd..0072bda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bottleneck", - "version": "1.3.1", + "version": "1.4.0", "description": "Async rate limiter", "main": "lib/index.js", "scripts": { diff --git a/src/Bottleneck.coffee b/src/Bottleneck.coffee index 976b06e..197d40c 100644 --- a/src/Bottleneck.coffee +++ b/src/Bottleneck.coffee @@ -6,7 +6,9 @@ class Bottleneck @_queue = [] @_timeouts = [] @_unblockTime = 0 - @penalty = 8 * @minTime + @penalty = (15 * @minTime) or 5000 + @interrupt = false + check: -> (@_nbRunning < @maxNb or @maxNb <= 0) and (@_nextRequest-Date.now()) <= 0 _tryToRun: -> if (@_nbRunning < @maxNb or @maxNb <= 0) and @_queue.length > 0 @_nbRunning++ @@ -14,19 +16,20 @@ class Bottleneck @_nextRequest = Date.now() + wait + @minTime next = @_queue.shift() done = false - index = -1 + @_timeouts.push setTimeout () => - next.task.apply {}, next.args.concat () => + index = -1 + @_timeouts.push setTimeout => + next.task.apply {}, next.args.concat => if not done done = true delete @_timeouts[index] @_nbRunning-- @_tryToRun() - next.cb?.apply {}, Array::slice.call arguments, 0 + if not @interrupt then next.cb?.apply {}, Array::slice.call arguments, 0 , wait submit: (task, args..., cb) -> reachedHighWaterMark = @highWater > 0 and @_queue.length == @highWater if @strategy == Bottleneck::strategy.BLOCK and (reachedHighWaterMark or @_unblockTime >= Date.now()) @_unblockTime = Date.now() + @penalty + @_nextRequest = @_unblockTime + @minTime return true else if reachedHighWaterMark if @strategy == Bottleneck::strategy.LEAK then @_queue.shift() @@ -36,9 +39,10 @@ class Bottleneck reachedHighWaterMark changeSettings: (@maxNb=@maxNb, @minTime=@minTime, @highWater=@highWater, @strategy=@strategy) -> @ changePenalty: (@penalty=@penalty) -> @ - stopAll: -> + stopAll: (@interrupt=@interrupt) -> (clearTimeout a for a in @_timeouts) @_tryToRun = -> - @submit = -> + @submit = -> false + @check = -> false module.exports = Bottleneck