-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: guard against overflow in sse backoff calculation (#455)
In some platform configurations, the SSE backoff calculation overflows which results in failing to schedule another backoff attempt via `asio::deadline_timer::expires_from_now`. This PR refactors the backoff algorithm to use free functions which can be independently tested. It fixes the bug by constraining the maximum backoff exponent based on the configurable max backoff parameter. Finally, I've added some test vectors for large attempt values. In the bug report, the overflow happens at about 34 minutes which corresponded to 55 attempts.
- Loading branch information
1 parent
1ca48d5
commit a3c1e58
Showing
6 changed files
with
212 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
#include "backoff_detail.hpp" | ||
|
||
#include <algorithm> | ||
#include <cmath> | ||
|
||
namespace launchdarkly::sse::detail { | ||
std::chrono::milliseconds calculate_backoff( | ||
std::chrono::milliseconds const initial, | ||
std::uint64_t const attempt, | ||
std::uint64_t const max_exponent) { | ||
std::uint64_t const exponent = std::min(attempt - 1, max_exponent); | ||
double const exponentiated = std::pow(2, exponent); | ||
return initial * static_cast<uint64_t>(exponentiated); | ||
} | ||
|
||
std::chrono::milliseconds jitter(std::chrono::milliseconds const base, | ||
double const jitter_ratio, | ||
std::function<double(double)> const& random) { | ||
double const jitter = random(jitter_ratio); | ||
auto const base_as_double = static_cast<double>(base.count()); | ||
double const jittered_base = base_as_double - jitter * base_as_double; | ||
return std::chrono::milliseconds(static_cast<uint64_t>(jittered_base)); | ||
} | ||
|
||
std::chrono::milliseconds delay(std::chrono::milliseconds const initial, | ||
Check warning on line 25 in libs/server-sent-events/src/backoff_detail.cpp GitHub Actions / cpp-linter/libs/server-sent-events/src/backoff_detail.cpp:25:33 [bugprone-easily-swappable-parameters]
|
||
std::chrono::milliseconds const max, | ||
std::uint64_t const attempt, | ||
std::uint64_t const max_exponent, | ||
Check warning on line 28 in libs/server-sent-events/src/backoff_detail.cpp GitHub Actions / cpp-linter/libs/server-sent-events/src/backoff_detail.cpp:28:33 [bugprone-easily-swappable-parameters]
|
||
double const jitter_ratio, | ||
std::function<double(double)> const& random) { | ||
auto const backoff = calculate_backoff(initial, attempt, max_exponent); | ||
auto const constrained_backoff = std::min(backoff, max); | ||
return jitter(constrained_backoff, jitter_ratio, random); | ||
} | ||
} // namespace launchdarkly::sse::detail |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#pragma once | ||
|
||
#include <chrono> | ||
#include <functional> | ||
|
||
namespace launchdarkly::sse::detail { | ||
/** | ||
* Calculate an exponential backoff based on the initial retry time and | ||
* the current attempt. | ||
* | ||
* @param initial The initial retry delay. | ||
* @param attempt The current attempt count. | ||
* @param max_exponent The maximum exponent to use in the calculation. | ||
* | ||
* @return The current backoff based on the number of attempts. | ||
*/ | ||
|
||
std::chrono::milliseconds calculate_backoff(std::chrono::milliseconds initial, | ||
std::uint64_t attempt, | ||
std::uint64_t max_exponent); | ||
/** | ||
* Produce a jittered version of the base value. This value will be adjusted | ||
* randomly to be between 50% and 100% of the input duration. | ||
* | ||
* @param base The base duration to jitter. | ||
* @param jitter_ratio The ratio to use when jittering. | ||
* @param random A random method which produces a value between 0 and | ||
* jitter_ratio. | ||
* @return The jittered duration. | ||
*/ | ||
std::chrono::milliseconds jitter(std::chrono::milliseconds base, | ||
double jitter_ratio, | ||
std::function<double(double)> const& random); | ||
|
||
/** | ||
* | ||
* @param initial The initial retry delay. | ||
* @param max The maximum delay between retries. | ||
* @param attempt The current attempt. | ||
* @param max_exponent The maximum exponent to use in the calculation. | ||
* @param jitter_ratio The ratio to use when jittering. | ||
* @param random A random method which produces a value between 0 and | ||
* jitter_ratio. | ||
* @return The delay between connection attempts. | ||
*/ | ||
std::chrono::milliseconds delay(std::chrono::milliseconds initial, | ||
std::chrono::milliseconds max, | ||
std::uint64_t attempt, | ||
std::uint64_t max_exponent, | ||
double jitter_ratio, | ||
std::function<double(double)> const& random); | ||
} // namespace launchdarkly::sse::detail |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters