Comparing version 0.0.3 to 0.1.0
@@ -13,66 +13,75 @@ /* | ||
function ExponentialBackoff(options) { | ||
/** | ||
* Fibonacci back off. | ||
*/ | ||
function Backoff(options) { | ||
events.EventEmitter.call(this); | ||
options = options || {}; | ||
if (isDef(options.initialTimeout) && options.initialTimeout < 1) { | ||
if (isDef(options.initialDelay) && options.initialDelay < 1) { | ||
throw new Error('The initial timeout must be greater than 0.'); | ||
} else if (isDef(options.maxTimeout) && options.maxTimeout < 1) { | ||
} else if (isDef(options.maxDelay) && options.maxDelay < 1) { | ||
throw new Error('The maximal timeout must be greater than 0.'); | ||
} | ||
this.initialTimeout = options.initialTimeout || 100; | ||
this.maxTimeout = options.maxTimeout || 10000; | ||
this.initialDelay_ = options.initialDelay || 100; | ||
this.maxDelay_ = options.maxDelay || 10000; | ||
if (this.maxTimeout <= this.initialTimeout) { | ||
throw new Error('The maximal timeout must be greater ' + | ||
'than the the initial timeout.'); | ||
if (this.maxDelay_ <= this.initialDelay_) { | ||
throw new Error('The maximal backoff delay must be ' + | ||
'greater than the initial backoff delay.'); | ||
} | ||
this.backoffInProgress = false; | ||
this.backoffNumber = 0; | ||
this.backoffDelay = 0; | ||
this.timeoutID = -1; | ||
if (isDef(options.randomisationFactor) && | ||
(options.randomisationFactor < 0 || options.randomisationFactor > 1)) { | ||
throw new Error('The randomisation factor must be between 0 and 1.'); | ||
} | ||
this.randomisationFactor_ = options.randomisationFactor || 0; | ||
this.backoffDelay_ = 0; | ||
this.randomizedDelay_ = 0; | ||
this.nextBackoffDelay_ = this.initialDelay_; | ||
this.backoffNumber_ = 0; | ||
this.timeoutID_ = -1; | ||
this.handlers = { | ||
backoff: this.onBackoff.bind(this) | ||
backoff: this.onBackoff_.bind(this) | ||
}; | ||
} | ||
util.inherits(ExponentialBackoff, events.EventEmitter); | ||
util.inherits(Backoff, events.EventEmitter); | ||
ExponentialBackoff.prototype.EXPONENTIAL_FACTOR = 2; | ||
ExponentialBackoff.prototype.updateBackoffDelay = function() { | ||
if (this.backoffDelay < this.maxTimeout) { | ||
var multiplicativeFactor = Math.pow(this.EXPONENTIAL_FACTOR, this.backoffNumber); | ||
var delay = Math.min(this.initialTimeout * multiplicativeFactor, this.maxTimeout); | ||
this.backoffDelay = Math.round(delay); | ||
Backoff.prototype.backoff = function() { | ||
if (this.timeoutID_ !== -1) { | ||
throw new Error('Backoff in progress.'); | ||
} | ||
}; | ||
ExponentialBackoff.prototype.backoff = function() { | ||
if (this.backoffInProgress) { | ||
throw new Error('Backoff in progress.'); | ||
} | ||
this.updateBackoffDelay(); | ||
this.timeoutID = setTimeout(this.handlers.backoff, this.backoffDelay); | ||
this.backoffInProgress = true; | ||
this.backoffNumber++; | ||
this.backoffNumber_++; | ||
var backoffDelay = Math.min(this.nextBackoffDelay_, this.maxDelay_); | ||
this.nextBackoffDelay_ += this.backoffDelay_; | ||
this.backoffDelay_ = backoffDelay; | ||
var randomisationMultiple = 1 + Math.random() * this.randomisationFactor_; | ||
this.randomizedDelay_ = Math.round(backoffDelay * randomisationMultiple); | ||
this.timeoutID_ = setTimeout(this.handlers.backoff, this.randomizedDelay_); | ||
}; | ||
ExponentialBackoff.prototype.onBackoff = function(delay) { | ||
this.backoffInProgress = false; | ||
this.emit('backoff', this.backoffNumber, this.backoffDelay); | ||
Backoff.prototype.onBackoff_ = function() { | ||
this.timeoutID_ = -1; | ||
this.emit('backoff', this.backoffNumber_, this.randomizedDelay_); | ||
}; | ||
ExponentialBackoff.prototype.reset = function() { | ||
this.backoffInProgress = false; | ||
clearTimeout(this.timeoutID); | ||
this.backoffNumber = 0; | ||
this.backoffDelay = 0; | ||
this.emit('reset'); | ||
Backoff.prototype.reset = function() { | ||
clearTimeout(this.timeoutID_); | ||
this.timeoutID_ = -1; | ||
this.backoffNumber_ = 0; | ||
this.nextBackoffDelay_ = this.initialDelay_; | ||
this.randomizedDelay_ = 0; | ||
this.backoffDelay_ = 0; | ||
}; | ||
module.exports = ExponentialBackoff; | ||
module.exports = Backoff; | ||
@@ -6,4 +6,4 @@ #!/usr/bin/env node | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
initialDelay: 10, | ||
maxDelay: 1000 | ||
}); | ||
@@ -10,0 +10,0 @@ |
{ | ||
"name": "backoff", | ||
"description": "Exponential backoff implementation.", | ||
"version": "0.0.3", | ||
"description": "Fibonnaci backoff implementation.", | ||
"version": "0.1.0", | ||
"author": "Mathieu Turcotte <turcotte.mat@gmail.com>", | ||
"keywords": ["backoff", "exponential"], | ||
"keywords": ["backoff", "fibonnaci"], | ||
"repository": { | ||
@@ -12,3 +12,3 @@ "type": "git", | ||
"devDependencies": { | ||
"sinon": "1.3", | ||
"sinon": "1.4", | ||
"nodeunit": "0.7", | ||
@@ -15,0 +15,0 @@ "jshint": "0.7" |
@@ -1,4 +0,4 @@ | ||
# Exponential backoff implementation for Node.js [![Build Status](https://secure.travis-ci.org/MathieuTurcotte/node-backoff.png?branch=master)](http://travis-ci.org/MathieuTurcotte/node-backoff) | ||
# Backoff for Node.js [![Build Status](https://secure.travis-ci.org/MathieuTurcotte/node-backoff.png?branch=master)](http://travis-ci.org/MathieuTurcotte/node-backoff) | ||
An exponential backoff implementation for Node.js. | ||
Fibonnaci backoff implementation for Node.js. | ||
@@ -24,4 +24,5 @@ ## Installation | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
randomisationFactor: 0, | ||
initialDelay: 10, | ||
maxDelay: 1000 | ||
}); | ||
@@ -32,3 +33,3 @@ | ||
if (number < 10) { | ||
if (number < 12) { | ||
backoff.backoff(); | ||
@@ -45,13 +46,18 @@ } | ||
1 10ms | ||
2 20ms | ||
3 40ms | ||
4 80ms | ||
5 160ms | ||
6 320ms | ||
7 640ms | ||
8 1000ms | ||
9 1000ms | ||
10 1000ms | ||
2 10ms | ||
3 20ms | ||
4 30ms | ||
5 50ms | ||
6 80ms | ||
7 130ms | ||
8 210ms | ||
9 340ms | ||
10 550ms | ||
11 890ms | ||
12 1000ms | ||
``` | ||
Backoff objects are meant to be instantiated once and reused several times | ||
by calling `reset` after each successful backoff operation. | ||
## API | ||
@@ -67,21 +73,31 @@ | ||
options = { | ||
initialTimeout: 100, | ||
maxTimeout: 10000 | ||
randomisationFactor: 0, | ||
initialDelay: 100, | ||
maxDelay: 10000 | ||
}; | ||
``` | ||
With these values, the timeout delay will exponentially increase from 100ms to | ||
1000ms. | ||
With these values, the backoff delay will increase from 100ms to 10000ms. The | ||
randomisation factor controls the range of randomness and must be between 0 | ||
and 1. By default, no randomisation is applied on the backoff delay. | ||
### backoff.backoff() | ||
Start a backoff operation, doubling the previous timeout. | ||
Start a backoff operation. Will throw an error if a backoff operation is already | ||
in progress. | ||
Returns true on success and false if a backoff was already in progress. | ||
In practice, this method should be called after a failed attempt to perform a | ||
sensitive operation (connecting to a database, downloading a resource over the | ||
network, etc.). | ||
### backoff.reset() | ||
Reset the backoff object state. If a backoff operation is in progress when | ||
called, it will be stop. After reset, a backoff instance can be reused. | ||
Reset the backoff delay to the initial backoff delay and stop any backoff | ||
operation in progress. After reset, a backoff instance can and should be | ||
reused. | ||
In practice, this method should be called after having successfully completed | ||
the sensitive operation guarded by the backoff instance or if the client code | ||
request to stop any reconnection attempt. | ||
### Event: 'backoff' | ||
@@ -92,10 +108,7 @@ | ||
Emitted on backoff completion. | ||
Emitted on backoff completion, effectively signaling that the failing operation | ||
should be retried. | ||
### Event: 'reset' | ||
Emitted when a backoff instance is reset. | ||
## License | ||
This code is free to use under the terms of the [MIT license](http://mturcotte.mit-license.org/). |
@@ -21,5 +21,5 @@ /* | ||
"'backoff' event should be emitted on backoff completion": function(test) { | ||
"backoff event should be emitted on backoff completion": function(test) { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10 | ||
initialDelay: 10 | ||
}); | ||
@@ -36,17 +36,6 @@ var spy = new sinon.spy(); | ||
"'reset' event should be emitted on reset": function(test) { | ||
var backoff = new Backoff(); | ||
var reset = sinon.spy(); | ||
backoff.on('reset', reset); | ||
backoff.reset(); | ||
test.ok(reset.calledOnce, 'reset event has not been emitted'); | ||
test.done(); | ||
}, | ||
"the backoff delay should increase exponentially from initialTimeout to maxTimeout": function(test) { | ||
"the backoff delay should follow a Fibonacci sequence": function(test) { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
initialDelay: 10, | ||
maxDelay: 1000 | ||
}); | ||
@@ -56,4 +45,5 @@ var spy = new sinon.spy(); | ||
// Fibonnaci sequence: x[i] = x[i-1] + x[i-2]. | ||
var delays = [10, 10, 20, 30, 50, 80, 130, 210, 340, 550, 890, 1000]; | ||
var clock = this.clock; | ||
var delays = [10, 20, 40, 80, 160, 320, 640, 1000, 1000]; | ||
@@ -73,6 +63,6 @@ delays.forEach(function(delay, i) { | ||
"the initial timeout should be greater than 0": function(test) { | ||
"the initial backoff delay should be greater than 0": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: -1 | ||
initialDelay: -1 | ||
}); | ||
@@ -83,3 +73,3 @@ }); | ||
var backoff = new Backoff({ | ||
initialTimeout: 0 | ||
initialDelay: 0 | ||
}); | ||
@@ -90,3 +80,3 @@ }); | ||
var backoff = new Backoff({ | ||
initialTimeout: 1 | ||
initialDelay: 1 | ||
}); | ||
@@ -98,6 +88,6 @@ }); | ||
"the maximal timeout should be greater than 0": function(test) { | ||
"the maximal backoff delay should be greater than 0": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
maxTimeout: -1 | ||
maxDelay: -1 | ||
}); | ||
@@ -108,3 +98,3 @@ }); | ||
var backoff = new Backoff({ | ||
maxTimeout: 0 | ||
maxDelay: 0 | ||
}); | ||
@@ -116,7 +106,7 @@ }); | ||
"the maximal timeout should be greater than the original timeout": function(test) { | ||
"the maximal backoff delay should be greater than the initial backoff delay": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 10 | ||
initialDelay: 10, | ||
maxDelay: 10 | ||
}); | ||
@@ -127,4 +117,4 @@ }); | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 11 | ||
initialDelay: 10, | ||
maxDelay: 11 | ||
}); | ||
@@ -136,2 +126,24 @@ }); | ||
"the randomisation factor should be between 0 and 1": function(test) { | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
randomisationFactor: -0.1 | ||
}); | ||
}); | ||
test.throws(function() { | ||
var backoff = new Backoff({ | ||
randomisationFactor: 1.1 | ||
}); | ||
}); | ||
test.doesNotThrow(function() { | ||
var backoff = new Backoff({ | ||
randomisationFactor: 0.5 | ||
}); | ||
}); | ||
test.done(); | ||
}, | ||
"call to backoff while a backoff is in progress should throw an error": function(test) { | ||
@@ -148,5 +160,5 @@ var backoff = new Backoff(); | ||
"calling reset when a backoff is in progress should disarm the timeout": function(test) { | ||
"calling reset when a backoff is in progress should cancel its execution": function(test) { | ||
var backoff = new Backoff({ | ||
initialTimeout: 10 | ||
initialDelay: 10 | ||
}); | ||
@@ -162,3 +174,3 @@ | ||
test.equals(spy.callCount, 0, "backoff timeout did trigger"); | ||
test.equals(spy.callCount, 0, "backoff did trigger"); | ||
test.done(); | ||
@@ -169,4 +181,4 @@ }, | ||
var backoff = new Backoff({ | ||
initialTimeout: 10, | ||
maxTimeout: 1000 | ||
initialDelay: 10, | ||
maxDelay: 1000 | ||
}); | ||
@@ -185,3 +197,3 @@ var spy = new sinon.spy(); | ||
// Skip the initial timeout value. | ||
// Skip the initial backoff delay. | ||
this.clock.tick(10); | ||
@@ -188,0 +200,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17569
11
260
110
1