<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-hub/tests/test_search_fanout.py, branch main</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=main</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-25T08:23:05Z</updated>
<entry>
<title>test(hub): read the search code wherever it is split</title>
<updated>2026-09-25T08:23:05Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-25T08:23:05Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=f1ed3e6be452f6211cee8db1afef8b56473a04db'/>
<id>urn:sha1:f1ed3e6be452f6211cee8db1afef8b56473a04db</id>
<content type='text'>
The Node harnesses take one file or several, and the tests that lift
connectToGroup and the pool out of search-page.js as text take them from
spa_source, which also reads connection-pool.js once it exists.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): Search reaches each group with one offer, and a busy hub is not a dead node</title>
<updated>2026-09-23T22:10:41Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-23T22:10:41Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=059eb0318daf27d98bd1c9532705ff406c0a10f8'/>
<id>urn:sha1:059eb0318daf27d98bd1c9532705ff406c0a10f8</id>
<content type='text'>
The other half of the 4G failure. The page negotiated every group twice: the
sweep opened a connection, read the index and closed it, then the warm-up
opened the same group again. The sweep, the warm-up and the tiles each had a
concurrency ceiling of their own, and together they went past what the hub
admits per account. Whatever the hub refused was then reported as "node
unreachable" and remembered as down, which put that group last next time.

- Every connection goes through ConnectionPool, which holds the page's one
  ceiling (six at once, sized for twenty groups on a phone) and keeps what
  the sweep opened for the tiles. A visit costs one offer per group. A
  refresh costs none for a connection that answers a four-second ping, and
  a connection that died while the phone slept is replaced, not waited on.
- A connection whose index is being read is held against eviction. With
  more groups than the pool keeps, it was otherwise the least recently used
  one.
- Negotiations still under way when the page closes close what they get,
  and a sweep cut short that way remembers nobody as down.
- transport.js sends an offer again on 429, 502 or 503, honouring
  Retry-After, with jittered waits of about twenty seconds at worst. Search
  counts each retry as progress. A 404, 403 or 504 still fails at once, so
  a dead node costs no time.

The fan-out tests assumed a ceiling of three and were re-measured: four dead
groups of twelve now hold nothing back, even on a first visit. The pool and
the retry run as shipped code, lifted as text, against a fake clock. Each
guard was checked by removing it and seeing its test fail.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): a group whose node is down is no longer something the reader waits for</title>
<updated>2026-09-18T21:38:39Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-18T21:38:39Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=345a59fc128e5e4f253c277c8cee69a2b1062a4f'/>
<id>urn:sha1:345a59fc128e5e4f253c277c8cee69a2b1062a4f</id>
<content type='text'>
A node is a machine in somebody's house, so with a handful of groups one is
always off. Search treated that as the exception and charged the reader for it.

Measured on twelve groups against a virtual clock, a live group answering in
200 ms and a dead one taking the full deadline: one node down in the first batch
put a blank page and a progress bar in front of the reader for **ten seconds**,
while two groups of that same batch had answered in two hundred milliseconds and
nine others had not been dialled at all. Four down, spread out — the shape a real
reader has — was ten seconds to the first result and **forty** to the last.

Two causes, neither of them the connection deadline. Results were drawn once a
batch was complete rather than as each arrived, so an index already in hand waited
on a node that was not answering. And the batches were sequential, so a dead group
did not merely cost its own deadline, it postponed every group behind it.

So the three is a ceiling on concurrency and never a batch — `inFlight` starts the
next group the moment one ends — and each index is drawn when it lands.

That alone is not enough, and the tests say where it stops: a ceiling still lets
silent nodes hold every place at once, and with four of twelve down the last three
live groups still waited out a deadline. So the browser remembers which groups
were silent and dials them last, which puts all eight on screen in 600 ms. The
list is advisory and rewritten from what each sweep saw: a private window, storage
that refuses, a first visit or a node that has come back all fall through to the
hub's own order, cost one sweep, and correct themselves.

The "n groups unreachable" line also waited for the sweep to finish, which is the
one moment it is no longer needed. It now appears as they are found.

First result, before and after, twelve groups: one down 10 s → 200 ms; four down
10 s → 200 ms with every reachable group on screen by 600 ms; three down and
listed first 10 s → 200 ms from the second visit on.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
