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();
}
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_args = args;
m_retryMs = retryMs;
m_probe = probe;
m_maxRetries = maxRetries;
m_retryCount = 0;
m_stopRequested = false;
m_openMarkerSeen = false;
m_retryTimer.stop();
@ -91,17 +93,21 @@ void NinePConnection::onFinished(int exitCode, QProcess::ExitStatus)
if (!m_process) return;
m_process->deleteLater();
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) {
m_retryCount = 0;
if (m_retryMs > 0)
m_retryTimer.start(m_retryMs);
else
startProcess(); // immediate re-probe
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);
return;
}
++m_retryCount;
setState(RetryWait);
m_retryTimer.start(m_retryMs);
}

View File

@ -16,7 +16,7 @@ public:
explicit NinePConnection(QObject *parent = nullptr);
~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();
State state() const { return m_state; }
@ -47,6 +47,8 @@ private:
int m_retryMs = 4000;
bool m_probe = false;
bool m_openMarkerSeen = false;
int m_maxRetries = 0; // 0 = unlimited
int m_retryCount = 0;
};
#endif // NINEPCONNECTION_H

View File

@ -254,11 +254,10 @@ void Ollie9pClient::reconcileAgentConnections()
// rather than showing a transient red indicator.
refreshSessions();
});
// No retry — reconcileAgentConnections is the authority for
// connection lifecycle. If the agent is still alive after refresh,
// reconcile will create a new connection.
// Retry with backoff (5s, max 3 retries) to avoid hammering the server
// when the connection file exists but returns an error (e.g., toolsConn nil).
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();) {