[Pkg-javascript-commits] [node-retry] 04/09: New upstream version 0.10.1
Paolo Greppi
paolog-guest at moszumanska.debian.org
Wed Dec 14 18:53:29 UTC 2016
This is an automated email from the git hooks/post-receive script.
paolog-guest pushed a commit to branch master
in repository node-retry.
commit d578bc1b2b0e5b5af49aca5c3d2f3de0cff2c561
Author: Paolo Greppi <paolo.greppi at libpf.com>
Date: Wed Dec 14 18:24:48 2016 +0000
New upstream version 0.10.1
---
.gitignore | 1 +
Makefile | 15 +++++
Readme.md => README.md | 76 ++++++++++++++++++++-----
example/dns.js | 2 +-
example/stop.js | 40 +++++++++++++
lib/retry.js | 57 +++++++++++++++++--
lib/retry_operation.js | 52 ++++++++++++++---
package.json | 7 ++-
test/integration/test-forever.js | 24 ++++++++
test/integration/test-retry-operation.js | 98 +++++++++++++++++++++++++++++++-
test/integration/test-retry-wrap.js | 77 +++++++++++++++++++++++++
11 files changed, 417 insertions(+), 32 deletions(-)
diff --git a/.gitignore b/.gitignore
index 5a23aa6..e7726a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/node_modules/*
+npm-debug.log
diff --git a/Makefile b/Makefile
index a6e68c4..eee21a9 100644
--- a/Makefile
+++ b/Makefile
@@ -3,5 +3,20 @@ SHELL := /bin/bash
test:
@node test/runner.js
+release-major: test
+ npm version major -m "Release %s"
+ git push
+ npm publish
+
+release-minor: test
+ npm version minor -m "Release %s"
+ git push
+ npm publish
+
+release-patch: test
+ npm version patch -m "Release %s"
+ git push
+ npm publish
+
.PHONY: test
diff --git a/Readme.md b/README.md
similarity index 58%
rename from Readme.md
rename to README.md
index 2bb8650..eee05f7 100644
--- a/Readme.md
+++ b/README.md
@@ -14,7 +14,7 @@ This module has been tested and is ready to be used.
The example below will retry a potentially failing `dns.resolve` operation
`10` times using an exponential backoff strategy. With the default settings, this
-means the last attempt is made after `34 minutes and 7 seconds`.
+means the last attempt is made after `17 minutes and 3 seconds`.
``` javascript
var dns = require('dns');
@@ -29,7 +29,7 @@ function faultTolerantResolve(address, cb) {
return;
}
- cb(operation.mainError(), addresses);
+ cb(err ? operation.mainError() : null, addresses);
});
});
}
@@ -57,8 +57,10 @@ var operation = retry.operation({
### retry.operation([options])
-Creates a new `RetryOperation` object. See the `retry.timeouts()` function
-below for available `options`.
+Creates a new `RetryOperation` object. `options` is the same as `retry.timeouts()`'s `options`, with two additions:
+
+* `forever`: Whether to retry forever, defaults to `false`.
+* `unref`: Wether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`.
### retry.timeouts([options])
@@ -69,14 +71,14 @@ milliseconds. If `options` is an array, a copy of that array is returned.
* `retries`: The maximum amount of times to retry the operation. Default is `10`.
* `factor`: The exponential factor to use. Default is `2`.
-* `minTimeout`: The amount of time before starting the first retry. Default is `1000`.
-* `maxTimeout`: The maximum amount of time between two retries. Default is `Infinity`.
+* `minTimeout`: The number of milliseconds before starting the first retry. Default is `1000`.
+* `maxTimeout`: The maximum number of milliseconds between two retries. Default is `Infinity`.
* `randomize`: Randomizes the timeouts by multiplying with a factor between `1` to `2`. Default is `false`.
The formula used to calculate the individual timeouts is:
```
-var Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout);
+Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout)
```
Have a look at [this article][article] for a better explanation of approach.
@@ -96,15 +98,49 @@ Explaining the various values from left to right:
To make this a little easier for you, use wolfram alpha to do the calculations:
-[http://www.wolframalpha.com/input/?i=Sum%5B1000*x^k%2C+{k%2C+0%2C+9}%5D+%3D+5+*+60+*+1000]()
+<http://www.wolframalpha.com/input/?i=Sum%5B1000*x^k%2C+{k%2C+0%2C+9}%5D+%3D+5+*+60+*+1000>
[article]: http://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html
-### new RetryOperation(timeouts)
+### retry.createTimeout(attempt, opts)
+
+Returns a new `timeout` (integer in milliseconds) based on the given parameters.
+
+`attempt` is an integer representing for which retry the timeout should be calculated. If your retry operation was executed 4 times you had one attempt and 3 retries. If you then want to calculate a new timeout, you should set `attempt` to 4 (attempts are zero-indexed).
+
+`opts` can include `factor`, `minTimeout`, `randomize` (boolean) and `maxTimeout`. They are documented above.
+
+`retry.createTimeout()` is used internally by `retry.timeouts()` and is public for you to be able to create your own timeouts for reinserting an item, see [issue #13](https://github.com/tim-kos/node-retry/issues/13).
+
+### retry.wrap(obj, [options], [methodNames])
+
+Wrap all functions of the `obj` with retry. Optionally you can pass operation options and
+an array of method names which need to be wrapped.
+
+```
+retry.wrap(obj)
+
+retry.wrap(obj, ['method1', 'method2'])
+
+retry.wrap(obj, {retries: 3})
+
+retry.wrap(obj, {retries: 3}, ['method1', 'method2'])
+```
+The `options` object can take any options that the usual call to `retry.operation` can take.
+
+### new RetryOperation(timeouts, [options])
Creates a new `RetryOperation` where `timeouts` is an array where each value is
a timeout given in milliseconds.
+Available options:
+* `forever`: Whether to retry forever, defaults to `false`.
+* `unref`: Wether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`.
+
+If `forever` is true, the following changes happen:
+* `RetryOperation.errors()` will only output an array of one item: the last error.
+* `RetryOperation` will repeatedly use the `timeouts` array. Once all of its timeouts have been used up, it restarts with the first timeout, then uses the second and so on.
+
#### retryOperation.errors()
Returns an array of all errors that have been passed to
@@ -131,11 +167,11 @@ Whenever your retry operation takes longer than `timeout` to execute, the timeou
#### retryOperation.try(fn)
-This is an alias for `retryOperation.attempt(fn)`. This is deprecated.
+This is an alias for `retryOperation.attempt(fn)`. This is deprecated. Please use `retryOperation.attempt(fn)` instead.
#### retryOperation.start(fn)
-This is an alias for `retryOperation.attempt(fn)`. This is deprecated.
+This is an alias for `retryOperation.attempt(fn)`. This is deprecated. Please use `retryOperation.attempt(fn)` instead.
#### retryOperation.retry(error)
@@ -145,6 +181,10 @@ has been reached.
Otherwise it returns `true`, and retries the operation after the timeout for
the current attempt number.
+#### retryOperation.stop()
+
+Allows you to stop the operation being retried. Useful for aborting the operation on a fatal error etc.
+
#### retryOperation.attempts()
Returns an int representing the number of attempts it took to call `fn` before it was successful.
@@ -154,11 +194,19 @@ Returns an int representing the number of attempts it took to call `fn` before i
retry is licensed under the MIT license.
-#Changelog
+# Changelog
+
+0.10.0 Adding `stop` functionality, thanks to @maxnachlinger.
+
+0.9.0 Adding `unref` functionality, thanks to @satazor.
+
+0.8.0 Implementing retry.wrap.
+
+0.7.0 Some bug fixes and made retry.createTimeout() public. Fixed issues [#10](https://github.com/tim-kos/node-retry/issues/10), [#12](https://github.com/tim-kos/node-retry/issues/12), and [#13](https://github.com/tim-kos/node-retry/issues/13).
-0.6.0 Introduced optional timeOps parameter for the attempt() function which is an object having a property timeout in miliseconds and a property cb callback function. Whenever your retry operation takes longer than timeout to execute, the timeout callback function cb is called.
+0.6.0 Introduced optional timeOps parameter for the attempt() function which is an object having a property timeout in milliseconds and a property cb callback function. Whenever your retry operation takes longer than timeout to execute, the timeout callback function cb is called.
-0.5.0 Some minor refactorings.
+0.5.0 Some minor refactoring.
0.4.0 Changed retryOperation.try() to retryOperation.attempt(). Deprecated the aliases start() and try() for it.
diff --git a/example/dns.js b/example/dns.js
index e4082af..446729b 100644
--- a/example/dns.js
+++ b/example/dns.js
@@ -3,7 +3,7 @@ var retry = require('../lib/retry');
function faultTolerantResolve(address, cb) {
var opts = {
- times: 2,
+ retries: 2,
factor: 2,
minTimeout: 1 * 1000,
maxTimeout: 2 * 1000,
diff --git a/example/stop.js b/example/stop.js
new file mode 100644
index 0000000..e1ceafe
--- /dev/null
+++ b/example/stop.js
@@ -0,0 +1,40 @@
+var retry = require('../lib/retry');
+
+function attemptAsyncOperation(someInput, cb) {
+ var opts = {
+ retries: 2,
+ factor: 2,
+ minTimeout: 1 * 1000,
+ maxTimeout: 2 * 1000,
+ randomize: true
+ };
+ var operation = retry.operation(opts);
+
+ operation.attempt(function(currentAttempt) {
+ failingAsyncOperation(someInput, function(err, result) {
+
+ if (err && err.message === 'A fatal error') {
+ operation.stop();
+ return cb(err);
+ }
+
+ if (operation.retry(err)) {
+ return;
+ }
+
+ cb(operation.mainError(), operation.errors(), result);
+ });
+ });
+}
+
+attemptAsyncOperation('test input', function(err, errors, result) {
+ console.warn('err:');
+ console.log(err);
+
+ console.warn('result:');
+ console.log(result);
+});
+
+function failingAsyncOperation(input, cb) {
+ return setImmediate(cb.bind(null, new Error('A fatal error')));
+}
diff --git a/lib/retry.js b/lib/retry.js
index 3840686..77428cf 100644
--- a/lib/retry.js
+++ b/lib/retry.js
@@ -2,7 +2,10 @@ var RetryOperation = require('./retry_operation');
exports.operation = function(options) {
var timeouts = exports.timeouts(options);
- return new RetryOperation(timeouts);
+ return new RetryOperation(timeouts, {
+ forever: options && options.forever,
+ unref: options && options.unref
+ });
};
exports.timeouts = function(options) {
@@ -27,7 +30,11 @@ exports.timeouts = function(options) {
var timeouts = [];
for (var i = 0; i < opts.retries; i++) {
- timeouts.push(this._createTimeout(i, opts));
+ timeouts.push(this.createTimeout(i, opts));
+ }
+
+ if (options && options.forever && !timeouts.length) {
+ timeouts.push(this.createTimeout(i, opts));
}
// sort the array numerically ascending
@@ -38,7 +45,7 @@ exports.timeouts = function(options) {
return timeouts;
};
-exports._createTimeout = function(attempt, opts) {
+exports.createTimeout = function(attempt, opts) {
var random = (opts.randomize)
? (Math.random() + 1)
: 1;
@@ -47,4 +54,46 @@ exports._createTimeout = function(attempt, opts) {
timeout = Math.min(timeout, opts.maxTimeout);
return timeout;
-};
\ No newline at end of file
+};
+
+exports.wrap = function(obj, options, methods) {
+ if (options instanceof Array) {
+ methods = options;
+ options = null;
+ }
+
+ if (!methods) {
+ methods = [];
+ for (var key in obj) {
+ if (typeof obj[key] === 'function') {
+ methods.push(key);
+ }
+ }
+ }
+
+ for (var i = 0; i < methods.length; i++) {
+ var method = methods[i];
+ var original = obj[method];
+
+ obj[method] = function retryWrapper() {
+ var op = exports.operation(options);
+ var args = Array.prototype.slice.call(arguments);
+ var callback = args.pop();
+
+ args.push(function(err) {
+ if (op.retry(err)) {
+ return;
+ }
+ if (err) {
+ arguments[0] = op.mainError();
+ }
+ callback.apply(this, arguments);
+ });
+
+ op.attempt(function() {
+ original.apply(obj, args);
+ });
+ };
+ obj[method].options = options;
+ }
+};
diff --git a/lib/retry_operation.js b/lib/retry_operation.js
index f24d2d5..2b3db8e 100644
--- a/lib/retry_operation.js
+++ b/lib/retry_operation.js
@@ -1,14 +1,33 @@
-function RetryOperation(timeouts) {
+function RetryOperation(timeouts, options) {
+ // Compatibility for the old (timeouts, retryForever) signature
+ if (typeof options === 'boolean') {
+ options = { forever: options };
+ }
+
this._timeouts = timeouts;
+ this._options = options || {};
this._fn = null;
this._errors = [];
this._attempts = 1;
this._operationTimeout = null;
this._operationTimeoutCb = null;
this._timeout = null;
+
+ if (this._options.forever) {
+ this._cachedTimeouts = this._timeouts.slice(0);
+ }
}
module.exports = RetryOperation;
+RetryOperation.prototype.stop = function() {
+ if (this._timeout) {
+ clearTimeout(this._timeout);
+ }
+
+ this._timeouts = [];
+ this._cachedTimeouts = null;
+};
+
RetryOperation.prototype.retry = function(err) {
if (this._timeout) {
clearTimeout(this._timeout);
@@ -22,22 +41,37 @@ RetryOperation.prototype.retry = function(err) {
var timeout = this._timeouts.shift();
if (timeout === undefined) {
- return false;
+ if (this._cachedTimeouts) {
+ // retry forever, only keep last error
+ this._errors.splice(this._errors.length - 1, this._errors.length);
+ this._timeouts = this._cachedTimeouts.slice(0);
+ timeout = this._timeouts.shift();
+ } else {
+ return false;
+ }
}
- this._attempts++;
-
var self = this;
- setTimeout(function() {
- self._fn(self._attempts);
+ var timer = setTimeout(function() {
+ self._attempts++;
if (self._operationTimeoutCb) {
self._timeout = setTimeout(function() {
self._operationTimeoutCb(self._attempts);
}, self._operationTimeout);
+
+ if (this._options.unref) {
+ self._timeout.unref();
+ }
}
+
+ self._fn(self._attempts);
}, timeout);
+ if (this._options.unref) {
+ timer.unref();
+ }
+
return true;
};
@@ -53,14 +87,14 @@ RetryOperation.prototype.attempt = function(fn, timeoutOps) {
}
}
- this._fn(this._attempts);
-
var self = this;
if (this._operationTimeoutCb) {
this._timeout = setTimeout(function() {
self._operationTimeoutCb();
}, self._operationTimeout);
}
+
+ this._fn(this._attempts);
};
RetryOperation.prototype.try = function(fn) {
@@ -106,4 +140,4 @@ RetryOperation.prototype.mainError = function() {
}
return mainError;
-};
\ No newline at end of file
+};
diff --git a/package.json b/package.json
index b720af7..5ac60c8 100644
--- a/package.json
+++ b/package.json
@@ -2,11 +2,12 @@
"author": "Tim Koschützki <tim at debuggable.com> (http://debuggable.com/)",
"name": "retry",
"description": "Abstraction for exponential and custom retry strategies for failed operations.",
- "version": "0.6.0",
+ "license": "MIT",
+ "version": "0.10.1",
"homepage": "https://github.com/tim-kos/node-retry",
"repository": {
"type": "git",
- "url": "git://github.com/felixge/node-retry.git"
+ "url": "git://github.com/tim-kos/node-retry.git"
},
"directories": {
"lib": "./lib"
@@ -20,4 +21,4 @@
"fake": "0.2.0",
"far": "0.0.1"
}
-}
\ No newline at end of file
+}
diff --git a/test/integration/test-forever.js b/test/integration/test-forever.js
new file mode 100644
index 0000000..b41307c
--- /dev/null
+++ b/test/integration/test-forever.js
@@ -0,0 +1,24 @@
+var common = require('../common');
+var assert = common.assert;
+var retry = require(common.dir.lib + '/retry');
+
+(function testForeverUsesFirstTimeout() {
+ var operation = retry.operation({
+ retries: 0,
+ minTimeout: 100,
+ maxTimeout: 100,
+ forever: true
+ });
+
+ operation.attempt(function(numAttempt) {
+ console.log('>numAttempt', numAttempt);
+ var err = new Error("foo");
+ if (numAttempt == 10) {
+ operation.stop();
+ }
+
+ if (operation.retry(err)) {
+ return;
+ }
+ });
+})();
diff --git a/test/integration/test-retry-operation.js b/test/integration/test-retry-operation.js
index d873d1f..9169364 100644
--- a/test/integration/test-retry-operation.js
+++ b/test/integration/test-retry-operation.js
@@ -77,4 +77,100 @@ var retry = require(common.dir.lib + '/retry');
};
fn();
-})();
\ No newline at end of file
+})();
+
+(function testRetryForever() {
+ var error = new Error('some error');
+ var operation = retry.operation({ retries: 3, forever: true });
+ var attempts = 0;
+
+ var finalCallback = fake.callback('finalCallback');
+ fake.expectAnytime(finalCallback);
+
+ var fn = function() {
+ operation.attempt(function(currentAttempt) {
+ attempts++;
+ assert.equal(currentAttempt, attempts);
+ if (attempts !== 6 && operation.retry(error)) {
+ return;
+ }
+
+ assert.strictEqual(attempts, 6);
+ assert.strictEqual(operation.attempts(), attempts);
+ assert.strictEqual(operation.mainError(), error);
+ finalCallback();
+ });
+ };
+
+ fn();
+})();
+
+(function testRetryForeverNoRetries() {
+ var error = new Error('some error');
+ var delay = 50
+ var operation = retry.operation({
+ retries: null,
+ forever: true,
+ minTimeout: delay,
+ maxTimeout: delay
+ });
+
+ var attempts = 0;
+ var startTime = new Date().getTime();
+
+ var finalCallback = fake.callback('finalCallback');
+ fake.expectAnytime(finalCallback);
+
+ var fn = function() {
+ operation.attempt(function(currentAttempt) {
+ attempts++;
+ assert.equal(currentAttempt, attempts);
+ if (attempts !== 4 && operation.retry(error)) {
+ return;
+ }
+
+ var endTime = new Date().getTime();
+ var minTime = startTime + (delay * 3);
+ var maxTime = minTime + 20 // add a little headroom for code execution time
+ assert(endTime > minTime)
+ assert(endTime < maxTime)
+ assert.strictEqual(attempts, 4);
+ assert.strictEqual(operation.attempts(), attempts);
+ assert.strictEqual(operation.mainError(), error);
+ finalCallback();
+ });
+ };
+
+ fn();
+})();
+
+(function testStop() {
+ var error = new Error('some error');
+ var operation = retry.operation([1, 2, 3]);
+ var attempts = 0;
+
+ var finalCallback = fake.callback('finalCallback');
+ fake.expectAnytime(finalCallback);
+
+ var fn = function() {
+ operation.attempt(function(currentAttempt) {
+ attempts++;
+ assert.equal(currentAttempt, attempts);
+
+ if (attempts === 2) {
+ operation.stop();
+
+ assert.strictEqual(attempts, 2);
+ assert.strictEqual(operation.attempts(), attempts);
+ assert.strictEqual(operation.mainError(), error);
+ finalCallback();
+ }
+
+ if (operation.retry(error)) {
+ return;
+ }
+ });
+ };
+
+ fn();
+})();
diff --git a/test/integration/test-retry-wrap.js b/test/integration/test-retry-wrap.js
new file mode 100644
index 0000000..7ca8bc7
--- /dev/null
+++ b/test/integration/test-retry-wrap.js
@@ -0,0 +1,77 @@
+var common = require('../common');
+var assert = common.assert;
+var fake = common.fake.create();
+var retry = require(common.dir.lib + '/retry');
+
+function getLib() {
+ return {
+ fn1: function() {},
+ fn2: function() {},
+ fn3: function() {}
+ };
+}
+
+(function wrapAll() {
+ var lib = getLib();
+ retry.wrap(lib);
+ assert.equal(lib.fn1.name, 'retryWrapper');
+ assert.equal(lib.fn2.name, 'retryWrapper');
+ assert.equal(lib.fn3.name, 'retryWrapper');
+}());
+
+(function wrapAllPassOptions() {
+ var lib = getLib();
+ retry.wrap(lib, {retries: 2});
+ assert.equal(lib.fn1.name, 'retryWrapper');
+ assert.equal(lib.fn2.name, 'retryWrapper');
+ assert.equal(lib.fn3.name, 'retryWrapper');
+ assert.equal(lib.fn1.options.retries, 2);
+ assert.equal(lib.fn2.options.retries, 2);
+ assert.equal(lib.fn3.options.retries, 2);
+}());
+
+(function wrapDefined() {
+ var lib = getLib();
+ retry.wrap(lib, ['fn2', 'fn3']);
+ assert.notEqual(lib.fn1.name, 'retryWrapper');
+ assert.equal(lib.fn2.name, 'retryWrapper');
+ assert.equal(lib.fn3.name, 'retryWrapper');
+}());
+
+(function wrapDefinedAndPassOptions() {
+ var lib = getLib();
+ retry.wrap(lib, {retries: 2}, ['fn2', 'fn3']);
+ assert.notEqual(lib.fn1.name, 'retryWrapper');
+ assert.equal(lib.fn2.name, 'retryWrapper');
+ assert.equal(lib.fn3.name, 'retryWrapper');
+ assert.equal(lib.fn2.options.retries, 2);
+ assert.equal(lib.fn3.options.retries, 2);
+}());
+
+(function runWrappedWithoutError() {
+ var callbackCalled;
+ var lib = {method: function(a, b, callback) {
+ assert.equal(a, 1);
+ assert.equal(b, 2);
+ assert.equal(typeof callback, 'function');
+ callback();
+ }};
+ retry.wrap(lib);
+ lib.method(1, 2, function() {
+ callbackCalled = true;
+ });
+ assert.ok(callbackCalled);
+}());
+
+(function runWrappedWithError() {
+ var callbackCalled;
+ var lib = {method: function(callback) {
+ callback(new Error('Some error'));
+ }};
+ retry.wrap(lib, {retries: 1});
+ lib.method(function(err) {
+ callbackCalled = true;
+ assert.ok(err instanceof Error);
+ });
+ assert.ok(!callbackCalled);
+}());
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-retry.git
More information about the Pkg-javascript-commits
mailing list