aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core/src/firebase/firestore/remote/exponential_backoff.h
blob: 8836e7e33143dc83eedd0ead873b49e922bb7a65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/*
 * 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 <chrono>  // 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_