diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/search-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/search-page.js | 38 |
1 files changed, 33 insertions, 5 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js index 593760c..679b3f9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js @@ -27,7 +27,22 @@ const BATCH_SIZE = 3; // good. grenet (one shared group) never hit it; cbesson (many) always did. const MAX_POOL_SIZE = 12; const DEBOUNCE_MS = 200; -const SEARCH_TIMEOUT = 10000; +// How long a connection attempt may make **no progress**, not how long it may +// take. Search used a flat 10 s for the whole of `connect()`, which is the hub +// round trip, ICE gathering (capped at 4 s in transport.js), DTLS, the +// DataChannel opening and the handshake's own round trips. Opening the same +// group from the sidebar has no such deadline and gets the transport's own 30 s, +// so a link slow enough to need twelve seconds — a phone on 4G — failed here and +// worked from there, for the same work against the same node. +// +// A node that is not answering produces no progress event and still fails in +// `SEARCH_STALL_MS`, which is what keeps the fan-out bounded: `fetchAllIndexes` +// goes in batches of three and waits for the slowest of each, so the number that +// matters for a page full of unreachable groups is this one, unchanged. +// `SEARCH_MAX_MS` bounds the other case — a node that answers ICE and then stops +// — because a deadline that only ever resets has none. +const SEARCH_STALL_MS = 10000; +const SEARCH_MAX_MS = 30000; const SEARCH_VIDEO_ROOT = '__search__'; const SEARCH_AUDIO_ROOT = '__search__'; const SEARCH_PHOTO_ROOTS = ['__search_photos__']; @@ -55,20 +70,33 @@ async function connectToGroup(hubBase, groupId, token, bundleKey, username, user for (const n of nodesData.nodes) { const transport = new window.MeshBayTransport(hubBase, live); transport.onNeedToken = async () => (await ensureFreshToken()) || token; - let timer; + let stallTimer, capTimer; + const stopTimers = () => { + clearTimeout(stallTimer); + clearTimeout(capTimer); + transport.onConnectProgress = null; + }; try { const ack = await Promise.race([ transport.connect( n.node_id, live, groupId, null, null, bundleKey, username, userId, null), new Promise((_, reject) => { - timer = setTimeout(() => reject(new Error('Connection timeout')), SEARCH_TIMEOUT); + const giveUp = () => reject(new Error('Connection timeout')); + stallTimer = setTimeout(giveUp, SEARCH_STALL_MS); + capTimer = setTimeout(giveUp, SEARCH_MAX_MS); + // Each step the transport reports — the peer answering ICE, the + // channel opening — buys another window, never more than the cap. + transport.onConnectProgress = () => { + clearTimeout(stallTimer); + stallTimer = setTimeout(giveUp, SEARCH_STALL_MS); + }; }), ]); - clearTimeout(timer); + stopTimers(); return { transport, ack }; } catch (e) { - clearTimeout(timer); + stopTimers(); lastErr = e; try { transport.close(); } catch {} if (e.reason && e.reason !== 'not_hosted') throw e; |