diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-09 14:28:40 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-09 14:28:40 +0200 |
| commit | 7e2d078fe1870d256ae47781bee6ac4f454edf24 (patch) | |
| tree | 05689049fd48f01ebbdb9995d5189cba15cee052 /CLAUDE.md | |
| parent | 813d18424ec57963bb56e6f40824a2db0ccce50d (diff) | |
| parent | e6f895c473a0b19e7b186889c1836d3945bc880b (diff) | |
| download | meshbay-7e2d078fe1870d256ae47781bee6ac4f454edf24.tar.gz | |
Merge branch 'fix/large-download-paths'
Concurrent-transfer limits, with the queue, the pause and the flag day.
A node now caps how many transfers it runs at once (8 downloads, 8 uploads,
node-wide) and how many one member may run in one group (2 by default,
operator-signed). Beyond that the node answers "queued" and the client waits its
turn, visibly, in the transfers panel — and a slot that frees starts whatever is
next, skipping past a member who is at their own cap rather than letting them
stall everyone behind them.
Browsing is never subject to a slot: not the poster grid, not the covers, not
opening a photo to look at it. That is structural — a transfer is what the
transfers widget shows — and the exemption is bounded rather than open, at two
files in flight per session, because an exemption with no bound is a leaseless
branch under another name.
Transfers can be cancelled, and now paused and resumed. A paused one holds
nothing: its slot goes back at once and resuming rejoins the queue at the tail.
Uploads survive the connection that started them and resume where the node
stopped, asked for inside the seal rather than on a clear message. What they
leave behind when they are abandoned is reaped, which closes a disk leak that
predates this work.
MNP 3.0 makes the lease compulsory and refuses 2.x at the handshake, with the
desktop client checking `client.minimum` before connecting so an un-updated one
says "update" instead of failing every connection in a protocol vocabulary.
Fourteen defects were found on the way, eight of them by a person clicking
Download and pasting a console — none of which 2075 tests could reach. Section
12 of ~/next/improve-downloads.md is that report, including the three this work
introduced itself and the one that turned out to be caused by an instruction to
hard-reload after each deployment.
Node suite 1209 passed, hub suite 866 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 41 |
1 files changed, 41 insertions, 0 deletions
@@ -511,6 +511,47 @@ anything that assumes one key per person. Nothing errored. When a chain degrades, check what the floor costs on every platform that will reach it +- **A service worker with nothing to do is killed, and a streaming response is + not "something to do".** Firefox terminates an idle worker after about thirty + seconds; `event.respondWith(new Response(stream))` does not extend its life + while the page is still writing to that stream. So the reader vanished + mid-file and `writable.write()` **never resolved and never rejected** — no + error, no log, no failed transfer, just a progress bar that stopped near the + end. Measured 2026-09-08 in Firefox 154, writing 1 MB every 2 s: stalled at + 17 MB after 59 s; with a 10-second ping to the worker, 40 MB in 80 s, + complete. The page pings while it writes and the worker answers, because + receiving a message is the event that resets the timer. + Three things this cost, all worth remembering. **The first stress probe wrote + 450 MB in two seconds and passed** — fast enough to hide the bug entirely, so + a probe for anything time-based has to be paced like the real thing. **The + node was innocent and three measurements proved it** (615 MB pulled whole + over MNP, three files interleaved on one connection, three concurrent worker + streams), which is exactly what made the fault unfindable: nothing was wrong + anywhere anyone looked. And **the empty console was the evidence**, not the + absence of it: `_sendAndWait` logs every timeout, so silence eliminated + everything that reports itself and left the one `await` on that path with no + bound. Every await on a download path is now bounded and says which chunk it + gave up on — an unbounded one is a freeze nobody can report + +- **Three headers decide whether a page may frame itself, and they must agree.** + The same streamed download navigates a hidden iframe to `/_mbdl/<id>`. + `frame-src` was reCAPTCHA's two origins with no `'self'`, `frame-ancestors` + was `'none'`, and `X-Frame-Options` was `DENY`. Each was fixed in turn, each + time costing a redeploy and a retest, and **all three were visible in one + `curl -I` against the deployed hub** — which is where that should have + started. `'self'`/`SAMEORIGIN` refuse every foreign origin exactly as + `'none'`/`DENY` do; what they add is this origin framing itself, which is all + the download needed. When a symptom points at a mechanism, enumerate + everything that governs that mechanism and check the set at once + +- **`encodeURIComponent` does not escape `'`, and `'` is RFC 5987's delimiter.** + `Content-Disposition: filename*=UTF-8''<value>` became unparseable for any + name with an apostrophe, so the browser named the file after the URL: 449 MB + of film arrived complete and correct, called `mtsshk9w-ohqty535`. `(`, `)` and + `*` are excluded from attr-char for the same reason. A plain ASCII + `filename=` rides alongside now, so the next surprise loses accents rather + than the name + - **Two elements each claiming `100vh - 52px`, one inside the other's padding.** `.layout` and `.page-center` both reserved the viewport below the header, and `main`'s `24px` top and bottom were added on top — a permanent 48px scrollbar |