fix(status): show a strip when the homeserver hangs, not 110 s later (#317)
CI / Build & Quality Checks (pull_request) Successful in 2m40s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 17m50s
CI / Build & Quality Checks (pull_request) Successful in 2m40s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 17m50s
A hung Synapse (accepts connections, never answers) kept sync SYNCING for up to 110 s (pollTimeout 30 s + the SDK's 80 s buffer), so no strip showed and Kuma was never asked. A watchdog now notices a /sync overdue by 45 s, confirms with a 5 s GET /_matrix/client/versions, then shows "Lotus Chat's server isn't responding…" and polls Kuma at once. Fixes #317 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3878a85339
commit
e8fd0a1112
@@ -0,0 +1,16 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { SyncState } from 'matrix-js-sdk';
|
||||
import { STALL_AFTER_MS, syncOverdue } from './useHomeserverStall';
|
||||
|
||||
test('syncOverdue: only while SYNCING and past the stall threshold (#317)', () => {
|
||||
const now = 1_000_000;
|
||||
assert.equal(syncOverdue(now - STALL_AFTER_MS - 1, now, SyncState.Syncing), true);
|
||||
assert.equal(syncOverdue(now - 20_000, now, SyncState.Syncing), false, 'a normal long-poll');
|
||||
assert.equal(
|
||||
syncOverdue(now - 120_000, now, SyncState.Reconnecting),
|
||||
false,
|
||||
'SyncStatus already shows that',
|
||||
);
|
||||
assert.equal(syncOverdue(now - 120_000, now, null), false);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { atom } from 'jotai';
|
||||
import { ClientEvent, MatrixClient, SyncState } from 'matrix-js-sdk';
|
||||
|
||||
/**
|
||||
* [Gitea #317] A hung homeserver (accepts connections, never answers) went
|
||||
* unnoticed for up to 110 s: the SDK only gives up on a /sync long-poll after
|
||||
* pollTimeout (30 s) + an 80 s buffer, and until then the sync state stays
|
||||
* SYNCING, so no strip showed and Kuma wasn't asked. This watchdog notices a
|
||||
* /sync that's overdue and confirms with a short probe of /versions.
|
||||
*/
|
||||
export const STALL_AFTER_MS = 45_000; // a healthy long-poll returns within ~30 s
|
||||
const CHECK_EVERY_MS = 10_000;
|
||||
const PROBE_TIMEOUT_MS = 5_000;
|
||||
|
||||
export const homeserverStalledAtom = atom(false);
|
||||
|
||||
/** Pure: should we probe, given the last sync response time? */
|
||||
export const syncOverdue = (lastSyncAt: number, now: number, state: SyncState | null): boolean =>
|
||||
state === SyncState.Syncing && now - lastSyncAt > STALL_AFTER_MS;
|
||||
|
||||
export function useHomeserverStall(mx: MatrixClient): boolean {
|
||||
const [stalled, setStalled] = useState(false);
|
||||
useEffect(() => {
|
||||
let lastSyncAt = Date.now();
|
||||
let probing = false;
|
||||
const onSync = () => {
|
||||
lastSyncAt = Date.now();
|
||||
setStalled(false);
|
||||
};
|
||||
const check = async () => {
|
||||
if (probing || !syncOverdue(lastSyncAt, Date.now(), mx.getSyncState())) return;
|
||||
probing = true;
|
||||
let answered = false;
|
||||
try {
|
||||
const res = await fetch(`${mx.getHomeserverUrl()}/_matrix/client/versions`, {
|
||||
cache: 'no-store',
|
||||
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
||||
});
|
||||
answered = res.ok;
|
||||
} catch {
|
||||
answered = false;
|
||||
}
|
||||
probing = false;
|
||||
// Still overdue and the server didn't answer a trivial request: stalled.
|
||||
if (!answered && syncOverdue(lastSyncAt, Date.now(), mx.getSyncState())) setStalled(true);
|
||||
if (answered) setStalled(false);
|
||||
};
|
||||
mx.on(ClientEvent.Sync, onSync);
|
||||
const id = setInterval(check, CHECK_EVERY_MS);
|
||||
return () => {
|
||||
mx.removeListener(ClientEvent.Sync, onSync);
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [mx]);
|
||||
return stalled;
|
||||
}
|
||||
Reference in New Issue
Block a user