[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