OverviewFilesCommitsIssuesDocsReleasesPeers

git-remote-punch / test / durable-connect.test.js

120 lines · javascript ref:
import test from 'brittle'
import { durableConnect } from '../lib/durable-connect.js'

// ── minimal fakes (no runtime-specific imports, runs under node or bare) ──

class Emitter {
  constructor () { this._l = new Map() }
  on (e, fn) { if (!this._l.has(e)) this._l.set(e, new Set()); this._l.get(e).add(fn); return this }
  off (e, fn) { if (this._l.has(e)) this._l.get(e).delete(fn); return this }
  emit (e, ...a) { if (this._l.has(e)) for (const fn of [...this._l.get(e)]) fn(...a) }
}

class FakeSwarm extends Emitter {
  constructor () { super(); this.joined = [] }
  join (topic, opts) { this.joined.push({ topic, opts }) }
}

class FakeConn extends Emitter {
  constructor (id) { super(); this.id = id; this.destroyed = false }
  destroy () { this.destroyed = true }
}

const TOPIC = Buffer.alloc(32, 7)
const later = (ms, fn) => setTimeout(fn, ms)

// The OLD connect model this transport used — the control. Reproduces the bug:
// swarm.once('connection') + a wall-clock reject timer that races the DHT.
function legacyConnect (swarm, topic, { timeout }) {
  return new Promise((resolve, reject) => {
    const t = setTimeout(() => reject(new Error('Connection timeout')), timeout)
    let done = false
    const onc = (conn) => { if (done) return; done = true; clearTimeout(t); swarm.off('connection', onc); resolve(conn) }
    swarm.on('connection', onc)
    swarm.join(topic, { server: false, client: true })
  })
}

// ── CONTROL: the old model FAILS a connection that arrives after the deadline ──
test('control — legacy once+timer rejects a late connection (reproduces the bug)', async (t) => {
  const swarm = new FakeSwarm()
  const p = legacyConnect(swarm, TOPIC, { timeout: 50 })
  later(150, () => swarm.emit('connection', new FakeConn('late'))) // arrives after 50ms deadline
  await t.exception(p, /Connection timeout/, 'a live-but-late peer is lost to the wall-clock timer')
})

// ── MECHANISM: durableConnect resolves the SAME late connection ──
test('mechanism — durableConnect resolves a late connection (no aggressive deadline)', async (t) => {
  const swarm = new FakeSwarm()
  const conn = new FakeConn('late')
  const p = durableConnect(swarm, TOPIC, {}) // no fallbackAfter → waits
  later(150, () => swarm.emit('connection', conn))
  const got = await p
  t.is(got, conn, 'the late connection wins — it just waited instead of dying')
  t.is(swarm.joined.length, 1, 'joined the topic as client once')
  t.is(swarm.joined[0].opts.client, true, 'client:true')
})

// ── THE ASSERTION: concurrency starves losers under the timer; durable fixes it ──
// Models the observed ashatron failure: N concurrent `git fetch punch://`, each
// its own swarm, sharing a contended connect budget so connections arrive
// STAGGERED. Under the 15s-style per-connector deadline the ones behind the
// contention exceed it and fail EMPTY; durableConnect lets every one land.
test('repro — 5 concurrent pulls, staggered connects: legacy starves losers, durable lands all', async (t) => {
  const N = 5
  const DEADLINE = 100
  // Staggered arrival times (a shared, contended connect budget serializes them).
  const arrivals = [40, 90, 140, 190, 240] // 2 within the deadline, 3 past it

  // Legacy: each connector has its own swarm + its own deadline timer.
  const legacyResults = await Promise.allSettled(arrivals.map((ms) => {
    const swarm = new FakeSwarm()
    const p = legacyConnect(swarm, TOPIC, { timeout: DEADLINE })
    later(ms, () => swarm.emit('connection', new FakeConn('l' + ms)))
    return p
  }))
  const legacyOk = legacyResults.filter((r) => r.status === 'fulfilled').length
  t.is(legacyOk, 2, 'legacy: only the 2 that beat the deadline complete — 3 starve EMPTY (the bug)')

  // Durable: same staggered arrivals, no aggressive deadline.
  const durableResults = await Promise.allSettled(arrivals.map((ms) => {
    const swarm = new FakeSwarm()
    const p = durableConnect(swarm, TOPIC, {})
    later(ms, () => swarm.emit('connection', new FakeConn('d' + ms)))
    return p
  }))
  const durableOk = durableResults.filter((r) => r.status === 'fulfilled').length
  t.is(durableOk, N, 'durable: ALL 5 land — the losers waited out the contention instead of failing')
})

// ── reconcile across connections: discard a bad peer, take the next ──
test('reconcile — a connection that fails validate is discarded, the next is taken', async (t) => {
  const swarm = new FakeSwarm()
  const bad = new FakeConn('bad')
  const good = new FakeConn('good')
  const p = durableConnect(swarm, TOPIC, {
    validate: (conn) => conn.id === 'good'
  })
  later(30, () => swarm.emit('connection', bad))
  later(80, () => swarm.emit('connection', good))
  const got = await p
  t.is(got, good, 'resolved with the peer that passed validate')
  t.ok(bad.destroyed, 'the failing peer was destroyed, not handed to the caller')
})

// ── caller cancellation ──
test('abort — rejects on AbortSignal, never on a built-in deadline', async (t) => {
  const swarm = new FakeSwarm()
  const ac = new AbortController()
  const p = durableConnect(swarm, TOPIC, { signal: ac.signal })
  later(20, () => ac.abort())
  await t.exception(p, /aborted/, 'aborts only when the caller asks')
})

// ── generous no-progress escape (for the HTTPS fallback), NOT a connect race ──
test('fallbackAfter — rejects after the generous bound when NO peer is reached', async (t) => {
  const swarm = new FakeSwarm()
  const p = durableConnect(swarm, TOPIC, { fallbackAfter: 60 }) // nothing ever connects
  await t.exception(p, /no peer reached/, 'offline server falls back instead of hanging forever')
})