[Pkg-javascript-commits] [node-retry] 01/09: Imported Upstream version 0.6.0

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 191b39975c7cdc786c377da504660ee374b72699
Author: Jérémy Lal <kapouer at melix.org>
Date:   Wed Mar 27 22:20:33 2013 +0100

    Imported Upstream version 0.6.0
---
 .gitignore                               |   1 +
 License                                  |  21 ++++
 Makefile                                 |   7 ++
 Readme.md                                | 167 +++++++++++++++++++++++++++++++
 equation.gif                             | Bin 0 -> 1209 bytes
 example/dns.js                           |  31 ++++++
 index.js                                 |   1 +
 lib/retry.js                             |  50 +++++++++
 lib/retry_operation.js                   | 109 ++++++++++++++++++++
 package.json                             |  23 +++++
 test/common.js                           |  10 ++
 test/integration/test-retry-operation.js |  80 +++++++++++++++
 test/integration/test-timeouts.js        |  69 +++++++++++++
 test/runner.js                           |   5 +
 14 files changed, 574 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5a23aa6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/node_modules/*
diff --git a/License b/License
new file mode 100644
index 0000000..0b58de3
--- /dev/null
+++ b/License
@@ -0,0 +1,21 @@
+Copyright (c) 2011:
+Tim Koschützki (tim at debuggable.com)
+Felix Geisendörfer (felix at debuggable.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a6e68c4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+SHELL := /bin/bash
+
+test:
+	@node test/runner.js
+
+.PHONY: test
+
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..2bb8650
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,167 @@
+# retry
+
+Abstraction for exponential and custom retry strategies for failed operations.
+
+## Installation
+
+    npm install retry
+
+## Current Status
+
+This module has been tested and is ready to be used.
+
+## Tutorial
+
+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`.
+
+``` javascript
+var dns = require('dns');
+var retry = require('retry');
+
+function faultTolerantResolve(address, cb) {
+  var operation = retry.operation();
+
+  operation.attempt(function(currentAttempt) {
+    dns.resolve(address, function(err, addresses) {
+      if (operation.retry(err)) {
+        return;
+      }
+
+      cb(operation.mainError(), addresses);
+    });
+  });
+}
+
+faultTolerantResolve('nodejs.org', function(err, addresses) {
+  console.log(err, addresses);
+});
+```
+
+Of course you can also configure the factors that go into the exponential
+backoff. See the API documentation below for all available settings.
+currentAttempt is an int representing the number of attempts so far.
+
+``` javascript
+var operation = retry.operation({
+  retries: 5,
+  factor: 3,
+  minTimeout: 1 * 1000,
+  maxTimeout: 60 * 1000,
+  randomize: true,
+});
+```
+
+## API
+
+### retry.operation([options])
+
+Creates a new `RetryOperation` object. See the `retry.timeouts()` function
+below for available `options`.
+
+### retry.timeouts([options])
+
+Returns an array of timeouts. All time `options` and return values are in
+milliseconds. If `options` is an array, a copy of that array is returned.
+
+`options` is a JS object that can contain any of the following keys:
+
+* `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`.
+* `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);
+```
+
+Have a look at [this article][article] for a better explanation of approach.
+
+If you want to tune your `factor` / `times` settings to attempt the last retry
+after a certain amount of time, you can use wolfram alpha. For example in order
+to tune for `10` attempts in `5 minutes`, you can use this equation:
+
+![screenshot](https://github.com/tim-kos/node-retry/raw/master/equation.gif)
+
+Explaining the various values from left to right:
+
+* `k = 0 ... 9`:  The `retries` value (10)
+* `1000`: The `minTimeout` value in ms (1000)
+* `x^k`: No need to change this, `x` will be your resulting factor
+* `5 * 60 * 1000`: The desired total amount of time for retrying in ms (5 minutes)
+
+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]()
+
+[article]: http://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html
+
+### new RetryOperation(timeouts)
+
+Creates a new `RetryOperation` where `timeouts` is an array where each value is
+a timeout given in milliseconds.
+
+#### retryOperation.errors()
+
+Returns an array of all errors that have been passed to
+`retryOperation.retry()` so far.
+
+#### retryOperation.mainError()
+
+A reference to the error object that occured most frequently. Errors are
+compared using the `error.message` property.
+
+If multiple error messages occured the same amount of time, the last error
+object with that message is returned.
+
+If no errors occured so far, the value is `null`.
+
+#### retryOperation.attempt(fn, timeoutOps)
+
+Defines the function `fn` that is to be retried and executes it for the first
+time right away. The `fn` function can receive an optional `currentAttempt` callback that represents the number of attempts to execute `fn` so far.
+
+Optionally defines `timeoutOps` 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.
+
+
+#### retryOperation.try(fn)
+
+This is an alias for `retryOperation.attempt(fn)`. This is deprecated.
+
+#### retryOperation.start(fn)
+
+This is an alias for `retryOperation.attempt(fn)`. This is deprecated.
+
+#### retryOperation.retry(error)
+
+Returns `false` when no `error` value is given, or the maximum amount of retries
+has been reached.
+
+Otherwise it returns `true`, and retries the operation after the timeout for
+the current attempt number.
+
+#### retryOperation.attempts()
+
+Returns an int representing the number of attempts it took to call `fn` before it was successful.
+
+## License
+
+retry is licensed under the MIT license.
+
+
+#Changelog
+
+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.5.0 Some minor refactorings.
+
+0.4.0 Changed retryOperation.try() to retryOperation.attempt(). Deprecated the aliases start() and try() for it.
+
+0.3.0 Added retryOperation.start() which is an alias for retryOperation.try().
+
+0.2.0 Added attempts() function and parameter to retryOperation.try() representing the number of attempts it took to call fn().
diff --git a/equation.gif b/equation.gif
new file mode 100644
index 0000000..9710723
Binary files /dev/null and b/equation.gif differ
diff --git a/example/dns.js b/example/dns.js
new file mode 100644
index 0000000..e4082af
--- /dev/null
+++ b/example/dns.js
@@ -0,0 +1,31 @@
+var dns = require('dns');
+var retry = require('../lib/retry');
+
+function faultTolerantResolve(address, cb) {
+  var opts = {
+    times: 2,
+    factor: 2,
+    minTimeout: 1 * 1000,
+    maxTimeout: 2 * 1000,
+    randomize: true
+  };
+  var operation = retry.operation(opts);
+
+  operation.attempt(function(currentAttempt) {
+    dns.resolve(address, function(err, addresses) {
+      if (operation.retry(err)) {
+        return;
+      }
+
+      cb(operation.mainError(), operation.errors(), addresses);
+    });
+  });
+}
+
+faultTolerantResolve('nodejs.org', function(err, errors, addresses) {
+  console.warn('err:');
+  console.log(err);
+
+  console.warn('addresses:');
+  console.log(addresses);
+});
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..ee62f3a
--- /dev/null
+++ b/index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/retry');
\ No newline at end of file
diff --git a/lib/retry.js b/lib/retry.js
new file mode 100644
index 0000000..3840686
--- /dev/null
+++ b/lib/retry.js
@@ -0,0 +1,50 @@
+var RetryOperation = require('./retry_operation');
+
+exports.operation = function(options) {
+  var timeouts = exports.timeouts(options);
+  return new RetryOperation(timeouts);
+};
+
+exports.timeouts = function(options) {
+  if (options instanceof Array) {
+    return [].concat(options);
+  }
+
+  var opts = {
+    retries: 10,
+    factor: 2,
+    minTimeout: 1 * 1000,
+    maxTimeout: Infinity,
+    randomize: false
+  };
+  for (var key in options) {
+    opts[key] = options[key];
+  }
+
+  if (opts.minTimeout > opts.maxTimeout) {
+    throw new Error('minTimeout is greater than maxTimeout');
+  }
+
+  var timeouts = [];
+  for (var i = 0; i < opts.retries; i++) {
+    timeouts.push(this._createTimeout(i, opts));
+  }
+
+  // sort the array numerically ascending
+  timeouts.sort(function(a,b) {
+    return a - b;
+  });
+
+  return timeouts;
+};
+
+exports._createTimeout = function(attempt, opts) {
+  var random = (opts.randomize)
+    ? (Math.random() + 1)
+    : 1;
+
+  var timeout = Math.round(random * opts.minTimeout * Math.pow(opts.factor, attempt));
+  timeout = Math.min(timeout, opts.maxTimeout);
+
+  return timeout;
+};
\ No newline at end of file
diff --git a/lib/retry_operation.js b/lib/retry_operation.js
new file mode 100644
index 0000000..f24d2d5
--- /dev/null
+++ b/lib/retry_operation.js
@@ -0,0 +1,109 @@
+function RetryOperation(timeouts) {
+  this._timeouts = timeouts;
+  this._fn = null;
+  this._errors = [];
+  this._attempts = 1;
+  this._operationTimeout = null;
+  this._operationTimeoutCb = null;
+  this._timeout = null;
+}
+module.exports = RetryOperation;
+
+RetryOperation.prototype.retry = function(err) {
+  if (this._timeout) {
+    clearTimeout(this._timeout);
+  }
+
+  if (!err) {
+    return false;
+  }
+
+  this._errors.push(err);
+
+  var timeout = this._timeouts.shift();
+  if (timeout === undefined) {
+    return false;
+  }
+
+  this._attempts++;
+
+  var self = this;
+  setTimeout(function() {
+    self._fn(self._attempts);
+
+    if (self._operationTimeoutCb) {
+      self._timeout = setTimeout(function() {
+        self._operationTimeoutCb(self._attempts);
+      }, self._operationTimeout);
+    }
+  }, timeout);
+
+  return true;
+};
+
+RetryOperation.prototype.attempt = function(fn, timeoutOps) {
+  this._fn = fn;
+
+  if (timeoutOps) {
+    if (timeoutOps.timeout) {
+      this._operationTimeout = timeoutOps.timeout;
+    }
+    if (timeoutOps.cb) {
+      this._operationTimeoutCb = timeoutOps.cb;
+    }
+  }
+
+  this._fn(this._attempts);
+
+  var self = this;
+  if (this._operationTimeoutCb) {
+    this._timeout = setTimeout(function() {
+      self._operationTimeoutCb();
+    }, self._operationTimeout);
+  }
+};
+
+RetryOperation.prototype.try = function(fn) {
+  console.log('Using RetryOperation.try() is deprecated');
+  this.attempt(fn);
+};
+
+RetryOperation.prototype.start = function(fn) {
+  console.log('Using RetryOperation.start() is deprecated');
+  this.attempt(fn);
+};
+
+RetryOperation.prototype.start = RetryOperation.prototype.try;
+
+RetryOperation.prototype.errors = function() {
+  return this._errors;
+};
+
+RetryOperation.prototype.attempts = function() {
+  return this._attempts;
+};
+
+RetryOperation.prototype.mainError = function() {
+  if (this._errors.length === 0) {
+    return null;
+  }
+
+  var counts = {};
+  var mainError = null;
+  var mainErrorCount = 0;
+
+  for (var i = 0; i < this._errors.length; i++) {
+    var error = this._errors[i];
+    var message = error.message;
+    var count = (counts[message] || 0) + 1;
+
+    counts[message] = count;
+
+    if (count >= mainErrorCount) {
+      mainError = error;
+      mainErrorCount = count;
+    }
+  }
+
+  return mainError;
+};
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b720af7
--- /dev/null
+++ b/package.json
@@ -0,0 +1,23 @@
+{
+  "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",
+  "homepage": "https://github.com/tim-kos/node-retry",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/felixge/node-retry.git"
+  },
+  "directories": {
+    "lib": "./lib"
+  },
+  "main": "index",
+  "engines": {
+    "node": "*"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "fake": "0.2.0",
+    "far": "0.0.1"
+  }
+}
\ No newline at end of file
diff --git a/test/common.js b/test/common.js
new file mode 100644
index 0000000..2247206
--- /dev/null
+++ b/test/common.js
@@ -0,0 +1,10 @@
+var common = module.exports;
+var path = require('path');
+
+var rootDir = path.join(__dirname, '..');
+common.dir = {
+  lib: rootDir + '/lib'
+};
+
+common.assert = require('assert');
+common.fake = require('fake');
\ No newline at end of file
diff --git a/test/integration/test-retry-operation.js b/test/integration/test-retry-operation.js
new file mode 100644
index 0000000..d873d1f
--- /dev/null
+++ b/test/integration/test-retry-operation.js
@@ -0,0 +1,80 @@
+var common = require('../common');
+var assert = common.assert;
+var fake = common.fake.create();
+var retry = require(common.dir.lib + '/retry');
+
+(function testErrors() {
+  var operation = retry.operation();
+
+  var error = new Error('some error');
+  var error2 = new Error('some other error');
+  operation._errors.push(error);
+  operation._errors.push(error2);
+
+  assert.deepEqual(operation.errors(), [error, error2]);
+})();
+
+(function testMainErrorReturnsMostFrequentError() {
+  var operation = retry.operation();
+  var error = new Error('some error');
+  var error2 = new Error('some other error');
+
+  operation._errors.push(error);
+  operation._errors.push(error2);
+  operation._errors.push(error);
+
+  assert.strictEqual(operation.mainError(), error);
+})();
+
+(function testMainErrorReturnsLastErrorOnEqualCount() {
+  var operation = retry.operation();
+  var error = new Error('some error');
+  var error2 = new Error('some other error');
+
+  operation._errors.push(error);
+  operation._errors.push(error2);
+
+  assert.strictEqual(operation.mainError(), error2);
+})();
+
+(function testAttempt() {
+  var operation = retry.operation();
+  var fn = new Function();
+
+  var timeoutOpts = {
+    timeout: 1,
+    cb: function() {}
+  };
+  operation.attempt(fn, timeoutOpts);
+
+  assert.strictEqual(fn, operation._fn);
+  assert.strictEqual(timeoutOpts.timeout, operation._operationTimeout);
+  assert.strictEqual(timeoutOpts.cb, operation._operationTimeoutCb);
+})();
+
+(function testRetry() {
+  var times = 3;
+  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 (operation.retry(error)) {
+        return;
+      }
+
+      assert.strictEqual(attempts, 4);
+      assert.strictEqual(operation.attempts(), attempts);
+      assert.strictEqual(operation.mainError(), error);
+      finalCallback();
+    });
+  };
+
+  fn();
+})();
\ No newline at end of file
diff --git a/test/integration/test-timeouts.js b/test/integration/test-timeouts.js
new file mode 100644
index 0000000..7206b0f
--- /dev/null
+++ b/test/integration/test-timeouts.js
@@ -0,0 +1,69 @@
+var common = require('../common');
+var assert = common.assert;
+var retry = require(common.dir.lib + '/retry');
+
+(function testDefaultValues() {
+  var timeouts = retry.timeouts();
+
+  assert.equal(timeouts.length, 10);
+  assert.equal(timeouts[0], 1000);
+  assert.equal(timeouts[1], 2000);
+  assert.equal(timeouts[2], 4000);
+})();
+
+(function testDefaultValuesWithRandomize() {
+  var minTimeout = 5000;
+  var timeouts = retry.timeouts({
+    minTimeout: minTimeout,
+    randomize: true
+  });
+
+  assert.equal(timeouts.length, 10);
+  assert.ok(timeouts[0] > minTimeout);
+  assert.ok(timeouts[1] > timeouts[0]);
+  assert.ok(timeouts[2] > timeouts[1]);
+})();
+
+(function testPassedTimeoutsAreUsed() {
+  var timeoutsArray = [1000, 2000, 3000];
+  var timeouts = retry.timeouts(timeoutsArray);
+  assert.deepEqual(timeouts, timeoutsArray);
+  assert.notStrictEqual(timeouts, timeoutsArray);
+})();
+
+(function testTimeoutsAreWithinBoundaries() {
+  var minTimeout = 1000;
+  var maxTimeout = 10000;
+  var timeouts = retry.timeouts({
+    minTimeout: minTimeout,
+    maxTimeout: maxTimeout
+  });
+  for (var i = 0; i < timeouts; i++) {
+    assert.ok(timeouts[i] >= minTimeout);
+    assert.ok(timeouts[i] <= maxTimeout);
+  }
+})();
+
+(function testTimeoutsAreIncremental() {
+  var timeouts = retry.timeouts();
+  var lastTimeout = timeouts[0];
+  for (var i = 0; i < timeouts; i++) {
+    assert.ok(timeouts[i] > lastTimeout);
+    lastTimeout = timeouts[i];
+  }
+})();
+
+(function testTimeoutsAreIncrementalForFactorsLessThanOne() {
+  var timeouts = retry.timeouts({
+    retries: 3,
+    factor: 0.5
+  });
+
+  var expected = [250, 500, 1000];
+  assert.deepEqual(expected, timeouts);
+})();
+
+(function testRetries() {
+  var timeouts = retry.timeouts({retries: 2});
+  assert.strictEqual(timeouts.length, 2);
+})();
diff --git a/test/runner.js b/test/runner.js
new file mode 100644
index 0000000..e0ee2f5
--- /dev/null
+++ b/test/runner.js
@@ -0,0 +1,5 @@
+var far = require('far').create();
+
+far.add(__dirname);
+far.include(/\/test-.*\.js$/);
+far.execute();

-- 
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