/* * Copyright 2018 Google * * 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. */ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_EXPONENTIAL_BACKOFF_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_EXPONENTIAL_BACKOFF_H_ #include // NOLINT(build/c++11) #include "Firestore/core/src/firebase/firestore/util/async_queue.h" #include "Firestore/core/src/firebase/firestore/util/secure_random.h" namespace firebase { namespace firestore { namespace remote { /** * * A helper for running delayed operations following an exponential backoff * curve between attempts. * * The first attempt will be done immediately. After that, each retry will * have a delay that is made up of a "base" delay which follows the * exponential backoff curve, and a +/- <=50% "jitter" that is calculated and * added to the base delay. This prevents clients from accidentally * synchronizing their delays causing spikes of load to the backend. * */ class ExponentialBackoff { public: /** * @param queue The queue to run operations on. * @param timer_id The id to use when scheduling backoff operations on the * queue. * @param backoff_factor The multiplier to use to determine the extended base * delay after each attempt. * @param initial_delay The initial delay (used as the base delay on the first * retry attempt, that is, the second attempt). Note that jitter will * still be applied, so the actual delay could be as little as * `0.5*initial_delay`. * @param max_delay The maximum base delay after which no further backoff is * performed. Note that jitter will still be applied, so the actual delay * could be as much as `1.5*max_delay`. */ ExponentialBackoff(util::AsyncQueue* queue, util::TimerId timer_id, double backoff_factor, util::AsyncQueue::Milliseconds initial_delay, util::AsyncQueue::Milliseconds max_delay); /** * Resets the backoff delay. * * The very next `backoffAndRun` will have no delay. If it is called again * (i.e. due to an error), `initial_delay` (plus jitter) will be used, and * subsequent ones will increase according to the `backoff_factor`. */ void Reset() { current_base_ = Milliseconds{0}; } /** * Resets the backoff to the maximum delay (e.g. for use after * a RESOURCE_EXHAUSTED error). */ void ResetToMax() { current_base_ = max_delay_; } /** * Waits for `current_base` seconds (which may be zero), increases the delay * and runs the specified operation. If there was a pending operation waiting * to be run already, it will be canceled. */ void BackoffAndRun(util::AsyncQueue::Operation&& operation); /** Cancels any pending backoff operation scheduled via `BackoffAndRun`. */ void Cancel() { delayed_operation_.Cancel(); } private: using Milliseconds = util::AsyncQueue::Milliseconds; // Returns a random value in the range [-current_base_/2, current_base_/2]. Milliseconds GetDelayWithJitter(); Milliseconds ClampDelay(Milliseconds delay) const; util::AsyncQueue* const queue_; const util::TimerId timer_id_; util::DelayedOperation delayed_operation_; const double backoff_factor_; Milliseconds current_base_{0}; const Milliseconds initial_delay_; const Milliseconds max_delay_; util::SecureRandom secure_random_; }; } // namespace remote } // namespace firestore } // namespace firebase #endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_EXPONENTIAL_BACKOFF_H_