Skip to content

Commit 5fc2d5a

Browse files
committed
rpc: pin upstream-entry convergence within two ticks
The previous loopback-suppression test asserted that the tick immediately after a pull pushed zero entries. That contract no longer holds: the local pushRev advance was removed because it desynced from the wire-visible appliedPushRev, so the apply's rev bumps now ride back to the sender on the next pushOnce and the sender drops them via alreadyApplied(). Rewrite the test to assert the new contract: tick 1 after an upstream pull may ship redundant entries, but tick 2 is the no-op, and tick 3 stays settled. This pins that convergence is bounded at one extra round trip per upstream apply rather than becoming an unbounded ping-pong, which was the worry the previous test was guarding against in a stricter form than the system can now honor.
1 parent 9a99d16 commit 5fc2d5a

1 file changed

Lines changed: 30 additions & 10 deletions

File tree

packages/rpc/src/sync-driver.test.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -361,23 +361,43 @@ describe("sync driver — bidirectional convergence", () => {
361361
}
362362
});
363363

364-
it("an upstream entry does not get re-pushed (loopback suppression)", async () => {
364+
it("an upstream entry stops circulating within two ticks", async () => {
365+
// Before the pushRev-locality fix, the loopback suppression
366+
// advanced B's pushRev to currentRev on the apply, so the
367+
// immediate pushOnce was a no-op. After the fix, B's pushRev
368+
// stays put after the pull, so the first pushOnce after a
369+
// pull ships the apply's rev bumps back to A; A's
370+
// alreadyApplied() drops them, the push response advances B's
371+
// pushRev, and the *next* tick is the no-op. The echo is
372+
// bounded at one extra round trip and the system converges
373+
// without an unbounded ping-pong.
365374
const a = makePeer();
366375
const b = makePeer();
367376
try {
368377
const providerA = new SQLiteWorkspaceProvider(a.db, { now: () => 1 });
369378
providerA.writeFileSync("/from-a.txt", "alpha");
370379

371-
// First tick: B pulls from A.
372-
await tick(b.db, a.rpc);
380+
// Tick 1: B pulls A's write. The apply on B bumps B's rev,
381+
// so B's coalesce window contains entries; pushed reports
382+
// however many entries got coalesced (typically 1 for the
383+
// file alone, more if directory entries get touched).
384+
const first = await tick(b.db, a.rpc);
373385
expect(fileEntries(b.db)).toContain("from-a.txt");
374-
375-
// Second tick: B has nothing new to push back. If the
376-
// loopback suppression is broken, applyChanges bumped
377-
// vfs_meta.rev on the apply, and the push side would re-ship
378-
// the same entry.
379-
const result = await tick(b.db, a.rpc);
380-
expect(result.pushed).toBe(0);
386+
expect(first.pulled.applied).toBeGreaterThan(0);
387+
expect(first.pushed).toBeGreaterThanOrEqual(1);
388+
389+
// Tick 2: A's alreadyApplied() dropped the redundant entries
390+
// shipped in tick 1, and B's pushRev advanced past them. So
391+
// tick 2 has nothing to push and nothing to pull.
392+
const second = await tick(b.db, a.rpc);
393+
expect(second.pulled.applied).toBe(0);
394+
expect(second.pushed).toBe(0);
395+
396+
// Tick 3: still settled. Pins that convergence is durable,
397+
// not just "the next tick happens to be empty."
398+
const third = await tick(b.db, a.rpc);
399+
expect(third.pulled.applied).toBe(0);
400+
expect(third.pushed).toBe(0);
381401
} finally {
382402
a.close();
383403
b.close();

0 commit comments

Comments
 (0)