Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <algorithm>
20 : : #include <chrono>
21 : : #include <cmath>
22 : : #include <functional>
23 : : #include <memory>
24 : : #include <mutex>
25 : : #include <random>
26 : : #include <thread>
27 : :
28 : : #include "wirestead/diagnostics/error_types.hpp"
29 : :
30 : : namespace wirestead {
31 : :
32 : : /**
33 : : * @brief Represents a decision on whether to retry a connection attempt.
34 : : */
35 : : struct ReconnectDecision {
36 : : bool retry{false};
37 : : std::chrono::milliseconds delay{0};
38 : : };
39 : :
40 : : /**
41 : : * @brief Function type for determining reconnection policy.
42 : : *
43 : : * Accepts the last error information and the current attempt count (0-based).
44 : : * Returns a ReconnectDecision.
45 : : */
46 : : using ReconnectPolicy = std::function<ReconnectDecision(const diagnostics::ErrorInfo&, uint32_t)>;
47 : :
48 : : /**
49 : : * @brief Creates a policy that retries with a fixed interval.
50 : : *
51 : : * @param delay The delay between retries.
52 : : * @return A ReconnectPolicy function.
53 : : */
54 : 3 : inline ReconnectPolicy FixedInterval(std::chrono::milliseconds delay) {
55 : 3 : return [delay](const diagnostics::ErrorInfo& error_info, uint32_t) -> ReconnectDecision {
56 [ + + ]: 17 : if (!error_info.retryable) {
57 : 1 : return {false, std::chrono::milliseconds(0)};
58 : : }
59 : 16 : return {true, delay};
60 : 3 : };
61 : : }
62 : :
63 : : /**
64 : : * @brief Creates a policy that retries with exponential backoff.
65 : : *
66 : : * @param min_delay The initial delay.
67 : : * @param max_delay The maximum delay cap.
68 : : * @param factor The multiplier for each retry (default 2.0).
69 : : * @param jitter Whether to add randomization to the delay (default true).
70 : : * @return A ReconnectPolicy function.
71 : : */
72 : 3 : inline ReconnectPolicy ExponentialBackoff(std::chrono::milliseconds min_delay, std::chrono::milliseconds max_delay,
73 : : double factor = 2.0, bool jitter = true) {
74 : : struct ProtectedRng {
75 : : std::mt19937 rng;
76 : : std::mutex mtx;
77 : :
78 : 1 : ProtectedRng() {
79 : 1 : auto seed = static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
80 : 1 : rng.seed(seed);
81 : 1 : }
82 : : };
83 : :
84 : 3 : std::shared_ptr<ProtectedRng> shared_rng;
85 [ + + ]: 3 : if (jitter) {
86 : 1 : shared_rng = std::make_shared<ProtectedRng>();
87 : : }
88 : :
89 : 6 : return [min_delay, max_delay, factor, shared_rng](const diagnostics::ErrorInfo& error_info,
90 : : uint32_t attempt_count) -> ReconnectDecision {
91 [ + + ]: 10 : if (!error_info.retryable) {
92 : 1 : return {false, std::chrono::milliseconds(0)};
93 : : }
94 : :
95 : 9 : double calculated = static_cast<double>(min_delay.count()) * std::pow(factor, attempt_count);
96 : 9 : double cap = static_cast<double>(max_delay.count());
97 : :
98 : : // Clamp to max_delay
99 : 9 : double delay_ms = std::min(calculated, cap);
100 : :
101 [ + + ]: 9 : if (shared_rng) {
102 : 1 : std::lock_guard<std::mutex> lock(shared_rng->mtx);
103 : : // Full Jitter: random between 0 and calculated delay
104 [ + - ]: 1 : std::uniform_real_distribution<> dist(0.0, delay_ms);
105 : 1 : delay_ms = dist(shared_rng->rng);
106 : 1 : }
107 : :
108 : 9 : return {true, std::chrono::milliseconds(static_cast<long long>(delay_ms))};
109 : 6 : };
110 : 3 : }
111 : :
112 : : } // namespace wirestead
|