2015-05-19 13:13:49 -04:00
|
|
|
/**
|
|
|
|
* DataApi auto-retry requests on specified failure
|
|
|
|
*
|
|
|
|
* Copyright (C) 2015 LoVullo Associates, Inc.
|
|
|
|
*
|
|
|
|
* This file is part of the Liza Data Collection Framework
|
|
|
|
*
|
|
|
|
* Liza is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
var Trait = require( 'easejs' ).Trait,
|
|
|
|
DataApi = require( './DataApi' );
|
|
|
|
|
|
|
|
|
2015-05-27 15:26:03 -04:00
|
|
|
/**
|
|
|
|
* Automatically retries requests while satisfying a given predicate
|
|
|
|
*
|
|
|
|
* It is important to distinguish between the concept of a request failure
|
|
|
|
* and a retry predicate: the former represents a problem with the request,
|
|
|
|
* whereas the latter indicates that a retry should be performed, but may
|
|
|
|
* not necessarily imply a request failure.
|
|
|
|
*/
|
2015-05-19 13:13:49 -04:00
|
|
|
module.exports = Trait( 'AutoRetry' )
|
|
|
|
.implement( DataApi )
|
|
|
|
.extend(
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Predicate function determining whether a retry is needed
|
|
|
|
* @var {function(?Error,*): boolean}
|
|
|
|
*/
|
|
|
|
'private _pred': '',
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maximum number of tries (including initial request)
|
|
|
|
* @var {number}
|
|
|
|
*/
|
|
|
|
'private _tries': 0,
|
|
|
|
|
|
|
|
/**
|
2015-05-22 16:22:58 -04:00
|
|
|
* Function to be passed a continuation to introduce a delay between
|
|
|
|
* requests
|
2015-05-19 13:13:49 -04:00
|
|
|
*
|
2015-05-22 16:22:58 -04:00
|
|
|
* @var {function(number,function(),function())} delay
|
2015-05-19 13:13:49 -04:00
|
|
|
*/
|
|
|
|
'private _delay': null,
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize auto-retry
|
|
|
|
*
|
2015-05-27 15:26:03 -04:00
|
|
|
* If TRIES is negative, then requests will continue indefinitely while
|
|
|
|
* the retry predicate is true, or is aborted by DELAY. If TRIES is 0,
|
|
|
|
* then no requests will be performed.
|
2015-05-22 16:22:58 -04:00
|
|
|
*
|
|
|
|
* If DELAY is a function, then it invoked with a retry continuation
|
|
|
|
* before each retry, the number of tries remaining, and a failure
|
|
|
|
* continuation that may be used to abort the process at an arbitrary
|
|
|
|
* time.
|
|
|
|
*
|
|
|
|
* @param {function(?Error,*): boolean} pred predicate determining if
|
|
|
|
* a retry is needed
|
|
|
|
* @param {number} tries maximum number of tries,
|
|
|
|
* including the initial
|
|
|
|
* request
|
|
|
|
*
|
|
|
|
* @param {?function(number,function(),function())} delay
|
|
|
|
* an optional function
|
|
|
|
* accepting a continuation
|
|
|
|
* to continue with the next
|
|
|
|
* request
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2015-05-19 13:13:49 -04:00
|
|
|
__mixin: function( pred, tries, delay )
|
|
|
|
{
|
|
|
|
if ( typeof pred !== 'function' )
|
|
|
|
{
|
|
|
|
throw TypeError( 'Predicate must be a function' );
|
|
|
|
}
|
2015-05-22 16:22:58 -04:00
|
|
|
if ( delay && ( typeof delay !== 'function' ) )
|
2015-05-19 13:13:49 -04:00
|
|
|
{
|
|
|
|
throw TypeError( "Delay must be a function" );
|
|
|
|
}
|
|
|
|
|
|
|
|
this._pred = pred;
|
|
|
|
this._tries = +tries;
|
2015-05-27 15:26:03 -04:00
|
|
|
this._delay = delay || function( _, c, __ ) { c(); };
|
2015-05-19 13:13:49 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Perform an asynchronous request and invoke CALLBACK with the
|
|
|
|
* reply
|
|
|
|
*
|
|
|
|
* In the special case that the number of tries is set to zero, CALLBACK
|
|
|
|
* will be immediately invoked with a null error and result (but not
|
|
|
|
* necessarily asynchronously---that remains undefined).
|
|
|
|
*
|
|
|
|
* Otherwise, requests will continue to be re-issued until either the
|
2015-05-27 15:26:03 -04:00
|
|
|
* retry predicate fails or the number of retries are exhausted,
|
|
|
|
* whichever comes first. Once the retries are exhausted, the error and
|
|
|
|
* output data from the final request are returned.
|
2015-05-19 13:13:49 -04:00
|
|
|
*
|
|
|
|
* If the number of tries is negative, then requests will be performed
|
2015-05-27 15:26:03 -04:00
|
|
|
* indefinitely while the retry predicate is met; the delay function (as
|
|
|
|
* provided via the constructor) may be used to abort in this case.
|
2015-05-19 13:13:49 -04:00
|
|
|
*
|
|
|
|
* @param {string} input binary data to transmit
|
|
|
|
* @param {function(?Error,*)} callback continuation upon reply
|
|
|
|
*
|
|
|
|
* @return {DataApi} self
|
|
|
|
*/
|
|
|
|
'abstract override public request': function( input, callback )
|
|
|
|
{
|
|
|
|
this._try( input, callback, this._tries );
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-27 15:26:03 -04:00
|
|
|
* Recursively perform request until retry predicate failure or try
|
|
|
|
* count exhaustion
|
2015-05-19 13:13:49 -04:00
|
|
|
*
|
|
|
|
* For more information, see `#request'.
|
|
|
|
*
|
|
|
|
* @param {string} input binary data to transmit
|
|
|
|
* @param {function(?Error,*)} callback continuation upon reply
|
|
|
|
* @param {number} n number of retries remaining
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
'private _try': function( input, callback, n )
|
|
|
|
{
|
|
|
|
var _self = this;
|
|
|
|
|
|
|
|
// the special case of 0 retries still invokes the callback, but has
|
|
|
|
// no data to return
|
|
|
|
if ( n === 0 )
|
|
|
|
{
|
|
|
|
callback( null, null );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.request.super.call( this, input, function( err, output )
|
|
|
|
{
|
2015-05-27 15:26:03 -04:00
|
|
|
var complete = function()
|
|
|
|
{
|
|
|
|
callback( err, output );
|
|
|
|
};
|
|
|
|
|
2015-05-19 13:13:49 -04:00
|
|
|
// predicate determines whether a retry is necessary
|
|
|
|
if ( !!_self._pred( err, output ) === false )
|
|
|
|
{
|
2015-05-27 15:26:03 -04:00
|
|
|
return complete();
|
2015-05-19 13:13:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// note that we intentionally do not want to check <= 1, so that
|
|
|
|
// we can proceed indefinitely (JavaScript does not wrap on overflow)
|
|
|
|
if ( n === 1 )
|
|
|
|
{
|
2015-05-27 15:26:03 -04:00
|
|
|
return complete();
|
2015-05-19 13:13:49 -04:00
|
|
|
}
|
|
|
|
|
2015-05-22 16:22:58 -04:00
|
|
|
_self._delay(
|
|
|
|
( n - 1 ),
|
|
|
|
function()
|
|
|
|
{
|
|
|
|
_self._try( input, callback, ( n - 1 ) );
|
|
|
|
},
|
2015-05-27 15:26:03 -04:00
|
|
|
complete
|
2015-05-22 16:22:58 -04:00
|
|
|
);
|
2015-05-19 13:13:49 -04:00
|
|
|
} );
|
|
|
|
},
|
|
|
|
} );
|