// Example
// -------
/*
var result = Utils_.rateLimitExpBackoff(
function() {return functionUnderTest()},
BACKOFF_WAIT_,
BACKOFF_TRIES_,
1,
true)
*/
// Exponetial Backoff
// ------------------
var BACKOFF_ENABLE_ = true
var BACKOFF_WAIT_ = 1000
var BACKOFF_TRIES_ = 5
var BACKOFF_MINIMUM_ERROR_LENGTH_ = 15
var BACKOFF_ON_ALL_ERRORS_ = false
var BACKOFF_ON_ERRORS = [
"Exception: Service invoked too many times",
"Exception: Rate Limit Exceeded",
"Exception: Quota Error: User Rate Limit Exceeded",
"Service error: Spreadsheets",
"Exception: Internal error. Please try again.",
"Exception: Cannot execute AddColumn because another task",
"Execution failed: Service invoked too many times in a short time",
"Exception: Failed to retrieve form data",
]
/**
*
* recursive rateLimitExpBackoff()
*
* @param {function} callBack some function to call that might return rate limit exception
* @param {number} [sleepFor=1000] optional amount of time to sleep for on the first failure in missliseconds
* @param {number} [maxAttempts=5] optional maximum number of amounts to try
* @param {number} [attempts=1] optional the attempt number of this instance - usually only used recursively and not user supplied
* @param {boolean} [optLogAttempts=false] log re-attempts to Logger
*
* @return {object} results of the callback
*/
var rateLimitExpBackoff = function(callBack, sleepFor, maxAttempts, attempts , optLogAttempts) {
Log_.functionEntryPoint()
var callingfunction = 'Utils.rateLimitExpBackoff()'
sleepFor = Math.abs(sleepFor || BACKOFF_WAIT_);
attempts = Math.abs(attempts || 1);
maxAttempts = Math.abs(maxAttempts || BACKOFF_TRIES_);
Log_.finest('sleepFor: ' + sleepFor)
Log_.finest('attempts: ' + attempts)
Log_.finest('maxAttempts: ' + maxAttempts)
Log_.finest('optLogAttempts: ' + optLogAttempts)
// Check properly constructed
Assert.assert(
callBack && typeof(callBack) === "function",
callingfunction,
'You need to specify a function for rateLimitBackoff to execute')
if (!BACKOFF_ENABLE_) {
return callBack()
}
// Try to execute it
try {
var r = callBack();
return r;
} catch(error) {
if (optLogAttempts) {
Log_.info("Backoff " + attempts + ": " + error)
}
// Failed due to rate limiting?
if (errorQualifies(error)) {
Assert.assert(
attempts <= maxAttempts,
callingfunction,
error + " (tried backing off " + (attempts - 1) + " times")
// Wait for some amount of time based on how many times
// we've tried plus a small random bit to avoid races
var wait = Math.pow(2,attempts) * sleepFor
var randomWait = Math.round(Math.random() * sleepFor)
Utilities.sleep(wait + randomWait);
Log_.finest('Sleep: ' + (wait + randomWait))
// Try again
return Utils_.rateLimitExpBackoff(callBack, sleepFor, maxAttempts, attempts + 1, optLogAttempts);
} else {
// Some other error
throw (error);
}
}
// Private Functions
// -----------------
/**
* Check if the error text matches that in the list
* of qualifying errors
*/
function errorQualifies (errorText) {
Log_.functionEntryPoint('Error text: ' + errorText)
if (BACKOFF_ON_ALL_ERRORS_) {
return true
}
var foundQualifyingError = BACKOFF_ON_ERRORS.some(function(nextErrorToCompare){
Log_.finest('nextErrorToCompare: ' + nextErrorToCompare)
var errorLongEnough = nextErrorToCompare.length > BACKOFF_MINIMUM_ERROR_LENGTH_
Log_.finest('errorLongEnough: ' + errorLongEnough)
var strippedText = errorText.toString().slice(0, nextErrorToCompare.length)
Log_.finest('stripped text: ' + strippedText)
return (errorLongEnough && (strippedText === nextErrorToCompare))
})
Log_.finest('Error qualifies: ' + foundQualifyingError)
return foundQualifyingError
} // Utils_.rateLimitExpBackoff.errorQualifies()
} // Utils_.rateLimitExpBackoff()