"use strict"; /*! * Copyright 2017 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("./logger"); /* * @module firestore/backoff * @private * * Contains backoff logic to facilitate RPC error handling. This class derives * its implementation from the Firestore Mobile Web Client. * * @see https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/src/remote/backoff.ts */ /*! * The default initial backoff time in milliseconds after an error. * Set to 1s according to https://cloud.google.com/apis/design/errors. */ const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000; /*! * The default maximum backoff time in milliseconds. */ const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000; /*! * The default factor to increase the backup by after each failed attempt. */ const DEFAULT_BACKOFF_FACTOR = 1.5; /*! * The default jitter to distribute the backoff attempts by (0 means no * randomization, 1.0 means +/-50% randomization). */ const DEFAULT_JITTER_FACTOR = 1.0; /*! * The maximum number of retries that will be attempted by backoff * before stopping all retry attempts. */ exports.MAX_RETRY_ATTEMPTS = 10; /*! * The timeout handler used by `ExponentialBackoff`. */ let delayExecution = setTimeout; /** * Allows overriding of the timeout handler used by the exponential backoff * implementation. If not invoked, we default to `setTimeout()`. * * Used only in testing. * * @private * @param {function} handler A handler than matches the API of `setTimeout()`. */ function setTimeoutHandler(handler) { delayExecution = handler; } exports.setTimeoutHandler = setTimeoutHandler; /** * A helper for running delayed tasks following an exponential backoff curve * between attempts. * * Each delay is made up of a "base" delay which follows the exponential * backoff curve, and a "jitter" (+/- 50% by default) that is calculated and * added to the base delay. This prevents clients from accidentally * synchronizing their delays causing spikes of load to the backend. * * @private */ class ExponentialBackoff { constructor(options = {}) { /** * The number of retries that has been attempted. * * @private */ this._retryCount = 0; /** * The backoff delay of the current attempt. * * @private */ this.currentBaseMs = 0; /** * Whether we are currently waiting for backoff to complete. * * @private */ this.awaitingBackoffCompletion = false; this.initialDelayMs = options.initialDelayMs !== undefined ? options.initialDelayMs : DEFAULT_BACKOFF_INITIAL_DELAY_MS; this.backoffFactor = options.backoffFactor !== undefined ? options.backoffFactor : DEFAULT_BACKOFF_FACTOR; this.maxDelayMs = options.maxDelayMs !== undefined ? options.maxDelayMs : DEFAULT_BACKOFF_MAX_DELAY_MS; this.jitterFactor = options.jitterFactor !== undefined ? options.jitterFactor : DEFAULT_JITTER_FACTOR; } /** * Resets the backoff delay and retry count. * * The very next backoffAndWait() will have no delay. If it is called again * (i.e. due to an error), initialDelayMs (plus jitter) will be used, and * subsequent ones will increase according to the backoffFactor. * * @private */ reset() { this._retryCount = 0; this.currentBaseMs = 0; } /** * Resets the backoff delay to the maximum delay (e.g. for use after a * RESOURCE_EXHAUSTED error). * * @private */ resetToMax() { this.currentBaseMs = this.maxDelayMs; } /** * Returns a promise that resolves after currentDelayMs, and increases the * delay for any subsequent attempts. * * @return A Promise that resolves when the current delay elapsed. * @private */ backoffAndWait() { if (this.awaitingBackoffCompletion) { return Promise.reject(new Error('A backoff operation is already in progress.')); } if (this.retryCount > exports.MAX_RETRY_ATTEMPTS) { return Promise.reject(new Error('Exceeded maximum number of retries allowed.')); } // First schedule using the current base (which may be 0 and should be // honored as such). const delayWithJitterMs = this.currentBaseMs + this.jitterDelayMs(); if (this.currentBaseMs > 0) { logger_1.logger('ExponentialBackoff.backoffAndWait', null, `Backing off for ${delayWithJitterMs} ms ` + `(base delay: ${this.currentBaseMs} ms)`); } // Apply backoff factor to determine next delay and ensure it is within // bounds. this.currentBaseMs *= this.backoffFactor; this.currentBaseMs = Math.max(this.currentBaseMs, this.initialDelayMs); this.currentBaseMs = Math.min(this.currentBaseMs, this.maxDelayMs); this._retryCount += 1; return new Promise(resolve => { this.awaitingBackoffCompletion = true; delayExecution(() => { this.awaitingBackoffCompletion = false; resolve(); }, delayWithJitterMs); }); } // Visible for testing. get retryCount() { return this._retryCount; } /** * Returns a randomized "jitter" delay based on the current base and jitter * factor. * * @returns {number} The jitter to apply based on the current delay. * @private */ jitterDelayMs() { return (Math.random() - 0.5) * this.jitterFactor * this.currentBaseMs; } } exports.ExponentialBackoff = ExponentialBackoff; //# sourceMappingURL=backoff.js.map