OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room / test / lease-handoff.test.js

52 lines · javascript ref:
// Lease correctness after the split-brain fix: claim() must NEVER steal a LIVE lease (that was the
// wj62 split-brain — a booting device grabbed the lease before replicating the holder's still-valid
// one). Failover must still work: a standby claims ONLY after the holder's lease TTL-lapses.
import test from 'brittle'
import { Lease } from '../lib/lease.js'

function mockSb () {
  const kv = new Map()
  return { async get (k) { return kv.get(k) || null }, async set (k, v) { kv.set(k, v) } }
}

test('claim() refuses to steal a LIVE lease (split-brain fix)', async (t) => {
  const sb = mockSb()
  const A = new Lease(sb, { self: 'A', ttlMs: 1000 })
  const B = new Lease(sb, { self: 'B', ttlMs: 1000 })
  t.is(await A.claim(10000), 1, 'A claims the free lease → epoch 1')
  t.ok(await A.holds(10100), 'A holds it')
  t.is(await B.claim(10100), null, 'B CANNOT steal A\'s live lease')
  t.absent(await B.holds(10100), 'B does not hold')
  t.ok(await A.holds(10100), 'A still holds — no split-brain')
})

test('failover: standby claims ONLY after the holder\'s TTL lapses', async (t) => {
  const sb = mockSb()
  const A = new Lease(sb, { self: 'A', ttlMs: 1000 })
  const B = new Lease(sb, { self: 'B', ttlMs: 1000 })
  await A.claim(10000) // A holds
  t.is(await B.claim(10500), null, 'B cannot claim while A\'s lease is still live (Δ500 < ttl)')
  t.is(await B.claim(11500), 2, 'B claims epoch 2 once A\'s lease lapsed (Δ1500 ≥ ttl) — failover')
  t.ok(await B.holds(11500), 'B now holds')
  t.absent(await A.holds(11500), 'A no longer holds')
})

test('renewOrClaim: renew if holder, claim if free, never steal a live lease', async (t) => {
  const sb = mockSb()
  const A = new Lease(sb, { self: 'A', ttlMs: 1000 })
  const B = new Lease(sb, { self: 'B', ttlMs: 1000 })
  t.ok(await A.renewOrClaim(10000), 'A claims the free lease')
  t.ok(await A.renewOrClaim(10500), 'A renews its own lease')
  t.absent(await B.renewOrClaim(10600), 'B does NOT steal A\'s live lease')
  t.ok(await A.holds(10700), 'A still holds')
  t.ok(await B.renewOrClaim(11700), 'B takes over after A\'s TTL lapse')
  t.absent(await A.holds(11700), 'A handed off cleanly')
})

test('re-claim by the same holder is allowed (restart)', async (t) => {
  const sb = mockSb()
  const A = new Lease(sb, { self: 'A', ttlMs: 1000 })
  await A.claim(10000)
  t.is(await A.claim(10100), 2, 'A re-claims its own live lease (e.g. after a restart) → epoch bumps')
})