fix(gui): add retry backoff for agent connections

Agent connections were using retryMs=0, causing a tight loop when the
/connection file read failed (e.g., toolsConn nil). The loop was:
  connection fails → disconnected signal → refreshSessions →
  reconcileAgentConnections → new connection → fails immediately → repeat

Fix by:
- Add maxRetries parameter to NinePConnection::start()
- Use 5s retry interval with max 3 retries for agent connections
- Reset retry count on successful connection
This commit is contained in:
Levi Neely 2026-08-03 16:02:59 +02:00
parent 8a0a5e6ff9
commit 21baea8691
3 changed files with 14 additions and 7 deletions

View File

@ -12,12 +12,14 @@ NinePConnection::~NinePConnection()
stop(); stop();
} }
void NinePConnection::start(const QString &program, const QStringList &args, int retryMs, bool probe) void NinePConnection::start(const QString &program, const QStringList &args, int retryMs, bool probe, int maxRetries)
{ {
m_program = program; m_program = program;
m_args = args; m_args = args;
m_retryMs = retryMs; m_retryMs = retryMs;
m_probe = probe; m_probe = probe;
m_maxRetries = maxRetries;
m_retryCount = 0;
m_stopRequested = false; m_stopRequested = false;
m_openMarkerSeen = false; m_openMarkerSeen = false;
m_retryTimer.stop(); m_retryTimer.stop();
@ -91,17 +93,21 @@ void NinePConnection::onFinished(int exitCode, QProcess::ExitStatus)
if (!m_process) return; if (!m_process) return;
m_process->deleteLater(); m_process->deleteLater();
m_process = nullptr; m_process = nullptr;
// Successful probe that was connected: schedule re-probe (reset retry count).
if (m_probe && m_state == Connected && m_openMarkerSeen && exitCode == 0 && !m_stopRequested) { if (m_probe && m_state == Connected && m_openMarkerSeen && exitCode == 0 && !m_stopRequested) {
m_retryCount = 0;
if (m_retryMs > 0) if (m_retryMs > 0)
m_retryTimer.start(m_retryMs); m_retryTimer.start(m_retryMs);
else else
startProcess(); // immediate re-probe startProcess(); // immediate re-probe
return; return;
} }
if (m_stopRequested || m_retryMs <= 0) { // No retry configured, or stop requested, or max retries exceeded.
if (m_stopRequested || m_retryMs <= 0 || (m_maxRetries > 0 && m_retryCount >= m_maxRetries)) {
setState(Disconnected); setState(Disconnected);
return; return;
} }
++m_retryCount;
setState(RetryWait); setState(RetryWait);
m_retryTimer.start(m_retryMs); m_retryTimer.start(m_retryMs);
} }

View File

@ -16,7 +16,7 @@ public:
explicit NinePConnection(QObject *parent = nullptr); explicit NinePConnection(QObject *parent = nullptr);
~NinePConnection() override; ~NinePConnection() override;
void start(const QString &program, const QStringList &args, int retryMs = 4000, bool probe = false); void start(const QString &program, const QStringList &args, int retryMs = 4000, bool probe = false, int maxRetries = 0);
void stop(); void stop();
State state() const { return m_state; } State state() const { return m_state; }
@ -47,6 +47,8 @@ private:
int m_retryMs = 4000; int m_retryMs = 4000;
bool m_probe = false; bool m_probe = false;
bool m_openMarkerSeen = false; bool m_openMarkerSeen = false;
int m_maxRetries = 0; // 0 = unlimited
int m_retryCount = 0;
}; };
#endif // NINEPCONNECTION_H #endif // NINEPCONNECTION_H

View File

@ -254,11 +254,10 @@ void Ollie9pClient::reconcileAgentConnections()
// rather than showing a transient red indicator. // rather than showing a transient red indicator.
refreshSessions(); refreshSessions();
}); });
// No retry — reconcileAgentConnections is the authority for // Retry with backoff (5s, max 3 retries) to avoid hammering the server
// connection lifecycle. If the agent is still alive after refresh, // when the connection file exists but returns an error (e.g., toolsConn nil).
// reconcile will create a new connection.
connection->start(ollie9pBin(), {"-a", serverAddr(), "read", "--open-marker", connection->start(ollie9pBin(), {"-a", serverAddr(), "read", "--open-marker",
"session/" + sid + "/agent/" + aid + "/connection"}, 0, true); "session/" + sid + "/agent/" + aid + "/connection"}, 5000, true, 3);
} }
} }
for (auto it = m_agentConnections.begin(); it != m_agentConnections.end();) { for (auto it = m_agentConnections.begin(); it != m_agentConnections.end();) {