fix(quartz): fresh subId per page in fetchAllPages (was truncating large results)

fetchAllPages reused a single subscription id across all pages
(unsubscribe + immediately re-subscribe the same id). On a real relay that
caps REQ results, the rapid same-id CLOSE→REQ races on the wire: in-flight
events from the previous page's REQ bleed into the next page's listener.
Those stale events carry a created_at above the freshly-lowered `until`, so
`match()` rejects them, the page ends with pageCount == 0, and the whole
loop breaks — silently truncating the download.

Observed against wss://wot.grapevine.network: a full kind:0 download (~3.55M
events, per a concurrent negentropy sync) stopped at 89,500. A controlled
diagnosis paging the same data with a fresh subId per page vs a shared subId
reproduced it exactly: shared stalled at ~95k with in-page duplicates and
events above `until`; fresh advanced cleanly with no duplicates. After the
fix, the real-relay fetchAllPages sails past the old stall (100k+ and
counting).

Fix: allocate the subId inside the paging loop so each page is an
independent subscription.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmSyzdmKyiz3pPxUZ8Mg8Z
This commit is contained in:
Claude
2026-07-01 00:42:33 +00:00
parent e5f43904f2
commit ed5c25e2b1
@@ -64,11 +64,17 @@ suspend fun INostrClient.fetchAllPages(
// Track how many matching events each filter has received so far.
val matchCountPerFilter = IntArray(filters.size)
val subId = newSubId()
while (true) {
coroutineContext.ensureActive()
// A fresh subscription id per page. Reusing one id across pages
// (unsubscribe + immediately re-subscribe the same id) races on the wire:
// in-flight events from the previous page's REQ bleed into the next page's
// listener. Those stale events carry a `created_at` above the new `until`,
// so `match()` rejects them, the page ends with `pageCount == 0`, and the
// whole download terminates early — silently truncating large results.
val subId = newSubId()
val pagedFilters =
if (until == null) {
filters