Failing repro: loadSubset resolves & live query marks ready while synced rows are parked behind a persisting transaction#1662
Conversation
…y while synced rows are parked behind a persisting transaction On-demand collections: `toArrayWhenReady()` resolves with `[]` for rows that `loadSubset` fetched and committed, whenever any user transaction on the collection is in state `persisting`. `commitPendingTransactions()` (collection/state.ts) parks the committed synced transaction while `hasPersistingTransaction`, but `loadSubset` still resolves and the live query still calls `markReady()`, so the read reports ready over a stale/empty snapshot. Control test (no mutation in flight) passes; the persisting case fails (receives `[]`). Verified failing on current `main`. Related: TanStack#1017, TanStack#1657. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Thanks for the excellent reproduction — this is exactly the kind of report that's easy to act on. We verified it against This is now tracked in the loadSubset/pagination RFC (#1657) as evidence item E13 with a dedicated work item (§3.8): loadSubset resolution must mean the rows are actually applied — your test is the red test for it. A maintainer will be picking up the RFC work soon, and we'd rather land this as a proper fix at the contract level within that effort than as a one-off patch — that's the whole point of the cluster analysis. We'll keep this PR open for its repro until then. |
What this is
A failing test (no fix) reproducing an on-demand
loadSubsetreadiness bug in@tanstack/db. Opened as draft so the repro runs in CI — happy to turn it into a fix PR once maintainers weigh in on the approach.packages/db/tests/query/load-subset-persisting-deferral.test.tsreceived []). Verified failing on currentmain(expected [] to deeply equal [ 'r1', 'r2' ]).The bug
In on-demand sync mode,
await loadSubset(...),collection.status === 'ready', andtoArrayWhenReady()all resolve before the rowsloadSubsetfetched are readable, whenever any user transaction on the collection is in statepersisting(an in-flight optimistic mutation).commit()is fire-and-forget:commitPendingTransactions()inpackages/db/src/collection/state.tsskips applying committed synced transactions whilehasPersistingTransaction(guard:!hasPersistingTransaction || hasTruncateSync || hasImmediateSync), leaving the rows parked inpendingSyncedTransactions. Nothing downstream is signalled, soloadSubsetresolves, the live query callsmarkReady(), and the read returns[]. The rows become readable only when the mutation settles.The test's
loadSubsetbegins/writes/commits synchronously then resolves — exactly what@tanstack/electric-db-collectiondoes inside its subscribe handler on asubset-endcontrol message beforerequestSnapshotresolves.@electric-sql/clientis not involved and not at fault (it delivers to subscribers before resolving since 1.5.20).Why it matters
Downstream (offline-first apps using
@tanstack/offline-transactions) this surfaces as silently empty/partial reads whenever a background write is in flight — e.g. duplicating a template's child rows into a new record returns nothing. Settle-window/timer workarounds cannot recover the rows, because a parked commit emits no change events until the transaction settles.Possible fixes (happy to implement)
loadSubsetshouldn't resolve until its synced transaction(s) are applied (e.g.commit()returns a promise / awhenApplied()signal). This also fixes theDeduplicatedLoadSubsetcoverage-poisoning, since tracking runs on resolution.begin({ immediate: true })(already bypasses the guard; used today for resume-metadata resets). Authoritative server rows have nothing to reconcile against.Related
loadSubsetcomposite on top.