Skip relays that fail to connect when rendering on the server

This commit is contained in:
dtonon 2026-08-19 18:32:26 +01:00
parent 018c72021f
commit 9ab145f816

View file

@ -12,15 +12,20 @@ import type { Query } from "$lib/forum/query";
// cached briefly to absorb crawler bursts. // cached briefly to absorb crawler bursts.
const QUERY_TIMEOUT = 2500; const QUERY_TIMEOUT = 2500;
const PROFILE_TIMEOUT = 1500; const PROFILE_TIMEOUT = 1500;
const CONNECT_TIMEOUT = 1500;
const CACHE_TTL = 30_000; const CACHE_TTL = 30_000;
const DEAD_RELAY_TTL = 5 * 60_000;
const CACHE_MAX = 2000; const CACHE_MAX = 2000;
const pool = new SimplePool(); const pool = new SimplePool();
const cache = new Map<string, { at: number; result: Promise<Event[]> }>(); const cache = new Map<string, { at: number; result: Promise<Event[]> }>();
// Relays that failed to connect are skipped for a while, so a dead profile
// relay doesn't add its connect timeout to every cold page.
const deadUntil = new Map<string, number>();
// Resolve at EOSE from every relay or at the deadline, whichever comes first, // Ask every reachable relay and resolve as soon as each has answered (EOSE or
// with whatever arrived: a stalled relay must not cost the events the others // closed), or at the deadline, whichever comes first — always with whatever
// already delivered. // arrived, so one stalled relay never costs the events the others delivered.
function boundedQuery( function boundedQuery(
relays: string[], relays: string[],
filter: Filter, filter: Filter,
@ -28,24 +33,54 @@ function boundedQuery(
): Promise<Event[]> { ): Promise<Event[]> {
return new Promise((resolve) => { return new Promise((resolve) => {
const events: Event[] = []; const events: Event[] = [];
const subs: { close(): void }[] = [];
let done = false; let done = false;
let sub: { close(): void } | null = null; let pending = 0;
const finish = () => { const finish = () => {
if (done) return; if (done) return;
done = true; done = true;
clearTimeout(timer); clearTimeout(timer);
sub?.close(); for (const s of subs) {
try {
s.close();
} catch {
// Already closed
}
}
resolve(events); resolve(events);
}; };
const timer = setTimeout(finish, maxWait); const timer = setTimeout(finish, maxWait);
try {
sub = pool.subscribeMany(relays, filter, { const now = Date.now();
const live = relays.filter((url) => (deadUntil.get(url) ?? 0) <= now);
pending = live.length;
if (pending === 0) return finish();
for (const url of live) {
let settled = false;
const settle = () => {
if (settled) return;
settled = true;
if (--pending <= 0) finish();
};
pool.ensureRelay(url, { connectionTimeout: CONNECT_TIMEOUT }).then(
(relay) => {
if (done) return settle();
const sub = relay.subscribe([filter], {
onevent: (e) => events.push(e), onevent: (e) => events.push(e),
oneose: finish, oneose: () => {
sub.close();
settle();
},
onclose: settle,
}); });
if (done) sub.close(); // EOSE fired synchronously subs.push(sub);
} catch { },
finish(); () => {
deadUntil.set(url, Date.now() + DEAD_RELAY_TTL);
settle();
},
);
} }
}); });
} }