fix(relay): never call a reserve cell's hosts stranded when it goes existing-only (#30609)

A reserve cell keeps no activity rows and its ledger stamps last_activity_at, so the
stranded rule read its connected hosts as unserved and re-placed them.
This commit is contained in:
Jinwoo Hong
2026-10-19 03:33:15 -04:00
committed by GitHub
parent 7dcb7d4439
commit cb82bc80da
2 changed files with 26 additions and 0 deletions
@@ -228,6 +228,27 @@ describe('reserve-mode census', () => {
await restore()
})
it('keeps a connected host pinned when its reserve cell goes existing-only after a ledger write', async () => {
let now = 1_000
const store = await setup(() => now)
const identity = { userId: 'user-a', relayHostId: 'host000000000001' }
const first = await store.assign(identity)
await setReserve(store, [first.cellId])
// The reserve ledger stamps last_activity_at and the cell keeps no activity rows.
now += 5 * 60_000
await database!.query('DELETE FROM relay_assignment_activity_leases WHERE user_id = ?', [identity.userId])
await database!.query('UPDATE relay_assignments SET last_activity_at = ? WHERE user_id = ?', [now, identity.userId])
await store.setCellAdmissionState(first.cellId, 'existing-only')
now += 2 * 60_000
expect(await store.assign(identity)).toMatchObject({
cellId: first.cellId,
assignmentEpoch: first.assignmentEpoch
})
const { unknown, restore } = await unknownSet(() => now)
expect((await unknown.assign(identity)).assignmentEpoch).toBe(first.assignmentEpoch)
await restore()
})
// Only the first reserve-set read is held; it is released (or failed) by the test.
function holdFirstRead(options: { fail?: boolean } = {}) {
const query = database!.query.bind(database!)
+5
View File
@@ -1290,6 +1290,11 @@ export class RelayAssignmentStore {
if (state !== 'existing-only') {
return false
}
// A reserve cell keeps no activity rows and its ledger stamps last_activity_at, so zero
// leases proves nothing; a dead one is left to the dead-reserve path and its fence.
if (await this.mayBeReserveCell(text(existing, 'cell_id'), transaction)) {
return false
}
const liveLeases = (
await transaction.query(
`SELECT COUNT(*) AS live FROM relay_assignment_activity_leases