Use an exponential backoff when deciding how long we need to wait for reconnects (#2892)

Co-authored-by: Leon Richardt <leon.richardt@gmail.com>
This commit is contained in:
pajlada
2021-06-20 15:29:52 +02:00
committed by GitHub
parent d6b5921a0e
commit 0c2488505c
6 changed files with 129 additions and 18 deletions
+4 -15
View File
@@ -5,9 +5,6 @@
namespace chatterino {
// The minimum interval between attempting to establish a new connection
const int RECONNECT_MIN_INTERVAL = 15000;
namespace {
const auto payload = QString("chatterino/" CHATTERINO_VERSION);
@@ -48,18 +45,11 @@ IrcConnection::IrcConnection(QObject *parent)
return;
}
auto delta =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - this->lastConnected_)
.count();
delta = delta < RECONNECT_MIN_INTERVAL
? (RECONNECT_MIN_INTERVAL - delta)
: 10;
qCDebug(chatterinoIrc) << "Reconnecting in" << delta << "ms";
this->reconnectTimer_.start(delta);
auto delay = this->reconnectBackoff_.next();
qCDebug(chatterinoIrc) << "Reconnecting in" << delay.count() << "ms";
this->reconnectTimer_.start(delay);
});
this->reconnectTimer_.setInterval(RECONNECT_MIN_INTERVAL);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
@@ -120,13 +110,12 @@ IrcConnection::IrcConnection(QObject *parent)
[this](Communi::IrcMessage *message) {
// This connection is probably still alive
this->recentlyReceivedMessage_ = true;
this->reconnectBackoff_.reset();
});
}
void IrcConnection::open()
{
// Accurately track the time a connection was opened
this->lastConnected_ = std::chrono::steady_clock::now();
this->expectConnectionLoss_ = false;
this->waitingForPong_ = false;
this->recentlyReceivedMessage_ = false;
+5 -2
View File
@@ -1,10 +1,11 @@
#pragma once
#include "util/ExponentialBackoff.hpp"
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
#include <chrono>
namespace chatterino {
@@ -28,7 +29,9 @@ private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
std::chrono::steady_clock::time_point lastConnected_;
// Reconnect with a base delay of 1 second and max out at 1 second * (2^4) (i.e. 16 seconds)
ExponentialBackoff<4> reconnectBackoff_{std::chrono::milliseconds{1000}};
std::atomic<bool> expectConnectionLoss_{false};
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <algorithm>
#include <chrono>
namespace chatterino {
// Yes, you can't specify the base 😎 deal with it
template <unsigned maxSteps>
class ExponentialBackoff
{
public:
/**
* Creates an object helping you make exponentially (with base 2) backed off times.
*
* @param start The start time in milliseconds
* @param maxSteps The max number of progressions we will take before stopping
*
* For example, ExponentialBackoff(10ms, 3) would have the next() function return 10ms, 20ms, 40ms, 40ms, ..., 40ms
**/
ExponentialBackoff(const std::chrono::milliseconds &start)
: start_(start)
, step_{1}
{
static_assert(maxSteps > 1, "maxSteps must be higher than 1");
}
/**
* Return the current number in the progression and increment the step until the next one (assuming we're not at the cap)
*
* @returns current step in milliseconds
**/
[[nodiscard]] std::chrono::milliseconds next()
{
auto next = this->start_ * (1 << (this->step_ - 1));
this->step_ += 1;
if (this->step_ >= maxSteps)
{
this->step_ = maxSteps;
}
return next;
}
/**
* Reset the progression back to its initial state
**/
void reset()
{
this->step_ = 1;
}
private:
const std::chrono::milliseconds start_;
unsigned step_;
};
} // namespace chatterino