prerender-v0.13.0 — @harperfast/prerender
Registry walks no longer hold pending writes across an open cursor.
Harper ends any transaction open past storage_maxTransactionOpenTime (default 30s), and the two outcomes are not equivalent — the discriminator is hasPendingWrites() at the moment the monitor fires:
- pending writes → ABORTED and poisoned. Every later write or commit throws "Transaction was aborted after exceeding the open-transaction limit; split long-running work into smaller transactions" (422). Core deliberately refuses to force-commit a partial write set.
- no pending writes → committed, clock reset, not poisoned. A read-only walk survives indefinitely.
So the dangerous shape is not "a long scan" but "a cursor still open while writes are pending" — and three loops were exactly that, each able to die partway through with rows already changed:
| loop | write issued inside the cursor |
|---|---|
Sitemap.refresh prune |
RenderTarget.patch |
Sitemap.delete |
RenderTarget.delete |
RenderTarget.revalidate |
page patch + schedule put |
All three are now two-phase — collect while reading, write once the cursor has closed — via a shared util/scan.js. This is the discipline util/reconcile.js already followed and documented.
A fourth exposure had nothing to do with cursors: the sitemap creation loop drained with await lastPromise, which awaits only the most recent write and left up to 49 others in flight, so writes still spanned a monitor tick despite looking drained. It now awaits the full batch.
Notes
collectFromScancaps what it buffers (config.scan.collectCap) but always scans to completion, soexaminedis the true registry size andtruncatedreports whether the caller acted on a partial set.- Yields on rows scanned, not rows collected: a walk that collects almost nothing is the healthy case, and awaiting a cursor drains microtasks without letting timers or I/O callbacks run.
- New
config.scansection:collectCap,batchSize,yieldEvery. - Recommended upgrade for any deployment with large sitemaps — this supersedes 0.12.0, which slightly lengthened the prune loop's exposure.
reconcileSchedules is unaffected: it walks read-only and is already two-phase, so it cannot reach the abort path.
Full detail: #43