increase time between connection tries

each time we do a failed attempt to connect increase the time until the
next try

use an elapsed timer to ensure we do properly wait between each attempt

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
This commit is contained in:
Matthieu Gallien 2022-04-12 23:43:19 +02:00
parent 43b5eaa5bd
commit 4e348db1d0
No known key found for this signature in database
GPG key ID: 7D0F74F05C22F553
3 changed files with 119 additions and 23 deletions

View file

@ -21,17 +21,19 @@
#include "logger.h"
#include "configfile.h"
#include "ocsnavigationappsjob.h"
#include "pushnotifications.h"
#include <QSettings>
#include <QTimer>
#include <qfontmetrics.h>
#include <QFontMetrics>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QNetworkRequest>
#include <QBuffer>
#include <cmath>
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg)
@ -54,6 +56,8 @@ AccountState::AccountState(AccountPtr account)
this, &AccountState::slotCredentialsFetched);
connect(account.data(), &Account::credentialsAsked,
this, &AccountState::slotCredentialsAsked);
connect(account.data(), &Account::pushNotificationsReady,
this, &AccountState::slotPushNotificationsReady);
connect(this, &AccountState::isConnectedChanged, [=]{
// Get the Apps available on the server if we're now connected.
@ -61,6 +65,12 @@ AccountState::AccountState(AccountPtr account)
fetchNavigationApps();
}
});
connect(&_checkConnectionTimer, &QTimer::timeout, this, &AccountState::slotCheckConnection);
_checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec);
_checkConnectionTimer.start();
QTimer::singleShot(0, this, &AccountState::slotCheckConnection);
}
AccountState::~AccountState() = default;
@ -120,6 +130,10 @@ void AccountState::setState(State state)
if (oldState == Connected || _state == Connected) {
emit isConnectedChanged();
}
if (_state == Connected) {
_checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec);
setRetryCount(0);
}
}
// might not have changed but the underlying _connectionErrors might have
@ -149,6 +163,16 @@ QString AccountState::stateString(State state)
return tr("Unknown account state");
}
int AccountState::retryCount() const
{
return _retryCount;
}
void AccountState::increaseRetryCount()
{
++_retryCount;
}
bool AccountState::isSignedOut() const
{
return _state == SignedOut;
@ -234,8 +258,15 @@ void AccountState::trySignIn()
}
}
void AccountState::systemOnlineConfigurationChanged()
{
QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection);
}
void AccountState::checkConnectivity()
{
qCInfo(lcAccountState()) << "check connectivity";
if (isSignedOut() || _waitingForNewCredentials) {
return;
}
@ -293,6 +324,20 @@ void AccountState::checkConnectivity()
void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors)
{
const auto updateRetryCount = [this]() {
increaseRetryCount();
qCInfo(lcAccountState()) << "connection retry count" << retryCount();
_lastCheckConnectionTimer.invalidate();
_lastCheckConnectionTimer.start();
};
const auto resetRetryCount = [this]() {
qCInfo(lcAccountState) << "reset retry count";
setRetryCount(0);
_lastCheckConnectionTimer.invalidate();
_lastCheckConnectionTimer.start();
};
if (isSignedOut()) {
qCWarning(lcAccountState) << "Signed out, ignoring" << status << _account->url().toString();
return;
@ -329,6 +374,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
case ConnectionValidator::Connected:
if (_state != Connected) {
setState(Connected);
resetRetryCount();
// Get the Apps available on the server.
fetchNavigationApps();
@ -340,6 +386,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
case ConnectionValidator::Undefined:
case ConnectionValidator::NotConfigured:
setState(Disconnected);
updateRetryCount();
break;
case ConnectionValidator::ServerVersionMismatch:
setState(ConfigurationError);
@ -349,6 +396,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
// or because we are having network issues. The latter one is
// much more likely, so keep trying to connect.
setState(NetworkError);
updateRetryCount();
break;
case ConnectionValidator::CredentialsWrong:
case ConnectionValidator::CredentialsNotReady:
@ -367,6 +415,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
break;
case ConnectionValidator::Timeout:
setState(NetworkError);
updateRetryCount();
break;
}
}
@ -456,6 +505,11 @@ void AccountState::fetchNavigationApps(){
job->getNavigationApps();
}
void AccountState::setRetryCount(int count)
{
_retryCount = count;
}
void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
if(statusCode == 200){
qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value;
@ -468,6 +522,42 @@ void AccountState::slotOcsError(int statusCode, const QString &message)
qCDebug(lcAccountState) << "Error " << statusCode << " while fetching new navigation apps: " << message;
}
void AccountState::slotCheckConnection()
{
if (_lastCheckConnectionTimer.isValid()) {
const auto currentDelay = (retryCount() <= 1 ? ConnectionValidator::DefaultCallingIntervalMsec :
(retryCount() == 2 ? ConnectionValidator::DefaultCallingIntervalMsec * 2 :
(retryCount() == 3 ? ConnectionValidator::DefaultCallingIntervalMsec * 4 :
ConnectionValidator::DefaultCallingIntervalMsec * 8)));
if (!_lastCheckConnectionTimer.hasExpired(currentDelay - 1)) {
qCInfo(lcAccountState()) << "timer has not expired: do not check now" << _lastCheckConnectionTimer.elapsed() << currentDelay;
return;
}
}
const auto currentState = state();
// Don't check if we're manually signed out or
// when the error is permanent.
const auto pushNotifications = account()->pushNotifications();
const auto pushNotificationsAvailable = (pushNotifications && pushNotifications->isReady());
if (currentState != AccountState::SignedOut && currentState != AccountState::ConfigurationError
&& currentState != AccountState::AskingCredentials && !pushNotificationsAvailable) {
checkConnectivity();
} else if (currentState == AccountState::SignedOut && lastConnectionStatus() == AccountState::ConnectionStatus::SslError) {
qCWarning(lcAccountState()) << "Account is signed out due to SSL Handshake error. Going to perform a sign-in attempt...";
trySignIn();
}
}
void AccountState::slotPushNotificationsReady()
{
if (state() != AccountState::State::Connected) {
setState(AccountState::State::Connected);
}
}
void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode)
{
if(_account){

View file

@ -16,11 +16,13 @@
#ifndef ACCOUNTINFO_H
#define ACCOUNTINFO_H
#include "connectionvalidator.h"
#include "creds/abstractcredentials.h"
#include <QByteArray>
#include <QElapsedTimer>
#include <QPointer>
#include "connectionvalidator.h"
#include "creds/abstractcredentials.h"
#include <QTimer>
#include <memory>
@ -104,6 +106,9 @@ public:
State state() const;
static QString stateString(State state);
int retryCount() const;
void increaseRetryCount();
bool isSignedOut() const;
AccountAppList appList() const;
@ -175,6 +180,8 @@ public:
void trySignIn();
void systemOnlineConfigurationChanged();
public slots:
/// Triggers a ping to the server to update state and
/// connection status and errors.
@ -184,6 +191,8 @@ private:
void setState(State state);
void fetchNavigationApps();
void setRetryCount(int count);
signals:
void stateChanged(State state);
void isConnectedChanged();
@ -205,6 +214,11 @@ protected Q_SLOTS:
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
void slotOcsError(int statusCode, const QString &message);
private Q_SLOTS:
void slotCheckConnection();
void slotPushNotificationsReady();
private:
AccountPtr _account;
State _state;
@ -241,6 +255,12 @@ private:
AccountAppList _apps;
bool _isDesktopNotificationsAllowed;
int _retryCount = 0;
QTimer _checkConnectionTimer;
QElapsedTimer _lastCheckConnectionTimer;
};
class AccountApp : public QObject

View file

@ -470,30 +470,16 @@ void Application::slotCleanup()
void Application::slotSystemOnlineConfigurationChanged(QNetworkConfiguration cnf)
{
if (cnf.state() & QNetworkConfiguration::Active) {
QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection);
const auto list = AccountManager::instance()->accounts();
for (const auto &accountState : list) {
accountState->systemOnlineConfigurationChanged();
}
}
}
void Application::slotCheckConnection()
{
const auto list = AccountManager::instance()->accounts();
for (const auto &accountState : list) {
AccountState::State state = accountState->state();
// Don't check if we're manually signed out or
// when the error is permanent.
const auto pushNotifications = accountState->account()->pushNotifications();
const auto pushNotificationsAvailable = (pushNotifications && pushNotifications->isReady());
if (state != AccountState::SignedOut && state != AccountState::ConfigurationError
&& state != AccountState::AskingCredentials && !pushNotificationsAvailable) {
accountState->checkConnectivity();
} else if (state == AccountState::SignedOut && accountState->lastConnectionStatus() == AccountState::ConnectionStatus::SslError) {
qCWarning(lcApplication) << "Account is signed out due to SSL Handshake error. Going to perform a sign-in attempt...";
accountState->trySignIn();
}
}
if (list.isEmpty()) {
if (AccountManager::instance()->accounts().isEmpty()) {
// let gui open the setup wizard
_gui->slotOpenSettingsDialog();