OverviewFilesCommitsIssuesDocsReleasesPeers

favela-web commits / 200d431

boot: do not gate a P2P node's readiness on a remote web service
Cause of tonight's outage, in one line: `await inbound.start()`. The inbound lens subscribes to a remote index and backfills over HTTP. Awaiting it made a LOCAL, P2P-FIRST node refuse to serve until a REMOTE WEB SERVICE answered. The full chain, because no single link was sufficient: 1. Both backends run autoupdate:true, so a publish restarts them SIMULTANEOUSLY and guarantees a window with no healthy backend. 2. Apache proxies to them with ProxyTimeout 3600, so each hung request holds a worker for up to an hour. All 18 drained; it stopped answering EVERYTHING, including ap.7rnx.net, whose backend was healthy throughout. 3. This boot fetch then went through that saturated proxy with no deadline — bare-fetch silently drops `timeout` (its Request destructures only body/method/headers/signal/agent; verified in lib/request.js) — and hung forever. So it deadlocked: the daemon could not finish booting until the proxy recovered, and the proxy could not recover while its workers were held by requests to the daemon. Only restarting Apache broke it. Every one of those three was a known, documented, unfixed item in #47. None mattered alone. The lens is an ENHANCEMENT — it fills in replies from outside this node's window. Without it the timeline still renders. So it starts in the background and reports failure rather than blocking, and a failure now says what is missing instead of being silent. ★ Deliberately NOT "bounded with a timeout". A deadline would turn a permanent hang into a slow boot, when the correct answer is that a phone, tabertron and ashatron are P2P nodes whose readiness does not depend on a web bridge at all. AbortSignal is not available under bare regardless — it is not a global, and bare-abort's default export is a fatal-abort helper, not an AbortController — so #47's suggested fix does not exist as written. NOT fixed here, and worth its own change: `await swarm.flush()` (755, 771) also gates server.listen and is unbounded. That is P2P rather than web coupling, but it is the likely reason boot is slow enough for step 1 to matter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
200d4310ce277fe78593abe692a42519b4b9bbee
by plankatron <fleeky@gmail.com> · 2026-08-25T00:36:14.000Z

Changed files (2) +21 -3

bin/favela-web.js +20 -2
……@@ -477,8 +477,26 @@ if (SPACEDUST) {
477477 console.log(JSON.stringify({ msg: 'inbound', event, ...detail }))
478478 }
479479 })
480 await inbound.start()
481 console.log(JSON.stringify({ msg: 'inbound lens', enabled: true, subjects: ROSTER.length, budgetBytes: RETENTION_BUDGET }))
480 // ⛔ NOT AWAITED. This is the inbound lens: it subscribes to a remote index and backfills over HTTP.
481 // Awaiting it made a LOCAL, P2P-FIRST node refuse to serve until a REMOTE WEB SERVICE answered — and
482 // on 2026-08-24 that is exactly what happened. The bridge saturated, this fetch had no deadline
483 // (bare-fetch silently drops `timeout`: its Request destructures only body/method/headers/signal/
484 // agent, verified in node_modules/bare-fetch/lib/request.js), and the daemon hung in boot forever
485 // while every P2P capability it owns was already available. Both backends did it simultaneously,
486 // which left the proxy with no healthy node, which kept the fetch from ever completing — a deadlock
487 // that could only be broken by restarting Apache.
488 //
489 // A phone, tabertron and ashatron are P2P nodes. Nothing on the web side may gate them serving.
490 // The lens is an ENHANCEMENT: it fills in replies from outside this node's window. Without it the
491 // timeline still renders, so it starts in the background and reports failure instead of blocking.
492 //
493 // Deliberately not "bounded with a timeout" either. A deadline would have turned a permanent hang
494 // into a slow boot, when the correct answer is that a P2P node's readiness does not depend on this
495 // at all.
496 inbound.start().then(
497 () => console.log(JSON.stringify({ msg: 'inbound lens', enabled: true, subjects: ROSTER.length, budgetBytes: RETENTION_BUDGET })),
498 (e) => console.error(JSON.stringify({ msg: 'inbound lens FAILED to start — serving anyway, replies from outside this node will be missing', error: (e && e.message) || String(e) }))
499 )
482500} else {
483501 console.log(JSON.stringify({ msg: 'inbound lens', enabled: false }))
484502}
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-web",
3 "version": "0.3.78",
3 "version": "0.3.79",
44 "description": "favela-web \u2014 the web/SSR surface of Favela, the native P2P-local, community-owned social client + appview over the self-custody atproto stack (hyper-proto-peer). One of the Favela surfaces (alongside favela-cli and favela-native). bsky is an optional POSSE export, not the home. See DESIGN.md.",
55 "type": "module",
66 "main": "bin/favela-web.js",