58 lines
2.1 KiB
TypeScript
58 lines
2.1 KiB
TypeScript
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;
|
||
|
|
}
|