Merge origin/main into brennanb2025/orchestration-mail-holes

This commit is contained in:
Brennan Benson
2026-11-13 15:44:59 -08:00
5241 changed files with 51677 additions and 169752 deletions
@@ -1,28 +1,16 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import {
CloudSqlRolloutLease,
LEASE_TTL_MS,
LeaseConflict,
LeaseUnreadable
} from './storage-lease.mjs'
import { CloudSqlRolloutLease, LeaseConflict, LeaseUnreadable } from './storage-lease.mjs'
const BUCKET = 'onorca-cloud-terraform-state'
const OBJECT = 'terraform/state/cloud-sql-rollout/production.lock'
const NOW = 1_756_000_000_000
/** Model real GCS generation increments, ifGenerationMatch and 412 conflicts; faults inject failures. */
function fakeStorage({ object = null, faults = [] } = {}) {
const state = { object, requests: [] }
function fakeStorage({ object = null } = {}) {
const state = { object }
const fetcher = async (rawUrl, init = {}) => {
const url = new URL(rawUrl)
const method = init.method ?? 'GET'
const record = { method, url, path: url.pathname, search: url.searchParams }
state.requests.push(record)
const fault = faults.find((candidate) => candidate.when(record))
if (fault) {
return json(fault.status, fault.body ?? {})
}
if (url.pathname.startsWith('/upload/')) {
const want = url.searchParams.get('ifGenerationMatch')
@@ -63,15 +51,10 @@ function json(status, body) {
}
}
function storedRecord({
holderKey,
expiresAt,
repository = 'stablyai/orca',
workflow = 'Deploy Relay Production'
}) {
function storedRecord({ holderKey, expiresAt }) {
return {
repository,
workflow,
repository: 'stablyai/orca',
workflow: 'Deploy Relay Production',
run_id: '9001',
run_url: 'https://github.com/stablyai/orca/actions/runs/9001',
run_attempt: '1',
@@ -81,14 +64,14 @@ function storedRecord({
}
}
function leaseFor(storage, { warn = () => {} } = {}) {
function leaseFor(storage) {
return new CloudSqlRolloutLease({
bucket: BUCKET,
objectName: OBJECT,
accessToken: 'test-token',
fetcher: storage.fetcher,
now: () => NOW,
warn
warn: () => {}
})
}
@@ -114,77 +97,24 @@ test('refuses a live lease held by another run and never writes', async () => {
.catch((thrown) => thrown)
assert.ok(error instanceof LeaseConflict)
assert.equal(error.holder.repository, 'stablyai/orca')
assert.equal(error.holder.run_url, 'https://github.com/stablyai/orca/actions/runs/9001')
assert.equal(
storage.state.requests.filter((request) => request.method !== 'GET').length,
0,
'a foreign live lease must not be written'
)
assert.equal(storage.state.object.generation, '1500')
})
test('re-enters a live lease this run already holds and extends it', async () => {
const storage = fakeStorage({
object: {
generation: '1500',
body: storedRecord({ holderKey: HOLDER.holderKey, expiresAt: NOW + 60_000 })
}
})
const warnings = []
const claim = await leaseFor(storage, { warn: (message) => warnings.push(message) }).acquire(
HOLDER
)
assert.equal(claim.state, 'reentrant')
assert.deepEqual(warnings, [], 're-entering our own lease is not a takeover')
assert.equal(claim.record.acquired_at, NOW - 60_000, 'original acquisition time is preserved')
assert.equal(claim.record.expires_at, NOW + LEASE_TTL_MS)
const upload = storage.state.requests.find((request) => request.path.startsWith('/upload/'))
assert.equal(upload.search.get('ifGenerationMatch'), '1500')
assert.equal(storage.state.object.generation, '1501')
})
test('takes over an expired lease and warns naming the stale holder', async () => {
test('takes over an expired lease', async () => {
const storage = fakeStorage({
object: {
generation: '1500',
body: storedRecord({
holderKey: 'stablyai/orca/9001',
expiresAt: NOW - 1,
repository: 'stablyai/orca',
workflow: 'Deploy Relay Production Capacity'
expiresAt: NOW - 1
})
}
})
const warnings = []
const claim = await leaseFor(storage, { warn: (message) => warnings.push(message) }).acquire(
HOLDER
)
await leaseFor(storage).acquire(HOLDER)
assert.equal(claim.state, 'takeover')
assert.equal(warnings.length, 1)
assert.match(warnings[0], /stablyai\/orca/)
assert.match(warnings[0], /Deploy Relay Production Capacity/)
assert.match(warnings[0], /actions\/runs\/9001/)
const upload = storage.state.requests.find((request) => request.path.startsWith('/upload/'))
assert.equal(upload.search.get('ifGenerationMatch'), '1500')
assert.equal(storage.state.object.body.holder_key, HOLDER.holderKey)
})
test('releases with a generation match and leaves the object gone', async () => {
const storage = fakeStorage()
const lease = leaseFor(storage)
const claim = await lease.acquire(HOLDER)
const released = await lease.release(HOLDER.holderKey)
assert.deepEqual(released, { released: true, generation: claim.generation })
const remove = storage.state.requests.find((request) => request.method === 'DELETE')
assert.equal(remove.search.get('ifGenerationMatch'), claim.generation)
assert.equal(storage.state.object, null)
})
test('refuses to release a lease another run now holds', async () => {
const storage = fakeStorage({
object: {
@@ -212,17 +142,3 @@ test('treats an unreadable record as held, not free', async () => {
assert.ok(error instanceof LeaseUnreadable)
assert.equal(storage.state.object.generation, '1500')
})
test('renewal rewrites only expires_at on the observed generation', async () => {
const storage = fakeStorage()
const lease = leaseFor(storage)
await lease.acquire(HOLDER)
storage.state.object.body.expires_at = NOW - 1
const renewed = await lease.renew(HOLDER.holderKey)
assert.equal(renewed.renewed, true)
assert.equal(storage.state.object.body.expires_at, NOW + LEASE_TTL_MS)
assert.equal(storage.state.object.body.acquired_at, NOW)
assert.equal(storage.state.object.body.holder_key, HOLDER.holderKey)
})
+1 -2
View File
@@ -53,7 +53,6 @@ on:
- 'src/shared/computer-use-*.ts'
- 'tests/e2e/computer-linux.e2e.ts'
- 'tests/e2e/computer-windows.e2e.ts'
- 'tests/e2e/computer-windows-store.e2e.ts'
- 'tests/e2e/computer-windows-background.e2e.ts'
- 'tests/e2e/helpers/windows-semantic-controls-fixture.*'
- 'tests/e2e/helpers/computer-cli-driver.ts'
@@ -153,4 +152,4 @@ jobs:
- run: pnpm verify:computer-native
- run: pnpm build:cli
- run: pnpm run build:electron-vite:parallel
- run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-store.e2e.ts tests/e2e/computer-windows-background.e2e.ts
- run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-background.e2e.ts
@@ -38,6 +38,8 @@ jobs:
mac-native-owner-smoke:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: macos-15
env:
ORCA_BACKGROUND_LAUNCH: '1'
timeout-minutes: 180
permissions:
contents: read
@@ -1,6 +1,6 @@
import { afterEach, expect, it } from 'vitest'
import type { CloudDatabase as PushDatabase } from '@orca-cloud/cloud-database/cloud-database'
import { DELIVERY_LEASE_MS, DurablePushStore } from './durable-push-store.js'
import { DurablePushStore } from './durable-push-store.js'
import {
cleanupDurablePushFixtures,
@@ -63,20 +63,6 @@ it.skipIf(!durablePushTestDatabaseUrl)(
}
)
it('leases exactly one delivery per device across concurrent claimers', async () => {
const { db, store, clock, advance } = await fixture()
for (let seq = 1; seq <= 5; seq++) {
await store.accept('host', 'phone', notification(seq))
advance(1)
}
const claims = await Promise.all(
Array.from({ length: 8 }, () => new DurablePushStore(db, clock).claim())
)
const leased = claims.filter((claim) => claim !== null)
expect(leased).toHaveLength(1)
expect(leased[0]?.notification.notificationSeq).toBe(1)
})
it.skipIf(!durablePushTestDatabaseUrl)(
'does not lease the row behind a head another claimer holds',
async () => {
@@ -138,21 +124,3 @@ it.skipIf(!durablePushTestDatabaseUrl)(
expect((await first)?.notification.notificationSeq).toBe(1)
}
)
// Only the TTL term keeps the candidate scan off an unpruned backlog; expires_at is not indexed first.
it('frees a device held by a crashed worker once its lease lapses', async () => {
const { db, store, clock, advance } = await fixture()
await store.accept('host', 'phone', notification(1))
advance(1)
await store.accept('host', 'phone', notification(2))
const crashed = (await new DurablePushStore(db, clock).claim())!
advance(DELIVERY_LEASE_MS - 1)
expect(await store.claim()).toBeNull()
advance(1)
const reclaimed = (await store.claim())!
expect(reclaimed.id).toBe(crashed.id)
expect(reclaimed.attempts).toBe(2)
await store.finish(reclaimed)
expect((await store.claim())?.notification.notificationSeq).toBe(2)
})
@@ -1,9 +1,7 @@
import { afterEach, describe, expect, it } from 'vitest'
import { openPushDatabase } from '../database/push-database.js'
import { DurablePushStore, DELIVERY_LEASE_MS } from './durable-push-store.js'
import {
cleanupDurablePushFixtures,
durablePushTestDatabaseUrl,
fixture,
notification
} from './durable-push-store.test-fixture.js'
@@ -11,20 +9,6 @@ import {
afterEach(cleanupDurablePushFixtures)
describe('durable push acceptance', () => {
it('counts a logical event once across phones and separates the 300/15min dismissal budget', async () => {
const { store, advance } = await fixture()
for (let i = 0; i < 300; i++) {
expect(await store.accept('host', 'phone1', notification(i))).toBe('queued')
expect(await store.accept('host', 'phone2', notification(i))).toBe('queued')
expect(await store.accept('host', 'phone1', notification(i, 'dismiss'))).toBe('queued')
}
expect(await store.accept('host', 'phone1', notification(300))).toBe('rate_limited')
expect(await store.accept('host', 'phone1', notification(300, 'dismiss'))).toBe('rate_limited')
expect(await store.accept('another-host', 'phone3', notification(300))).toBe('queued')
advance(15 * 60_000)
expect(await store.accept('host', 'phone1', notification(301))).toBe('queued')
})
it('queues one delivery per event and recovers work across service instances', async () => {
const { db, store, clock, advance } = await fixture()
await store.accept('host', 'phone', notification(1))
@@ -32,16 +16,6 @@ describe('durable push acceptance', () => {
await restarted.accept('host', 'phone', notification(1))
advance(1)
await restarted.accept('host', 'phone', notification(2))
const rows = await db.query(
"SELECT payload_json, due_at, created_at FROM push_delivery_batches WHERE registration_id = ? AND state = 'pending' ORDER BY created_at, batch_id",
['phone']
)
expect(rows).toHaveLength(2)
expect(rows.map((row) => JSON.parse(String(row.payload_json)))).toEqual([
notification(1),
notification(2)
])
expect(rows.every((row) => Number(row.due_at) >= Number(row.created_at))).toBe(true)
const delivery = await restarted.claim()
expect(delivery?.notification.notificationSeq).toBe(1)
expect(await store.claim()).toBeNull()
@@ -93,18 +67,9 @@ describe('durable push acceptance', () => {
const { db, store } = await fixture()
await db.query('ALTER TABLE push_delivery_batches RENAME TO push_delivery_batches_unavailable')
try {
const databaseUrl = durablePushTestDatabaseUrl
if (databaseUrl) {
const concurrent = await openPushDatabase({ databaseUrl, dataDir: '' })
try {
await expect(
concurrent.query('SELECT COUNT(*) FROM push_delivery_batches')
).resolves.toHaveLength(1)
} finally {
await concurrent.close()
}
}
await expect(store.accept('host', 'phone', notification(1))).rejects.toThrow()
await expect(store.accept('host', 'phone', notification(1))).rejects.toThrow(
/push_delivery_batches/
)
expect(await db.query('SELECT * FROM push_events')).toEqual([])
expect(await db.query('SELECT * FROM push_event_recipients')).toEqual([])
} finally {
@@ -160,38 +125,3 @@ it('does not resurrect an in-flight alert after a dismissal and transient provid
expect(await store.claim()).toBeNull()
expect(await store.pendingCount('phone')).toBe(0)
})
it('fences late renew and finish after an expired claim is dismissed', async () => {
const { db, store, advance } = await fixture()
const alert = notification(1)
await store.accept('host', 'phone', alert)
const stale = (await store.claim())!
advance(DELIVERY_LEASE_MS)
await store.accept('host', 'phone', {
...notification(2, 'dismiss'),
notificationId: alert.notificationId
})
const read = async () =>
(
await db.query(
'SELECT state, payload_json, lease_until FROM push_delivery_batches WHERE batch_id = ?',
[stale.id]
)
)[0]
expect(await read()).toBeUndefined()
await store.renew(stale)
await store.finish(stale, 1000)
await store.finish(stale)
expect(await read()).toBeUndefined()
const dismissal = (await store.claim())!
expect(dismissal.notification.kind).toBe('dismiss')
await store.finish(dismissal)
await store.accept('host', 'phone', notification(3))
const fresh = (await store.claim())!
await store.finish(fresh, 1000)
advance(1000)
const retry = (await store.claim())!
expect(retry.id).toBe(fresh.id)
await store.finish(retry)
expect(await store.claim()).toBeNull()
})
@@ -13,7 +13,6 @@ import { pauseAfter, within } from './durable-push-store.test-fixture.js'
const cleanups: (() => Promise<void>)[] = []
afterEach(async () => {
for (const cleanup of cleanups.splice(0)) await cleanup()
vi.useRealTimers()
vi.restoreAllMocks()
})
const note = (seq: number, overrides: Partial<PushNotification> = {}): PushNotification => ({
@@ -42,9 +41,8 @@ async function fixture() {
const send = vi.fn(async (_delivery: PushDelivery): Promise<PushProviderOutcome> => ({
status: 'sent'
}))
const onRetry = vi.fn()
const dispatcher = new PushDispatcher({ devices, fcm: { send } as never })
const worker = new DurablePushWorker(store, dispatcher, { now, onRetry })
const worker = new DurablePushWorker(store, dispatcher, { now })
cleanups.push(async () => {
await worker.stop()
await db.close()
@@ -57,7 +55,6 @@ async function fixture() {
worker,
dispatcher,
send,
onRetry,
now,
registrationId: device.registrationId,
accept: (notification: PushNotification) =>
@@ -102,7 +99,6 @@ it('stops queued claim admissions without leasing the remaining device', async (
paused.release()
await within(Promise.all([pending, stopping]), 2_000)
expect(h.send).toHaveBeenCalledOnce()
expect(h.send.mock.calls[0]?.[0].orca.notificationSeq).toBe(1)
const [remaining] = await h.db.query(
'SELECT attempts, lease_until FROM push_delivery_batches WHERE registration_id = ?',
[other.registrationId]
@@ -26,18 +26,10 @@ describe('push host challenge store', () => {
await database.close()
})
it('refuses a wrong host: the box will not open and a foreign proof will not match', async () => {
it('rejects a proof issued for another challenge', async () => {
const owner = createPushHostKeypair(6)
const intruder = createPushHostKeypair(7)
const ownerChallenge = await store.issue(hostPublicKeyB64(owner))
expect(
answerPushHostChallenge(ownerChallenge!, {
gatewayOrigin: GATEWAY_ORIGIN,
keypair: intruder,
now: () => clock
})
).toBeNull()
const intruderChallenge = await store.issue(hostPublicKeyB64(intruder))
const intruderProof = answerPushHostChallenge(intruderChallenge!, {
gatewayOrigin: GATEWAY_ORIGIN,
@@ -19,11 +19,6 @@ async function concurrentSessions(db: PushDatabase) {
sessions.map((session) => store.resolve(session.sessionToken))
)
expect(decisions.filter((decision) => decision.ok)).toHaveLength(1)
const [row] = await db.query(
'SELECT COUNT(*) AS count FROM push_sessions WHERE host_fingerprint = ?',
[host]
)
expect(Number(row?.count)).toBe(1)
} finally {
await db.query('DELETE FROM push_sessions WHERE host_fingerprint = ?', [host])
}
@@ -1,36 +0,0 @@
import { afterEach, expect, it } from 'vitest'
import { createPushServerHarness, notification } from './push-server-harness.test-fixture.js'
import { createPushHostKeypair } from '../host-auth/host-challenge-answering.test-fixture.js'
const harnesses: Awaited<ReturnType<typeof createPushServerHarness>>[] = []
afterEach(async () => {
await Promise.all(harnesses.splice(0).map((h) => h.close()))
})
it('returns queued for concurrent retries without double quota or delivery', async () => {
const h = await createPushServerHarness()
harnesses.push(h)
const token = await h.signIn(createPushHostKeypair(2))
const registrationId = await h.registerAndroid(token)
const body = { v: 1, registrationIds: [registrationId], notification: notification() }
const responses = await Promise.all(
Array.from({ length: 10 }, () => h.post('/v1/send', body, token))
)
for (const response of responses)
expect(await response.json()).toEqual({ results: [{ registrationId, status: 'queued' }] })
expect(await h.server.deliveryStore.pendingCount(registrationId)).toBe(1)
await h.flushDeliveries()
await h.post('/v1/send', body, token)
await h.flushDeliveries()
expect(h.fcmRequests).toHaveLength(1)
expect(JSON.parse(h.fcmRequests[0]!.body).message.data.coalescedCount).toBeUndefined()
expect(
Number((await h.database.query('SELECT COUNT(*) AS count FROM push_events'))[0]?.count)
).toBe(1)
await h.post(
'/v1/send',
{ ...body, notification: notification({ notificationEpoch: 'new-epoch' }) },
token
)
await h.flushDeliveries()
expect(h.fcmRequests).toHaveLength(2)
})
@@ -2,7 +2,6 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { createPushHostKeypair } from '../host-auth/host-challenge-answering.test-fixture.js'
import {
createPushServerHarness,
FCM_TOKEN,
notification
} from './push-server-harness.test-fixture.js'
@@ -34,12 +33,6 @@ describe('push gateway send route', () => {
})
await harness.flushDeliveries()
expect(harness.fcmRequests).toHaveLength(1)
expect(JSON.parse(harness.fcmRequests[0]!.body)).toMatchObject({
message: {
token: FCM_TOKEN,
data: { title: 'Agent needs input', tag: expect.stringMatching(/^[a-f0-9]{64}$/) }
}
})
const afterDeath = await harness.post(
'/v1/send',
@@ -47,11 +40,6 @@ describe('push gateway send route', () => {
sessionToken
)
expect(await afterDeath.json()).toEqual({ results: [{ registrationId, status: 'dead' }] })
const listed = await harness.authorized('/v1/devices', {}, sessionToken)
expect(await listed.json()).toEqual({
devices: [{ registrationId, deviceId: 'device-1', platform: 'android', dead: true }]
})
})
it('reports an error for a registration the host does not own', async () => {
@@ -36,7 +36,6 @@ it('delivers again after a topic error without re-registering the phone', async
expect(await sent.json()).toEqual({ results: [{ registrationId, status: 'queued' }] })
await h.flushDeliveries()
if (seq === 1) {
expect(h.server.observability.consume().delivery_error).toBe(1)
h.setApnsResponse({ status: 200, body: '' })
}
}
@@ -67,11 +67,10 @@ describe('fcm client', () => {
error: { status: 'INVALID_ARGUMENT', message: 'Invalid value at message.android.ttl' }
})
})
await expect(unnamed.client.send(delivery(), { token: TOKEN })).resolves.toEqual({
await expect(unnamed.client.send(delivery(), { token: TOKEN })).resolves.toMatchObject({
status: 'error',
reason: 'INVALID_ARGUMENT',
retryable: false,
retryAfterMs: 10000
retryable: false
})
})
})
@@ -12,7 +12,7 @@ const ERASER = 'https://erasure.test.invalid'
const BODY = JSON.stringify({ relayHostId: 'AAAAAAAAAAAAAAAA' })
const JTI = 'jti-0123456789abcdef'
type Sign = { key?: string; issuer?: string; op?: AdminOp; jti?: unknown; body?: string; iat?: number; exp?: number }
type Sign = { key?: string; issuer?: string; op?: AdminOp; jti?: unknown; body?: string }
async function sign(opts: Sign = {}): Promise<string> {
const claims: Record<string, unknown> = {
@@ -26,8 +26,8 @@ async function sign(opts: Sign = {}): Promise<string> {
.setProtectedHeader({ alg: 'HS256' })
.setIssuer(opts.issuer ?? ISSUER)
.setAudience('orca-relay-edge-admin')
.setIssuedAt(opts.iat ?? now)
.setExpirationTime(opts.exp ?? now + 60)
.setIssuedAt(now)
.setExpirationTime(now + 60)
.sign(new TextEncoder().encode(opts.key ?? KEY))
}
@@ -45,24 +45,19 @@ const baseEnv = { ADMIN_SIGNING_KEY: KEY, ADMIN_ISSUER: ISSUER } as Env
describe('admin token verify (jti)', () => {
it('still binds op, body, key and age', async () => {
const now = Math.floor(Date.now() / 1000)
it('binds the operation and exact body', async () => {
expect(await verifyWith(await sign({ jti: JTI, op: 'erase' }), KEY, ISSUER, 'drain', raw())).toBeNull()
expect(await verifyWith(await sign({ jti: JTI, body: '{}' }), KEY, ISSUER, 'drain', raw())).toBeNull()
expect(await verifyWith(await sign({ jti: JTI, key: NEXT }), KEY, ISSUER, 'drain', raw())).toBeNull()
expect(await verifyWith(await sign({ jti: JTI, iat: now - 180, exp: now + 60 }), KEY, ISSUER, 'drain', raw())).toBeNull()
})
})
describe('admin request verify (keys)', () => {
it('accepts the primary or the NEXT key, nothing else', async () => {
it('accepts the primary key and only a configured NEXT key', async () => {
const env = { ...baseEnv, ADMIN_SIGNING_KEY_NEXT: NEXT } as Env
expect(await verifyAdminRequest(env, request(await sign({ jti: JTI })), 'drain')).not.toBeNull()
expect(await verifyAdminRequest(env, request(await sign({ jti: JTI, key: NEXT })), 'drain')).not.toBeNull()
expect(await verifyAdminRequest(baseEnv, request(await sign({ jti: JTI, key: NEXT })), 'drain')).toBeNull()
const wrong = randomBytes(36).toString('base64url')
expect(await verifyAdminRequest(env, request(await sign({ jti: JTI, key: wrong })), 'drain')).toBeNull()
})
it('the erasure keys (primary or NEXT) verify only erase, under their own issuer', async () => {
+3 -7
View File
@@ -28,7 +28,7 @@ const mint = (key: CryptoKey = privateKey, kid = 'k1') =>
.sign(key)
// A Cache API stand-in shared by every "isolate" (module instance) in the test.
function stubCache(): Map<string, Response> {
function stubCache(): void {
const store = new Map<string, Response>()
vi.stubGlobal('caches', {
default: {
@@ -38,7 +38,6 @@ function stubCache(): Map<string, Response> {
match: async (request: Request) => store.get(request.url)?.clone()
}
})
return store
}
// Each call is a fresh isolate: a new module instance with an empty in-memory key cache.
@@ -64,24 +63,21 @@ afterEach(() => {
describe('relay JWT key availability', () => {
it('still says invalid for a bad token when keys are available', async () => {
it('rejects an unknown key when the key set is available', async () => {
stubCache()
authUp(() => true)
const verify = await coldVerifier()
const env = envFor(`https://auth.test.invalid/jwks/${++urlSeq}`)
expect(await verify(env, await mint(otherKey))).toEqual({ ok: false, reason: 'invalid' })
expect(await verify(env, 'not-a-jwt')).toEqual({ ok: false, reason: 'invalid' })
expect(await verify(env, await mint(privateKey, 'unknown-kid'))).toEqual({ ok: false, reason: 'invalid' })
})
it('a cold isolate verifies from the persisted set while auth is down', async () => {
const store = stubCache()
stubCache()
let up = true
authUp(() => up)
const env = envFor(`https://auth.test.invalid/jwks/${++urlSeq}`)
const warm = await coldVerifier()
expect((await warm(env, await mint())).ok).toBe(true)
expect(store.size).toBe(1)
up = false
const cold = await coldVerifier()
const result = await cold(env, await mint())
@@ -10,4 +10,3 @@ vi.mock('../../relay/src/database/database.js', async (importOriginal) => ({
}))
await import('../../relay/src/credentials/credential-store.test.ts')
await import('../../relay/src/credentials/credential-store-cleanup.test.ts')
@@ -53,23 +53,6 @@ describe('admin routes under a database that is briefly out of reach', () => {
expect(response.headers.get('Retry-After')).toBe('7')
expect(await response.json()).toEqual({ error: 'database_temporarily_unavailable' })
})
it('answers admission-selector/status with a retryable 503 instead of a conflict', async () => {
const inspectCellAdmissionSelector = vi.fn(async () => {
throw poolTimeout()
})
const app = appWith({ inspectCellAdmissionSelector })
const response = await app.request(
'/v1/admin/admission-selector/status',
adminRequest({ v: 1, attemptId: '22222222-2222-4222-8222-222222222222' })
)
expect(response.status).toBe(503)
expect(response.headers.get('Retry-After')).toBe('5')
expect(await response.json()).toEqual({ error: 'database_temporarily_unavailable' })
})
})
function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
@@ -1,140 +0,0 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from './assignment-store.js'
import { openRelayDatabase, type RelayDatabase } from '../database/database.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
const cell = {
id: 'control-supersession-postgres',
url: 'https://control-supersession-postgres.example.com',
capacityRequests: 1_000,
connectionHardCap: 600 as const,
connectionUnobservedBound: 50
}
const identity = {
userId: 'control-supersession-postgres-user',
relayHostId: 'supersedehost001'
}
describePostgres('PostgreSQL control supersession', () => {
const databases: RelayDatabase[] = []
beforeAll(async () => {
databases.push(
await openRelayDatabase({ databaseUrl, dataDir: '' }),
await openRelayDatabase({ databaseUrl, dataDir: '' })
)
})
afterAll(async () => {
const database = databases[0]
if (database) {
await database.query(
`DELETE FROM relay_control_connection_reservations WHERE user_id = ?`,
[identity.userId]
)
await database.query(
`DELETE FROM relay_assignment_activity_leases WHERE user_id = ?`,
[identity.userId]
)
await database.query(`DELETE FROM relay_assignments WHERE user_id = ?`, [identity.userId])
// A snapshot left by an aborted run rejects the replayed watermark with stale_connection_snapshot.
await database.query(`DELETE FROM relay_cell_connection_snapshots WHERE cell_id = ?`, [
cell.id
])
await database.query(`DELETE FROM relay_cell_connection_runtime WHERE cell_id = ?`, [cell.id])
await database.query(`DELETE FROM relay_cell_connection_limits WHERE cell_id = ?`, [cell.id])
await database.query(`DELETE FROM relay_cell_runtime WHERE cell_id = ?`, [cell.id])
await database.query(`DELETE FROM relay_cells WHERE cell_id = ?`, [cell.id])
}
for (const connection of databases) await connection.close()
})
it('serializes parallel generations into one durable control', async () => {
const stores = databases.map((database) => new RelayAssignmentStore(database, () => 100))
await stores[0]!.reconcileCells([cell])
await stores[0]!.recordCellHeartbeat({
cellId: cell.id,
cellUrl: cell.url,
cellIncarnation: '11111111-1111-4111-8111-111111111111',
startedAt: 50,
ready: true,
observedRequests: 0,
totalConnections: 0,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: 0,
connectionInclusionWatermark: 1,
connectionHardCap: 600,
connectionUnobservedBound: 50
})
const assignment = await stores[0]!.assign(identity)
await Promise.all([
stores[0]!.activateControl(identity, {
cellId: cell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1,
connectionInclusionWatermark: 10
}),
stores[1]!.activateControl(identity, {
cellId: cell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 2,
connectionInclusionWatermark: 11
})
])
await databases[0]!.query(
`INSERT INTO relay_assignment_activity_leases
(user_id, relay_host_id, activity_id, activity_kind, cell_id,
request_units, expires_at, updated_at)
VALUES (?, ?, ?, 'control', ?, 1, 90100, 100),
(?, ?, ?, 'control', ?, 1, 90100, 100)`,
[
identity.userId,
identity.relayHostId,
`control:${cell.id}:100`,
cell.id,
identity.userId,
identity.relayHostId,
`control:${cell.id}:101`,
cell.id
]
)
// The store reserves a unit per control lease, so a hand-written pair has to
// carry its own reservation or the fixture starts out of balance.
await databases[0]!.query(
`UPDATE relay_cells SET reserved_requests = reserved_requests + 2 WHERE cell_id = ?`,
[cell.id]
)
await stores[0]!.activateControl(identity, {
cellId: cell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 102,
connectionInclusionWatermark: 12
})
const controls = await databases[0]!.query(
`SELECT COUNT(*) AS count FROM relay_assignment_activity_leases
WHERE user_id = ? AND activity_kind = 'control'`,
[identity.userId]
)
expect(Number(controls[0]!.count)).toBe(1)
const assignments = await databases[0]!.query(
`SELECT reserved_controls FROM relay_assignments WHERE user_id = ?`,
[identity.userId]
)
expect(Number(assignments[0]!.reserved_controls)).toBe(1)
const cells = await databases[0]!.query(
`SELECT reserved_requests FROM relay_cells WHERE cell_id = ?`,
[cell.id]
)
expect(Number(cells[0]!.reserved_requests)).toBe(1)
const claims = await databases[0]!.query(
`SELECT claim_activity_id FROM relay_control_connection_reservations
WHERE user_id = ? AND state = 'claimed'`,
[identity.userId]
)
expect(claims).toEqual([{ claim_activity_id: `control:${cell.id}:102` }])
}, 15_000)
})
@@ -169,7 +169,6 @@ describePostgres('a host leaving a dead reserve cell, on PostgreSQL', () => {
const ledger = new ReserveLedgerWriter(database, { now: () => now, log: () => undefined })
for (const epoch of [6, 7]) ledger.enqueue({ userId: USER, relayHostId: host, cellId: dead.id, epoch })
await ledger.flush()
expect(ledger.pending()).toBe(0)
expect(await rowOf(host)).toEqual({ cell_id: live.id, assignment_epoch: 7 })
})
@@ -181,63 +180,6 @@ describePostgres('a host leaving a dead reserve cell, on PostgreSQL', () => {
await expect(hostAssign(host, true, null)).rejects.toThrow('relay_home_cell_unavailable')
})
it('a withheld verdict (fleet or region guard) keeps the fence', async () => {
const host = nextHost()
await ledgerRow(host, 3)
await store.setCellAdmitMode(dead.id, 'reserve')
await killDeadCell()
store = new RelayAssignmentStore(database, () => now, {
requireLiveCells: true,
heartbeatTtlMs: HEARTBEAT_TTL_MS,
deadCellVerdict: () => ({
dead: false,
withheld: 'region',
fleetFailing: 6,
fleetPolled: 34,
regionFailing: 6,
regionPolled: 6
})
})
await expect(hostAssign(host)).rejects.toThrow('relay_home_cell_unavailable')
})
// Only a heartbeat that stopped (cause 'unheard') is death; polls failing for 45 s add nothing else.
it('a cell heartbeating but not ready keeps its hosts, however long its polls fail', async () => {
const host = nextHost()
await ledgerRow(host, 3)
await store.setCellAdmitMode(dead.id, 'reserve')
now += HEARTBEAT_TTL_MS + 15_000
await heartbeat(dead, false)
await heartbeat(live)
await expect(hostAssign(host)).rejects.toThrow('relay_home_cell_unavailable')
expect(await rowOf(host)).toEqual({ cell_id: dead.id, assignment_epoch: 3 })
})
it('drain then death keeps the fence: a disabled cell is draining, not unheard', async () => {
const host = nextHost()
await ledgerRow(host, 3)
await store.setCellAdmitMode(dead.id, 'reserve')
await database.query(`UPDATE relay_cells SET enabled = 0 WHERE cell_id = ?`, [dead.id])
try {
await killDeadCell()
await expect(hostAssign(host)).rejects.toThrow('relay_home_cell_unavailable')
expect(await rowOf(host)).toEqual({ cell_id: dead.id, assignment_epoch: 3 })
} finally {
await database.query(`UPDATE relay_cells SET enabled = 1 WHERE cell_id = ?`, [dead.id])
}
})
it('LB drain: heartbeat fresh while polls fail, so the host is pinned to its cell', async () => {
const host = nextHost()
await ledgerRow(host, 3)
await store.setCellAdmitMode(dead.id, 'reserve')
now += 10_000
await heartbeat(dead)
await heartbeat(live)
expect(await hostAssign(host)).toMatchObject({ cellId: dead.id, assignmentEpoch: 3 })
expect(await rowOf(host)).toEqual({ cell_id: dead.id, assignment_epoch: 3 })
})
it('DB outage or director freeze: heartbeat stale but polls answered (no verdict), so 503', async () => {
const host = nextHost()
await ledgerRow(host, 3)
@@ -84,27 +84,4 @@ describePostgres('PostgreSQL deployment status', () => {
restartBlockingReservedRequests: -1
})
})
it('reads the same counts through the reserve-aware status, and none for a reserve-mode cell', async () => {
const store = new RelayAssignmentStore(database!, () => 100)
const reserveCell = { ...cell, id: `${cell.id}-reserve`, url: 'https://deployment-status-reserve.example.com' }
await store.reconcileCells([cell, reserveCell])
await store.assign({ userId: 'aware-user', relayHostId: 'd1e2f3a4b5c6' })
expect(await store.reserveAwareCellDeploymentStatus(cell.id)).toEqual({
admitMode: 'db',
status: await store.cellDeploymentStatus(cell.id)
})
await store.setCellAdmitMode(reserveCell.id, 'reserve')
await expect(store.cellDeploymentStatus(reserveCell.id)).rejects.toThrow('cell_in_reserve_mode')
const reserve = await store.reserveAwareCellDeploymentStatus(reserveCell.id)
expect(reserve).toMatchObject({
admitMode: 'reserve',
status: { cellId: reserveCell.id, cellUrl: reserveCell.url, enabled: true, capacityRequests: 20 }
})
for (const field of ['assignments', 'activityLeases', 'reservedRequests', 'restartBlockingReservedRequests']) {
expect(reserve.status).not.toHaveProperty(field)
}
await expect(store.reserveAwareCellDeploymentStatus('missing-cell')).rejects.toThrow('cell_not_found')
})
})
@@ -3,42 +3,27 @@ import { RelayAssignmentStore } from './assignment-store.js'
import { openInMemoryRelayDatabase, type RelayDatabase } from '../database/database.js'
const NOW = 100_000
const CELL_COUNT = 32
const identity = { userId: 'headroom-user', relayHostId: 'headroomhost0001' }
function observeHeadroom(database: RelayDatabase, rowCounts: number[]): RelayDatabase {
return {
dialect: database.dialect,
async query(sql, params) {
const rows = await database.query(sql, params)
if (sql.includes('FROM relay_cell_connection_limits limits')) rowCounts.push(rows.length)
return rows
},
queryLocked: (sql, params, options) => database.queryLocked(sql, params, options),
transaction: (operation, options) =>
database.transaction((transaction) => operation(observeHeadroom(transaction, rowCounts)), options),
close: () => database.close()
}
}
describe('assignment headroom query scope', () => {
describe('assignment headroom admission', () => {
let database: RelayDatabase | undefined
afterEach(async () => await database?.close())
async function setup() {
database = await openInMemoryRelayDatabase()
const rowCounts: number[] = []
const store = new RelayAssignmentStore(observeHeadroom(database, rowCounts), () => NOW, {
const store = new RelayAssignmentStore(database, () => NOW, {
requireLiveCells: true,
heartbeatTtlMs: 45_000
})
const cells = Array.from({ length: CELL_COUNT }, (_, index) => ({
id: `cell-${String(index).padStart(2, '0')}`,
url: `https://cell-${index}.example.com`,
capacityRequests: 1_000,
connectionHardCap: 600 as const,
connectionUnobservedBound: 50
}))
const cells = [
{
id: 'cell-00',
url: 'https://cell-0.example.com',
capacityRequests: 1_000,
connectionHardCap: 600 as const,
connectionUnobservedBound: 50
}
]
await store.reconcileCells(cells)
for (const cell of cells) {
await store.recordCellHeartbeat({
@@ -69,29 +54,9 @@ describe('assignment headroom query scope', () => {
kind: 'splice',
cellId: 'cell-00'
})
rowCounts.length = 0
return { store, rowCounts, database }
return { store, database }
}
it('reads one cell for a sticky assignment even when the capped fleet grows', async () => {
const { store, rowCounts } = await setup()
await expect(store.assign(identity)).resolves.toMatchObject({
...identity,
cellId: 'cell-00',
assignmentEpoch: 1
})
console.info('sticky headroom rows', { fleetCells: CELL_COUNT, rowCounts })
expect(rowCounts).toEqual([1])
})
it('still considers the full fleet for a new placement', async () => {
const { store, rowCounts } = await setup()
await expect(
store.assign({ userId: 'new-user', relayHostId: 'newheadroomhost1' })
).resolves.toMatchObject({ cellId: 'cell-01', assignmentEpoch: 1 })
expect(rowCounts).toEqual([CELL_COUNT])
})
it.each([
['missing snapshot', `DELETE FROM relay_cell_connection_snapshots WHERE cell_id = 'cell-00'`],
['expired snapshot', `UPDATE relay_cell_connection_snapshots SET snapshot_at = ${NOW - 45_000} WHERE cell_id = 'cell-00'`],
@@ -104,10 +69,9 @@ describe('assignment headroom query scope', () => {
})
it('preserves admission for cells without a connection limit', async () => {
const { store, rowCounts, database } = await setup()
const { store, database } = await setup()
await database.query(`DELETE FROM relay_cell_connection_limits WHERE cell_id = 'cell-00'`)
await expect(store.assign(identity)).resolves.toMatchObject({ cellId: 'cell-00' })
expect(rowCounts).toEqual([0])
})
it('counts outstanding reservations at the admission boundary', async () => {
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it } from 'vitest'
import { RelayAssignmentStore, RelayHomeCellUnavailableError } from './assignment-store.js'
import { RelayAssignmentStore } from './assignment-store.js'
import type { RelayCellConfig } from '../config.js'
import { openInMemoryRelayDatabase, type RelayDatabase } from '../database/database.js'
@@ -7,16 +7,6 @@ const HEARTBEAT_TTL_MS = 45_000
const START_MS = 100
const IDENTITY = { userId: 'user-1', relayHostId: 'host000000000001' }
// A connection-limited cell is what makes the committed fence mandatory, and
// that is the branch which used to answer "capacity exhausted".
const FENCED_CELL: RelayCellConfig = {
id: 'home',
url: 'https://home.example.com',
capacityRequests: 1_000,
connectionHardCap: 600,
connectionUnobservedBound: 50
}
const databases: RelayDatabase[] = []
afterEach(async () => {
@@ -29,7 +19,7 @@ interface Harness {
setNow: (value: number) => void
}
async function setup(cells: RelayCellConfig[] = [FENCED_CELL]): Promise<Harness> {
async function setup(cells: RelayCellConfig[]): Promise<Harness> {
const database = await openInMemoryRelayDatabase()
databases.push(database)
let now = START_MS
@@ -45,76 +35,14 @@ async function setup(cells: RelayCellConfig[] = [FENCED_CELL]): Promise<Harness>
cellIncarnation: `1111111${cells.indexOf(cell)}-1111-4111-8111-111111111111`,
startedAt: 50,
ready,
observedRequests: 0,
...(cell.connectionHardCap === undefined
? {}
: {
totalConnections: 0,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: 0,
connectionHardCap: cell.connectionHardCap,
connectionUnobservedBound: cell.connectionUnobservedBound
})
observedRequests: 0
})
}
for (const cell of cells) await heartbeat(cell, true)
return { store, heartbeat, setNow: (value: number) => (now = value) }
}
async function assignFailure(store: RelayAssignmentStore): Promise<unknown> {
return await store.assign(IDENTITY).then(
() => new Error('assign unexpectedly succeeded'),
(error: unknown) => error
)
}
function homeCellError(error: unknown): RelayHomeCellUnavailableError {
expect(error).toBeInstanceOf(RelayHomeCellUnavailableError)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the assertion above.
return error as RelayHomeCellUnavailableError
}
describe('home cell unavailable', () => {
it('names a readiness failure rather than reporting fleet capacity', async () => {
const { store, heartbeat, setNow } = await setup()
await store.assign(IDENTITY)
setNow(START_MS + 1_000)
await heartbeat(FENCED_CELL, false)
const error = homeCellError(await assignFailure(store))
expect(error.message).toBe('relay_home_cell_unavailable')
expect(error.unavailableCause).toBe('not_ready')
expect(error.cellId).toBe(FENCED_CELL.id)
})
it('names a heartbeat gap as unheard even though the cell last reported ready', async () => {
const { store, setNow } = await setup()
await store.assign(IDENTITY)
setNow(START_MS + HEARTBEAT_TTL_MS + 1)
expect(homeCellError(await assignFailure(store)).unavailableCause).toBe('unheard')
})
it('names a drained cell as draining ahead of its heartbeat gap', async () => {
const { store, setNow } = await setup()
await store.assign(IDENTITY)
await store.configureCell(FENCED_CELL, false)
setNow(START_MS + HEARTBEAT_TTL_MS + 1)
expect(homeCellError(await assignFailure(store)).unavailableCause).toBe('draining')
})
it('still reports capacity exhaustion when the fleet has no headroom', async () => {
const { store } = await setup([{ ...FENCED_CELL, capacityRequests: 1 }])
await store.assign(IDENTITY)
await expect(
store.assign({ userId: 'user-2', relayHostId: 'host000000000002' })
).rejects.toThrow('relay_capacity_exhausted')
})
it('rehomes instead of rejecting when the unavailable cell needs no fence', async () => {
const home: RelayCellConfig = {
id: 'home',
@@ -1,108 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
formatAssignmentInventorySnapshot,
readAssignmentInventorySnapshot
} from './assignment-inventory-snapshot.js'
import { openInMemoryRelayDatabase } from '../database/database.js'
describe('assignment inventory snapshot', () => {
it('reports per-cell counters, lease backlog, and reservation debt', async () => {
const database = await openInMemoryRelayDatabase()
const now = 1_000_000
await database.query(
`INSERT INTO relay_cells
(cell_id, cell_url, enabled, capacity_requests, reserved_requests,
observed_requests, last_heartbeat_at, updated_at)
VALUES ('cell-a', 'https://a.example.test', 1, 4000, 3999, 5, ?, ?)`,
[now, now]
)
await database.query(
`INSERT INTO relay_cell_admission (cell_id, admission_state, updated_at)
VALUES ('cell-a', 'general', ?)`,
[now]
)
await database.query(
`INSERT INTO relay_cell_runtime
(cell_id, cell_url, cell_incarnation, started_at, ready, observed_requests,
last_heartbeat_at, updated_at)
VALUES ('cell-a', 'https://a.example.test', 'inc-1', ?, 1, 5, ?, ?)`,
[now - 60_000, now - 10_000, now]
)
await database.query(
`INSERT INTO relay_assignment_activity_leases
(user_id, relay_host_id, activity_id, activity_kind, cell_id, request_units,
expires_at, updated_at)
VALUES
('user-1', 'host-1', 'control:1', 'control', 'cell-a', 1, ?, ?),
('user-1', 'host-2', 'control:1', 'control', 'cell-a', 3, ?, ?)`,
[now - 1, now, now + 90_000, now]
)
await database.query(
`INSERT INTO relay_control_connection_reservations
(reservation_id, idempotency_key, user_id, relay_host_id, assignment_epoch,
cell_id, state, created_at, timeout_at, updated_at)
VALUES
('r1', 'k1', 'user-1', 'host-1', 1, 'cell-a', 'late-arrival-debt', ?, ?, ?),
('r2', 'k2', 'user-1', 'host-2', 1, 'cell-a', 'reserved', ?, ?, ?),
('r3', 'k3', 'user-1', 'host-3', 1, 'cell-a', 'claimed', ?, ?, ?),
('r4', 'k4', 'user-1', 'host-4', 1, 'cell-a', 'released', ?, ?, ?)`,
[now, now, now, now, now, now, now, now, now, now, now, now]
)
const snapshot = await readAssignmentInventorySnapshot(database, now)
expect(snapshot.cells).toEqual([
{
cellId: 'cell-a',
region: 'us-central1',
admissionState: 'general',
enabled: true,
capacityRequests: 4000,
reservedRequests: 3999,
runtimeReady: true,
heartbeatAgeMs: 10_000
}
])
expect(snapshot.activityLeases).toEqual({ total: 2, expired: 1, requestUnits: 4 })
expect(snapshot.connectionReservations).toEqual({ outstanding: 3, lateArrivalDebt: 1 })
expect(snapshot.regionalRehomes).toEqual({
active: 0,
awaitingReceipt: 0,
targetRegistered: 0,
completedLast24Hours: 0,
abortedLast24Hours: 0,
hostNotArrivedLast24Hours: 0,
oldestActiveAgeMs: null
})
const lines = formatAssignmentInventorySnapshot(snapshot)
expect(lines).toHaveLength(3)
expect(lines[0]).toContain('cellId=cell-a')
expect(lines[0]).toContain('reserved=3999')
expect(lines[1]).toContain('expiredLeases=1')
expect(lines[1]).toContain('lateArrivalDebt=1')
expect(lines[2]).toContain('active=0')
})
it('reports cells missing runtime and admission rows without failing', async () => {
const database = await openInMemoryRelayDatabase()
await database.query(
`INSERT INTO relay_cells
(cell_id, cell_url, enabled, capacity_requests, reserved_requests,
observed_requests, last_heartbeat_at, updated_at)
VALUES ('cell-b', 'https://b.example.test', 0, 4000, 0, 0, 0, 0)`
)
const snapshot = await readAssignmentInventorySnapshot(database, 5_000)
expect(snapshot.cells[0]).toMatchObject({
cellId: 'cell-b',
admissionState: 'unset',
enabled: false,
runtimeReady: null,
heartbeatAgeMs: null
})
expect(snapshot.activityLeases).toEqual({ total: 0, expired: 0, requestUnits: 0 })
expect(snapshot.regionalRehomes.active).toBe(0)
})
})
@@ -302,7 +302,7 @@ describePostgres('PostgreSQL re-placement off a cell isolated for a roll', () =>
).toBe(targetBaseline + 1)
}, 30_000)
it('never waits on the assignment row while its retry holds the pinned cell row', async () => {
it('refuses the assignment row when its retry holds the pinned cell row', async () => {
await resetFleet()
const identity = hostIdentity(923)
const first = await stores[0]!.assign(identity, 'us-central1')
@@ -324,10 +324,7 @@ describePostgres('PostgreSQL re-placement off a cell isolated for a roll', () =>
[identity.userId, identity.relayHostId]
)
await releaseCell()
const cellFreedAt = performance.now()
expect(await regrant).toBeInstanceOf(RelayAssignmentRowBusyError)
// NOWAIT once the cell row is held: nowhere near the 1s bounded wait.
expect(performance.now() - cellFreedAt).toBeLessThan(300)
} finally {
await releaseAssignment?.()
await releaseCell()
@@ -1,22 +1,10 @@
import { createHash } from 'node:crypto'
import { EventEmitter } from 'node:events'
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { SignJWT } from 'jose'
import nacl from 'tweetnacl'
import { describe, expect, it, vi } from 'vitest'
import type WebSocket from 'ws'
import { describe, expect, it } from 'vitest'
import {
ASSIGNMENT_LEASE_AUDIENCE,
assignmentLeaseKeyId,
classifyAssignmentLease
} from './assignment-lease.js'
import { AssignmentLeaseShadow } from './assignment-lease-shadow.js'
import type { RelayAssignmentStore } from './assignment-store.js'
import type { RelayConfig } from '../config.js'
import type { RelayCredentialStore } from '../credentials/credential-store.js'
import { HostSessionRegistry } from '../connections/host-session-registry.js'
import type { RelayTokenClaims } from '../authentication/relay-token-verifier.js'
import { ProcessQueuedByteBudget } from '../connections/splice-forwarder.js'
const KEY = new TextEncoder().encode('0123456789abcdef0123456789abcdef')
const OTHER_KEY = new TextEncoder().encode('fedcba9876543210fedcba9876543210')
@@ -30,7 +18,6 @@ async function lease(
cellId?: string
assignmentEpoch?: number
sub?: string
relayHostId?: string
expiresAt?: string | number
} = {}
): Promise<string> {
@@ -40,7 +27,7 @@ async function lease(
cellId: overrides.cellId ?? CELL_ID,
cellUrl: 'https://c7.relay.example.test',
assignmentEpoch: overrides.assignmentEpoch ?? 4,
relayHostId: overrides.relayHostId ?? HOST.relayHostId
relayHostId: HOST.relayHostId
})
.setProtectedHeader(kid === null ? { alg: 'HS256' } : { alg: 'HS256', kid })
.setIssuer('https://relay.example.test')
@@ -73,7 +60,6 @@ describe('assignment lease classification', () => {
expect(await classify(await lease({ kid: assignmentLeaseKeyId(OTHER_KEY) }))).toBe(
'bad-signature'
)
expect(await classify('not.a.jwt')).toBe('bad-signature')
expect(await classify('x'.repeat(5_000))).toBe('bad-signature')
expect(await classify(await lease({ sub: 'user-2' }))).toBe('wrong-host')
expect(await classify(await lease({ cellId: 'production-gce-c8' }))).toBe('wrong-cell')
@@ -81,127 +67,3 @@ describe('assignment lease classification', () => {
expect(await classify(await lease({ assignmentEpoch: 5 }))).toBe('epoch-ahead')
})
})
class FakeSocket extends EventEmitter {
readonly OPEN = 1
readonly CLOSED = 3
readyState = this.OPEN
readonly send = vi.fn()
readonly close = vi.fn((code?: number) => {
this.readyState = this.CLOSED
this.emit('close', code, Buffer.from(''))
})
}
describe('host hello with the lease shadow on', () => {
async function hello(input: { withLease: boolean; databaseSays: boolean }) {
const keyPair = nacl.box.keyPair()
const relayHostId = createHash('sha256')
.update(keyPair.publicKey)
.digest('base64url')
.slice(0, 16)
const identity: RelayTokenClaims = {
sub: HOST.userId,
prof: 'profile-1',
relayHostId,
purpose: 'host-control',
exp: 4_102_444_800
}
const recorded: string[] = []
const shadow = new AssignmentLeaseShadow({ enabled: () => true, key: KEY, cellId: CELL_ID })
const record = shadow.record.bind(shadow)
vi.spyOn(shadow, 'record').mockImplementation((check, dbValid, dbReadMs) => {
void check.then((leaseClass) => recorded.push(`${leaseClass}:${dbValid}`))
record(check, dbValid, dbReadMs)
})
const verifyCellAssignment = vi.fn(async () => input.databaseSays)
const registry = new HostSessionRegistry(
config(),
vi.fn(),
{} as RelayCredentialStore,
{ verifyCellAssignment } as unknown as RelayAssignmentStore,
new ProcessQueuedByteBudget(),
{
recordAuth: vi.fn(),
recordForwardedBytes: vi.fn(),
recordHttp: vi.fn(),
recordReconnect: vi.fn(),
recordSql: vi.fn()
},
Date.now,
Math.random,
'incarnation-1',
shadow
)
const socket = new FakeSocket()
const hostLease = input.withLease ? await lease({ relayHostId }) : undefined
registry.acceptControl(
socket as unknown as WebSocket,
identity,
undefined,
undefined,
hostLease
)
socket.emit(
'message',
Buffer.from(
JSON.stringify({
type: 'host-hello',
v: 1,
relayHostId,
assignmentEpoch: 4,
hostPublicKeyB64: Buffer.from(keyPair.publicKey).toString('base64'),
appVersion: '1.4.200'
})
),
false
)
await vi.waitFor(() => expect(recorded).toHaveLength(1))
return { socket, recorded, verifyCellAssignment }
}
it('still refuses a host the database refuses, whatever its lease says', async () => {
const { socket, recorded } = await hello({ withLease: true, databaseSays: false })
expect(socket.close).toHaveBeenCalledWith(RELAY_CLOSE_CODE.WRONG_CELL, 'wrong assignment epoch')
expect(recorded).toEqual(['valid:false'])
})
it('still admits a host the database admits, with no lease at all', async () => {
const { socket, recorded, verifyCellAssignment } = await hello({
withLease: false,
databaseSays: true
})
expect(verifyCellAssignment).toHaveBeenCalledTimes(1)
expect(socket.close).not.toHaveBeenCalled()
expect(String(socket.send.mock.calls[0]?.[0])).toContain('"type":"host-challenge"')
expect(recorded).toEqual(['absent:true'])
})
})
function config(): RelayConfig {
return {
port: 8080,
publicUrl: 'https://c7.relay.example.test',
cellUrl: 'https://c7.relay.example.test',
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: KEY,
role: 'cell',
cellId: CELL_ID,
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
runtimeServiceAccount: 'relay-cell@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 10,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data'
}
}
@@ -1,48 +0,0 @@
import { ASSIGNMENT_LIMITS } from '@orca-cloud/relay-contract/assignment-invariants'
import { afterEach, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from './assignment-store.js'
import { openInMemoryRelayDatabase, type RelayDatabase } from '../database/database.js'
const CELL = { id: 'cell-a', url: 'https://relay-a.example.com', capacityRequests: 10 }
const IDENTITY = { userId: 'user-a', relayHostId: 'host000000000001' }
describe('assign marks only the answers it minted as placed', () => {
let database: RelayDatabase | undefined
afterEach(async () => await database?.close())
async function setup(now: () => number): Promise<RelayAssignmentStore> {
database = await openInMemoryRelayDatabase()
const store = new RelayAssignmentStore(database, now)
await store.reconcileCells([CELL])
return store
}
it('marks a fresh placement and leaves the existing row unmarked', async () => {
const store = await setup(() => 100)
const first = await store.assign(IDENTITY)
const again = await store.assign(IDENTITY)
expect(first.placed).toBe(true)
expect(again).toMatchObject({ cellId: first.cellId, assignmentEpoch: first.assignmentEpoch })
expect(again.placed).toBeUndefined()
})
it('marks a same-cell re-placement with a new epoch', async () => {
let now = 100
const store = await setup(() => now)
const first = await store.assign(IDENTITY)
await store.changeActivity(IDENTITY, 'control', -1)
now += ASSIGNMENT_LIMITS.activityLeaseMs + 1
await store.releaseExpiredActivityLeases()
await store.releaseExpiredActivity()
now += ASSIGNMENT_LIMITS.dormantTtlMs
const replaced = await store.assign(IDENTITY)
expect(replaced).toMatchObject({
cellId: first.cellId,
assignmentEpoch: first.assignmentEpoch + 1
})
expect(replaced.placed).toBe(true)
})
})
@@ -1,82 +0,0 @@
import { describe, expect, it } from 'vitest'
import { AssignmentRejectionLogWindow } from './assignment-rejection-log-window.js'
describe('assignment rejection log window', () => {
it('emits once per key per window and closes it with its own suppressed count', () => {
const timers = manualTimers()
const closed: { key: string; suppressed: number; sample: string }[] = []
const window = new AssignmentRejectionLogWindow<string>({
windowMs: 10_000,
schedule: timers.schedule,
onWindowClosed: (input) => closed.push(input)
})
const key = 'assign:placement:host-rate-limited'
expect(window.admit(key, 'host-a')).toBe(true)
expect(window.admit(key, 'host-b')).toBe(false)
expect(window.admit(key, 'host-c')).toBe(false)
expect(closed).toEqual([])
timers.runPending()
expect(closed).toEqual([{ key, suppressed: 2, sample: 'host-c' }])
// The next window starts clean instead of inheriting the closed window's count.
expect(window.admit(key, 'host-d')).toBe(true)
timers.runPending()
expect(closed).toHaveLength(1)
})
it('keeps distinct keys on independent windows', () => {
const timers = manualTimers()
const window = new AssignmentRejectionLogWindow<string>({
windowMs: 10_000,
schedule: timers.schedule,
onWindowClosed: () => undefined
})
expect(window.admit('assign:placement:host-rate-limited', 'host-a')).toBe(true)
expect(window.admit('assign:placement:queue-full', 'host-a')).toBe(true)
expect(window.admit('assign:sticky:host-rate-limited', 'host-a')).toBe(true)
expect(window.admit('assign:placement:queue-full', 'host-a')).toBe(false)
})
it('bounds the tracked keys and reports what an evicted window suppressed', () => {
const timers = manualTimers()
const closed: { key: string; suppressed: number }[] = []
const window = new AssignmentRejectionLogWindow<string>({
windowMs: 10_000,
schedule: timers.schedule,
onWindowClosed: ({ key, suppressed }) => closed.push({ key, suppressed })
})
window.admit('key-0', 'host-a')
window.admit('key-0', 'host-b')
for (let index = 1; index < 200; index++) window.admit(`key-${index}`, 'host-a')
expect(closed[0]).toEqual({ key: 'key-0', suppressed: 1 })
// Evicted keys emit again rather than staying silently suppressed.
expect(window.admit('key-0', 'host-a')).toBe(true)
expect(window.admit('key-199', 'host-a')).toBe(false)
})
})
function manualTimers(): {
schedule: (callback: () => void) => () => void
runPending: () => void
} {
const pending = new Map<number, () => void>()
let nextId = 0
return {
schedule: (callback) => {
const id = nextId++
pending.set(id, callback)
return () => pending.delete(id)
},
runPending: () => {
for (const [id, callback] of [...pending]) {
pending.delete(id)
callback()
}
}
}
}
@@ -1,144 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { RelayAssignment } from './assignment-store.js'
import type { RelayConfig } from '../config.js'
const fakes = vi.hoisted(() => ({
verifyRelayToken: vi.fn(async (token: string) => ({
sub: 'user-1',
relayHostId: token
}))
}))
vi.mock('../authentication/relay-token-verifier.js', () => ({
createRelayTokenVerifier: () => fakes.verifyRelayToken,
readBearer: (value: string | undefined) => value?.replace(/^Bearer /, '') ?? null
}))
import { createRelayApp } from '../app.js'
import { relayHostLogDigest } from '../observability/relay-host-log-digest.js'
describe('assignment rejection logging', () => {
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
})
it('logs the store reason and host digest when assign() rejects on capacity', async () => {
const host = 'hhhhhhhhhhhhhhhh'
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const app = createRelayApp(config(), {
store: {} as never,
assignments: {
assign: vi.fn(async () => {
throw new Error('relay_capacity_exhausted')
}),
resolve: vi.fn(async () => assignment('cell-r', host))
} as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
const hinted = await app.request('/v1/assign', assignmentRequest(host, { reconnect: true }))
expect(hinted.status).toBe(503)
expect(await hinted.json()).toEqual({ error: 'relay_capacity_exhausted' })
const line = warn.mock.calls
.map((call) => String(call[0]))
.find((entry) => entry.includes('assignment rejected'))
expect(line).toContain('route=assign')
expect(line).toContain('lane=sticky')
expect(line).toContain('hinted=true')
expect(line).toContain('reason=relay_capacity_exhausted')
expect(line).toContain(`host=${relayHostLogDigest(host)}`)
expect(line).not.toContain(host)
})
it('names the throttled host once per window and closes it with the suppressed count', async () => {
vi.useFakeTimers()
const host = 'mmmmmmmmmmmmmmmm'
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const app = createRelayApp(config(), {
store: {} as never,
assignments: {
assign: vi.fn(async () => assignment('cell-r', host)),
resolve: vi.fn(async () => null)
} as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
expect((await app.request('/v1/assign', assignmentRequest(host))).status).toBe(200)
expect((await app.request('/v1/assign', assignmentRequest(host))).status).toBe(503)
expect((await app.request('/v1/assign', assignmentRequest(host))).status).toBe(503)
const throttled = (): string[] =>
warn.mock.calls
.map((call) => String(call[0]))
.filter((entry) => entry.includes('reason=host-rate-limited'))
expect(throttled()).toHaveLength(1)
expect(throttled()[0]).toContain('route=assign')
expect(throttled()[0]).toContain('lane=placement')
expect(throttled()[0]).toContain(`host=${relayHostLogDigest(host)}`)
expect(throttled()[0]).not.toContain('suppressed=')
expect(throttled()[0]).not.toContain(host)
await vi.advanceTimersByTimeAsync(10_000)
expect(throttled()).toHaveLength(2)
expect(throttled()[1]).toContain('suppressed=1')
expect(throttled()[1]).toContain(`host=${relayHostLogDigest(host)}`)
})
})
function assignmentRequest(
relayHostId: string,
extra: Record<string, unknown> = {}
): RequestInit & { headers: Record<string, string> } {
return {
method: 'POST',
headers: {
authorization: `Bearer ${relayHostId}`,
'content-type': 'application/json'
},
body: JSON.stringify({ v: 1, relayHostId, ...extra })
}
}
function assignment(cellId: string, relayHostId: string): RelayAssignment {
return {
userId: 'user-1',
relayHostId,
cellId,
cellUrl: `https://${cellId}.relay.example.test`,
assignmentEpoch: 1,
leaseExpiresAt: Date.now() + 300_000
}
}
function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
return {
port: 8080,
publicUrl: 'https://relay.example.test',
cellUrl: 'https://relay.example.test',
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: new TextEncoder().encode('assignment-key-with-at-least-32-bytes'),
role: 'director',
cellId: 'director',
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
runtimeServiceAccount: 'runtime@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 3,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data',
...overrides
}
}
@@ -62,15 +62,4 @@ describe('evacuation from reserve-mode cells', () => {
await store.setCellAdmitMode('cell-a', 'db')
expect(await store.completeReadyEvacuations()).toBe(1)
})
it('refuses evacuation status for a reserve cell, where activeCellMigrations would count no units', async () => {
const store = await setup(() => 1_000)
await store.setCellAdmitMode('cell-a', 'reserve')
await expect(store.cellEvacuationStatus('cell-a', 'cell-b', true)).rejects.toThrow(
'cell_in_reserve_mode'
)
await expect(store.cellEvacuationStatus('cell-b', 'cell-a', false)).rejects.toThrow(
'cell_in_reserve_mode'
)
})
})
@@ -202,49 +202,5 @@ describePostgres('reserve-mode census on PostgreSQL', () => {
await holder.close()
}
}, 20_000)
// A stall must not double connection demand: concurrent assigns share one reserve-set read,
// and after it fails the last answer stands for the backoff without another query.
it('reads the reserve set once for concurrent assigns during a stall, and not again in the backoff', async () => {
const timed = await openRelayDatabase({ databaseUrl, dataDir: '/tmp', statementTimeoutMs: 300 })
const holder = await openRelayDatabase({ databaseUrl, dataDir: '/tmp' })
let release!: () => void
const released = new Promise<void>((resolve) => (release = resolve))
let held!: () => void
const holding = new Promise<void>((resolve) => (held = resolve))
const lock = holder.transaction(async (transaction) => {
await transaction.query(`LOCK TABLE relay_cell_admit_modes IN ACCESS EXCLUSIVE MODE`)
held()
await released
})
try {
await holding
const store = new RelayAssignmentStore(timed, () => 500_000)
await store.reconcileCells([cellA, cellB], false)
await heartbeat(store, cellA)
await heartbeat(store, cellB)
const query = timed.query.bind(timed)
let reads = 0
timed.query = async (sql, params) => {
if (sql.includes('FROM relay_cell_admit_modes m')) reads += 1
return await query(sql, params)
}
const placed = await Promise.all(
Array.from({ length: 6 }, async (_, index) =>
await store.assign({ userId: USER, relayHostId: `hoststall0000${String(index).padStart(3, '0')}` })
)
)
expect(placed).toHaveLength(6)
expect(reads).toBe(1)
await store.assign({ userId: USER, relayHostId: 'hoststall0000100' })
expect(await store.reserveModeCells()).toBeNull()
expect(reads).toBe(1)
} finally {
release()
await lock
await timed.close()
await holder.close()
}
}, 20_000)
})
@@ -59,30 +59,11 @@ describe('assignment reserve-mode behavior', () => {
const { unknown, restore } = await unknownSet(() => now)
expect(await unknown.reserveModeCells()).toBeNull()
expect(await unknown.releaseExpiredActivityLeases()).toBe(0)
expect(await unknown.evacuateDeadCells()).toBe(0)
await restore()
await setReserve(store, [])
expect(await store.releaseExpiredActivityLeases()).toBeGreaterThan(0)
})
it('keeps an idle host sticky on a reserve-mode cell where today it would be re-placed', async () => {
let now = 1_000
const store = await setup(() => now)
const identity = { userId: 'user-a', relayHostId: 'host000000000001' }
const first = await store.assign(identity)
await store.changeActivity(identity, 'control', -1)
now += ASSIGNMENT_LIMITS.activityLeaseMs + ASSIGNMENT_LIMITS.dormantTtlMs + 1
await setReserve(store, [first.cellId])
expect(await store.assign(identity)).toMatchObject({
cellId: first.cellId,
assignmentEpoch: first.assignmentEpoch
})
// Not knowing which cells are switched on is treated the same way.
const { unknown, restore } = await unknownSet(() => now)
expect((await unknown.assign(identity)).assignmentEpoch).toBe(first.assignmentEpoch)
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)
@@ -125,7 +106,7 @@ describe('assignment reserve-mode behavior', () => {
}
return await query(sql, params)
}
return { counter, release, restore: () => (database!.query = query) }
return { release, restore: () => (database!.query = query) }
}
it('reads afresh after an admit-mode write instead of joining a read begun before it', async () => {
@@ -155,12 +136,10 @@ describe('assignment reserve-mode behavior', () => {
try {
const before = store.reserveModeCells()
await store.setCellAdmitMode('cell-a', 'reserve')
const reads = held.counter.reads
held.release()
await before
// A write cleared the cache: the next caller reads now, not after a 5 s backoff.
expect([...((await store.reserveModeCells()) ?? [])]).toEqual(['cell-a'])
expect(held.counter.reads).toBeGreaterThan(reads)
} finally {
held.release()
held.restore()
@@ -195,17 +174,6 @@ describe('assignment reserve-mode behavior', () => {
}
})
it('never places a fresh host on a reserve-mode cell through the database', async () => {
const store = await setup(() => 1_000, ['cell-a'])
for (let index = 0; index < 4; index += 1) {
const placed = await store.assign({
userId: `user-${index}`,
relayHostId: `host${String(index).padStart(12, '0')}`
})
expect(placed.cellId).toBe('cell-b')
}
})
it('refuses drains, evacuations and status reads that would treat a reserve-mode cell as empty', async () => {
const store = await setup(() => 1_000, ['cell-a'])
await expect(store.cellDeploymentStatus('cell-a')).rejects.toBeInstanceOf(
@@ -232,25 +200,4 @@ describe('assignment reserve-mode behavior', () => {
await expect(unknown.cellDeploymentStatus('cell-b')).rejects.toThrow('reserve_mode_unknown')
await restore()
})
it('refuses the flip to reserve while a migration names the cell', async () => {
const store = await setup(() => 1_000)
await database!.query(
`INSERT INTO relay_assignment_migrations
(user_id, relay_host_id, source_cell_id, target_cell_id, previous_epoch, assignment_epoch,
source_request_units, target_reserved_units, expires_at, created_at, updated_at)
VALUES ('user-a', 'host000000000001', 'cell-b', 'cell-a', 1, 2, 0, 1, 9999, 1000, 1000)`
)
await expect(store.setCellAdmitMode('cell-a', 'reserve')).rejects.toThrow(
'cell_has_open_migration'
)
expect(await store.cellAdmitMode('cell-a')).toMatchObject({ admitMode: 'db' })
await store.setCellAdmitMode('cell-a', 'db')
await database!.query(`UPDATE relay_assignment_migrations SET aborted_at = 1001`)
await store.setCellAdmitMode('cell-a', 'reserve')
expect(await store.cellAdmitMode('cell-a')).toMatchObject({
admitMode: 'reserve',
updatedAt: 1_000
})
})
})
@@ -1,152 +0,0 @@
import { ASSIGNMENT_LIMITS } from '@orca-cloud/relay-contract/assignment-invariants'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
RelayAssignmentStore,
type AssignOptions,
type RelayAssignment
} from './assignment-store.js'
import type { RelayCellConfig } from '../config.js'
import {
openInMemoryRelayDatabase,
type RelayDatabase,
type RelayLockOptions,
type RelayTransactionOptions,
type SqlRow
} from '../database/database.js'
import { ReservePaceError } from './reserve-placement/reserve-assignment.js'
const CELLS: RelayCellConfig[] = [
{ id: 'us-c1', url: 'https://us-c1.relay.example.com', region: 'us-central1', capacityRequests: 100 },
{ id: 'asia-c1', url: 'https://asia-c1.relay.example.com', region: 'asia-east2', capacityRequests: 100 }
]
const IDENTITY = { userId: 'spill-user', relayHostId: 'spillhost0000001' }
const ON_US_RESERVE: RelayAssignment = {
...IDENTITY,
cellId: 'us-reserve',
cellUrl: 'https://us-reserve.relay.example.com',
assignmentEpoch: 8,
leaseExpiresAt: 1_000_000,
region: 'us-central1'
}
// Reports the target cell full when its reservation is taken: the race between pick and reserve.
class FullAtReserveDatabase implements RelayDatabase {
armed = false
constructor(private readonly delegate: RelayDatabase) {}
get dialect(): 'sqlite' | 'postgres' | undefined {
return this.delegate.dialect
}
async query(sql: string, params?: unknown[]): Promise<SqlRow[]> {
return await this.delegate.query(sql, params)
}
async queryLocked(sql: string, params?: unknown[], options?: RelayLockOptions): Promise<SqlRow[]> {
const rows = await this.delegate.queryLocked(sql, params, options)
if (!this.armed || sql.trim() !== 'SELECT * FROM relay_cells WHERE cell_id = ?') {
return rows
}
return rows.map((row) => ({ ...row, reserved_requests: row.capacity_requests }))
}
async transaction<T>(operation: (transaction: RelayDatabase) => Promise<T>, options?: RelayTransactionOptions): Promise<T> {
return await this.delegate.transaction(async (inner) => {
const wrapped = new FullAtReserveDatabase(inner)
wrapped.armed = this.armed
return await operation(wrapped)
}, options)
}
async close(): Promise<void> {
await this.delegate.close()
}
}
describe('reserve spill before relay_capacity_exhausted', () => {
let database: FullAtReserveDatabase
let store: RelayAssignmentStore
let now: number
beforeEach(async () => {
now = 100
database = new FullAtReserveDatabase(await openInMemoryRelayDatabase())
store = new RelayAssignmentStore(database, () => now)
await store.reconcileCells(CELLS)
})
afterEach(async () => {
vi.restoreAllMocks()
await database.close()
})
const closeAllDatabaseCells = async () => {
for (const cell of CELLS) {
await store.configureCell(cell, 'migration-only')
}
}
const assignAsia = async (step5: AssignOptions) =>
await store.assign(IDENTITY, 'asia-east2', 'asia-east2', undefined, step5)
it('books a reserve seat when no database cell has room anywhere', async () => {
await closeAllDatabaseCells()
const reserveSpill = vi.fn(async () => ON_US_RESERVE)
await expect(assignAsia({ reserveSpill })).resolves.toEqual({ ...ON_US_RESERVE, placed: true })
expect(reserveSpill).toHaveBeenCalledWith({ skipOwnRegion: false, rowEpoch: 0 })
expect(await database.query(`SELECT cell_id FROM relay_assignments`)).toEqual([])
})
it('skips the own region after the in-region reserve fallback already tried it', async () => {
await closeAllDatabaseCells()
const regionReserve = vi.fn(async () => null)
const reserveSpill = vi.fn(async () => ON_US_RESERVE)
await expect(assignAsia({ regionReserve, reserveSpill })).resolves.toMatchObject({ cellId: 'us-reserve' })
expect(regionReserve).toHaveBeenCalledOnce()
expect(reserveSpill).toHaveBeenCalledWith({ skipOwnRegion: true, rowEpoch: 0 })
})
it('hands the spill the epoch of the host row it read', async () => {
const first = await assignAsia({})
// Dormant: its next assign is a placement, which reads the row and finds no cell.
await store.changeActivity(IDENTITY, 'control', -1)
now += ASSIGNMENT_LIMITS.activityLeaseMs + 1
await store.releaseExpiredActivityLeases()
await store.releaseExpiredActivity()
now += ASSIGNMENT_LIMITS.dormantTtlMs
await closeAllDatabaseCells()
const reserveSpill = vi.fn(async () => ON_US_RESERVE)
await assignAsia({ reserveSpill })
expect(reserveSpill).toHaveBeenCalledWith({ skipOwnRegion: false, rowEpoch: first.assignmentEpoch })
})
it('refuses with relay_capacity_exhausted when no reserve seat exists either', async () => {
await closeAllDatabaseCells()
await expect(assignAsia({ reserveSpill: async () => null })).rejects.toThrow('relay_capacity_exhausted')
})
it('answers a paced spill with its pace, not a refusal', async () => {
await closeAllDatabaseCells()
const reserveSpill = vi.fn(async () => {
throw new ReservePaceError(4)
})
await expect(assignAsia({ reserveSpill })).rejects.toBeInstanceOf(ReservePaceError)
})
it('answers an in-region reserve pace before any spill', async () => {
await closeAllDatabaseCells()
const reserveSpill = vi.fn(async () => ON_US_RESERVE)
const regionReserve = async () => {
throw new ReservePaceError(2)
}
await expect(assignAsia({ regionReserve, reserveSpill })).rejects.toBeInstanceOf(ReservePaceError)
expect(reserveSpill).not.toHaveBeenCalled()
})
it('does not spill a reservation race: the host retries and re-picks', async () => {
database.armed = true
const reserveSpill = vi.fn(async () => ON_US_RESERVE)
await expect(assignAsia({ reserveSpill })).rejects.toThrow('relay_capacity_exhausted')
expect(reserveSpill).not.toHaveBeenCalled()
})
})
@@ -140,21 +140,12 @@ async function setup(cells: RelayCellConfig[] = [SOURCE, TARGET]) {
}
}
// The old process stopped heartbeating at START_MS, so it is unheard from here.
const OLD_UNHEARD_AT = START_MS + HEARTBEAT_TTL_MS + 1_000
// Both routes that refused in production: a hinted assign (placement lane after its
// verification read finds the cell not live) and resolve, which then assigns.
async function hintedAssign(store: RelayAssignmentStore) {
return await store.assign(IDENTITY, 'us-central1', 'us-central1', undefined, {
hostRequested: true
})
}
async function resolveRoute(store: RelayAssignmentStore) {
return (await store.resolve(IDENTITY)) ?? (await store.assign(IDENTITY))
}
async function expectRefused(
attempt: Promise<unknown>,
cause: 'not_ready' | 'unheard'
@@ -164,10 +155,7 @@ async function expectRefused(
expect(error).toMatchObject({ unavailableCause: cause })
}
describe.each([
['hinted assign', hintedAssign],
['resolve', resolveRoute]
] as const)('a host offline through a roll redials via %s', (_route, redial) => {
describe('a host offline through a roll redials via hinted assign', () => {
describe('while the replacement process boots (not ready)', () => {
it('is re-placed when that process started after the roll isolated the cell', async () => {
const { store, first, heartbeat, at, isolateForRoll } = await setup()
@@ -175,7 +163,7 @@ describe.each([
await at(20_000)
await heartbeat(SOURCE, { incarnation: NEW_INCARNATION, startedAt: 15_000, ready: false })
const moved = await redial(store)
const moved = await hintedAssign(store)
expect(moved).toMatchObject({ cellId: TARGET.id })
expect(moved.assignmentEpoch).toBeGreaterThan(first.assignmentEpoch)
})
@@ -186,7 +174,7 @@ describe.each([
await at(20_000)
await heartbeat(SOURCE, { ready: false })
await expectRefused(redial(store), 'not_ready')
await expectRefused(hintedAssign(store), 'not_ready')
})
it('is refused while the new process reports holding connections', async () => {
@@ -200,7 +188,7 @@ describe.each([
totalConnections: 1
})
await expectRefused(redial(store), 'not_ready')
await expectRefused(hintedAssign(store), 'not_ready')
})
it('is refused once the new process itself goes unheard', async () => {
@@ -210,15 +198,7 @@ describe.each([
await heartbeat(SOURCE, { incarnation: NEW_INCARNATION, startedAt: 15_000, ready: false })
await at(20_000 + HEARTBEAT_TTL_MS + 1_000)
await expectRefused(redial(store), 'unheard')
})
it('is refused on an ordinary readiness dip with no roll stamp', async () => {
const { store, heartbeat, at } = await setup()
await at(20_000)
await heartbeat(SOURCE, { incarnation: NEW_INCARNATION, startedAt: 15_000, ready: false })
await expectRefused(redial(store), 'not_ready')
await expectRefused(hintedAssign(store), 'unheard')
})
it('is refused on a migration-only cell parked without a roll stamp', async () => {
@@ -227,20 +207,10 @@ describe.each([
await at(20_000)
await heartbeat(SOURCE, { incarnation: NEW_INCARNATION, startedAt: 15_000, ready: false })
await expectRefused(redial(store), 'not_ready')
await expectRefused(hintedAssign(store), 'not_ready')
})
})
// Residual: the old process is past its TTL and the new one has not heartbeated yet, so
// nothing proves the drained sockets gone. The client's retry lands once the new one is heard.
it('is still refused between the old process going unheard and the new one heartbeating', async () => {
const { store, at, isolateForRoll } = await setup()
await isolateForRoll()
await at(OLD_UNHEARD_AT)
await expectRefused(redial(store), 'unheard')
})
it('is refused when the roll stamp is stale, even with a new process', async () => {
const { store, heartbeat, at, isolateForRoll } = await setup()
await isolateForRoll()
@@ -248,7 +218,7 @@ describe.each([
await at(stale)
await heartbeat(SOURCE, { incarnation: NEW_INCARNATION, startedAt: stale - 1, ready: false })
await expectRefused(redial(store), 'not_ready')
await expectRefused(hintedAssign(store), 'not_ready')
})
})
@@ -1,134 +0,0 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from './assignment-store.js'
import { openRelayDatabase, type RelayDatabase } from '../database/database.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
const cells = [
{
id: 'row-lock-order-a',
url: 'https://row-lock-order-a.example.com',
capacityRequests: 200,
connectionHardCap: 600 as const,
connectionUnobservedBound: 50
},
{
id: 'row-lock-order-b',
url: 'https://row-lock-order-b.example.com',
capacityRequests: 200,
connectionHardCap: 600 as const,
connectionUnobservedBound: 50
}
]
const userId = 'row-lock-order-user'
const hosts = ['rowlockhost00001', 'rowlockhost00002', 'rowlockhost00003', 'rowlockhost00004'].map(
(relayHostId) => ({ userId, relayHostId })
)
// pg_stat_database exposes cycles hidden by three 40P01 retries; eventual success alone misses deadlocks that exhaust retries under load.
describePostgres('PostgreSQL row lock order', () => {
const databases: RelayDatabase[] = []
beforeAll(async () => {
for (let index = 0; index < 4; index++) {
databases.push(await openRelayDatabase({ databaseUrl, dataDir: '' }))
}
})
async function removeTestRows(database: RelayDatabase): Promise<void> {
await database.query(
`DELETE FROM relay_control_connection_reservations WHERE user_id = ?`,
[userId]
)
for (const table of [
'relay_control_capabilities',
'relay_assignment_activity_leases',
'relay_post_drain_migration_pins',
'relay_assignment_migration_incarnations',
'relay_assignment_migrations',
'relay_assignments'
]) {
await database.query(`DELETE FROM ${table} WHERE user_id = ?`, [userId])
}
for (const cell of cells) {
for (const table of [
'relay_cell_connection_snapshots',
'relay_cell_connection_runtime',
'relay_cell_connection_limits',
'relay_cell_runtime',
'relay_cells'
]) {
await database.query(`DELETE FROM ${table} WHERE cell_id = ?`, [cell.id])
}
}
}
afterAll(async () => {
if (databases[0]) await removeTestRows(databases[0])
for (const connection of databases) await connection.close()
})
async function deadlockCount(): Promise<number> {
const rows = await databases[0]!.query(
`SELECT deadlocks FROM pg_stat_database WHERE datname = current_database()`
)
return Number(rows[0]!.deadlocks)
}
it('runs the cell accept and the placement retry concurrently without a cycle', async () => {
await removeTestRows(databases[0]!)
const stores = databases.map((database) => new RelayAssignmentStore(database, () => 100))
await stores[0]!.reconcileCells(cells)
for (const cell of cells) {
await stores[0]!.recordCellHeartbeat({
cellId: cell.id,
cellUrl: cell.url,
cellIncarnation: '11111111-1111-4111-8111-111111111111',
startedAt: 50,
ready: true,
observedRequests: 0,
totalConnections: 0,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: 0,
connectionInclusionWatermark: 1,
connectionHardCap: 600,
connectionUnobservedBound: 50
})
}
const placements = new Map<string, { cellId: string; assignmentEpoch: number }>()
for (const identity of hosts) {
const assignment = await stores[0]!.assign(identity)
placements.set(identity.relayHostId, assignment)
}
const before = await deadlockCount()
// The accept path (host rows, then the cell row last) against the paths
// that must read the inventory first: placement and evacuation.
for (let round = 0; round < 12; round++) {
await Promise.allSettled(
hosts.flatMap((identity, index) => {
const placement = placements.get(identity.relayHostId)!
const store = stores[index % stores.length]!
const other = stores[(index + 1) % stores.length]!
return [
store.activateControl(identity, {
cellId: placement.cellId,
assignmentEpoch: placement.assignmentEpoch,
generation: round + 2
}),
other.assign(identity),
other.startEvacuation(
identity,
placement.cellId === cells[0]!.id ? cells[1]!.id : cells[0]!.id
)
]
})
)
}
const after = await deadlockCount()
expect(after - before).toBe(0)
}, 120_000)
})
@@ -1,83 +1,14 @@
import pg from 'pg'
import { afterAll, afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment-store.js'
import type { RelayCellConfig } from '../../config.js'
import {
openInMemoryRelayDatabase,
openRelayDatabase,
type RelayDatabase,
type SqlRow
} from '../../database/database.js'
import { openInMemoryRelayDatabase, type RelayDatabase } from '../../database/database.js'
// Both dialects must skip host scans with no actionable cells; stale existing-only rows previously scanned every assignment for nothing.
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const schema = 'relay_dead_cell_precheck_test'
const HOST_QUERY = 'FROM relay_assignments assignment'
const CELLS: RelayCellConfig[] = [
{ id: 'cell-a', url: 'https://relay-a.example.com', capacityRequests: 10 },
{ id: 'cell-b', url: 'https://relay-b.example.com', capacityRequests: 10 }
]
class RecordingDatabase implements RelayDatabase {
readonly statements: string[] = []
constructor(private readonly delegate: RelayDatabase) {}
get dialect() {
return this.delegate.dialect
}
async query(sql: string, params: unknown[] = []): Promise<SqlRow[]> {
this.statements.push(sql)
return await this.delegate.query(sql, params)
}
async queryLocked(...args: Parameters<RelayDatabase['queryLocked']>): Promise<SqlRow[]> {
return await this.delegate.queryLocked(...args)
}
async transaction<T>(operation: (transaction: RelayDatabase) => Promise<T>): Promise<T> {
return await this.delegate.transaction(operation)
}
async close(): Promise<void> {
await this.delegate.close()
}
}
function scopedUrl(): string {
const url = new URL(databaseUrl!)
url.searchParams.set('options', `-c search_path=${schema}`)
return url.toString()
}
async function onAdmin(sql: string): Promise<void> {
const client = new pg.Client({ connectionString: databaseUrl })
await client.connect()
try {
await client.query(sql)
} finally {
await client.end()
}
}
const dialects: [string, () => Promise<RelayDatabase>][] = [
['sqlite', openInMemoryRelayDatabase],
...(databaseUrl
? [
[
'postgres',
async () => {
await onAdmin(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
await onAdmin(`CREATE SCHEMA ${schema}`)
return await openRelayDatabase({ databaseUrl: scopedUrl(), dataDir: '' })
}
] as [string, () => Promise<RelayDatabase>]
]
: [])
]
describe.each(dialects)('dead-cell evacuation pre-check (%s)', (_dialect, open) => {
describe('dead-cell evacuation pre-check', () => {
let database: RelayDatabase | undefined
afterEach(async () => {
@@ -85,19 +16,14 @@ describe.each(dialects)('dead-cell evacuation pre-check (%s)', (_dialect, open)
database = undefined
})
afterAll(async () => {
if (databaseUrl) await onAdmin(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
})
async function setup(now: () => number) {
database = await open()
const recording = new RecordingDatabase(database)
const store = new RelayAssignmentStore(recording, now, {
database = await openInMemoryRelayDatabase()
const store = new RelayAssignmentStore(database, now, {
requireLiveCells: true,
heartbeatTtlMs: 45_000
})
await store.reconcileCells(CELLS)
return { store, recording }
return { store }
}
async function heartbeat(store: RelayAssignmentStore, cell: RelayCellConfig): Promise<void> {
@@ -111,31 +37,9 @@ describe.each(dialects)('dead-cell evacuation pre-check (%s)', (_dialect, open)
})
}
it('skips the host query when the only dead cell is unfenced and existing-only', async () => {
let now = 100
const { store, recording } = await setup(() => now)
for (const cell of CELLS) await heartbeat(store, cell)
const identity = { userId: 'user-a', relayHostId: 'host000000000001' }
await store.setCellEnabled('cell-b', false)
expect(await store.assign(identity)).toMatchObject({ cellId: 'cell-a' })
await store.setCellEnabled('cell-b', true)
await store.setCellEnabled('cell-a', false)
now += 45_001
await heartbeat(store, CELLS[1]!)
recording.statements.length = 0
expect(await store.evacuateDeadCells()).toBe(0)
expect(recording.statements.some((sql) => sql.includes(HOST_QUERY))).toBe(false)
expect(
await database!.query(`SELECT cell_id FROM relay_assignments WHERE user_id = ?`, [
identity.userId
])
).toEqual([{ cell_id: 'cell-a' }])
})
it('still evacuates hosts from a dead uncapped cell that admits', async () => {
let now = 100
const { store, recording } = await setup(() => now)
const { store } = await setup(() => now)
for (const cell of CELLS) await heartbeat(store, cell)
const identity = { userId: 'user-a', relayHostId: 'host000000000001' }
await store.setCellEnabled('cell-b', false)
@@ -143,10 +47,8 @@ describe.each(dialects)('dead-cell evacuation pre-check (%s)', (_dialect, open)
await store.setCellEnabled('cell-b', true)
now += 45_001
await heartbeat(store, CELLS[1]!)
recording.statements.length = 0
expect(await store.evacuateDeadCells()).toBe(1)
expect(recording.statements.some((sql) => sql.includes(HOST_QUERY))).toBe(true)
expect((await store.resolve(identity))?.cellId).toBe('cell-b')
})
})
@@ -1,54 +0,0 @@
import { expect, it } from 'vitest'
import { createDrainMigrationRowLookup } from './drain-migration-row-lookup.js'
import type { SqlRow } from '../../database/database.js'
function text(row: SqlRow, field: string): string {
const value = row[field]
if (typeof value !== 'string') {
throw new Error(`invalid_${field}`)
}
return value
}
it('keeps first assignment matches, lease order, row identity, and separate identity components', () => {
const rows = [
{ user_id: 'a:b', relay_host_id: 'c', value: 1 },
{ user_id: 'a', relay_host_id: 'b:c', value: 2 },
{ user_id: 'a:b', relay_host_id: 'c', value: 3 },
{ user_id: '', relay_host_id: '', value: 4 }
]
const lookup = createDrainMigrationRowLookup(rows, text)
for (const identity of [
{ userId: 'a:b', relayHostId: 'c' },
{ userId: 'a', relayHostId: 'b:c' },
{ userId: '', relayHostId: '' },
{ userId: 'missing', relayHostId: 'c' }
]) {
const expected = rows.filter(
(row) =>
row.user_id === identity.userId && row.relay_host_id === identity.relayHostId
)
expect(lookup.first(identity)).toBe(expected[0])
expect(lookup.all(identity)).toEqual(expected)
lookup.all(identity).forEach((row, index) => expect(row).toBe(expected[index]))
}
})
it('retains lazy validation and short circuiting when an inventory is malformed', () => {
const first = { user_id: 'user', relay_host_id: 'host' }
const identity = { userId: 'user', relayHostId: 'host' }
const lookup = createDrainMigrationRowLookup(
[first, { user_id: null, relay_host_id: 'bad' }],
text
)
expect(lookup.first(identity)).toBe(first)
expect(() => lookup.all(identity)).toThrow('invalid_user_id')
const unrelated = createDrainMigrationRowLookup(
[first, { user_id: 'other', relay_host_id: null }],
text
)
expect(unrelated.all(identity)).toEqual([first])
expect(() => unrelated.first({ userId: 'other', relayHostId: 'host' })).toThrow(
'invalid_relay_host_id'
)
})
@@ -1,122 +0,0 @@
import { ASSIGNMENT_LIMITS } from '@orca-cloud/relay-contract/assignment-invariants'
import { afterEach, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment-store.js'
import { openInMemoryRelayDatabase, type RelayDatabase, type SqlRow } from '../../database/database.js'
let database: RelayDatabase | undefined
afterEach(async () => await database?.close())
it.each([false, true])(
'looks up a whole drain inventory with linear identity reads (expired: %s)',
async (expired) => {
database = await openInMemoryRelayDatabase()
let measuring = false
let identityReads = 0
let indexedRows = 0
const instrument = (delegate: RelayDatabase): RelayDatabase => ({
dialect: delegate.dialect,
query: (sql, params) => delegate.query(sql, params),
queryLocked: async (sql, params, options) => {
const rows = await delegate.queryLocked(sql, params, options)
if (
!measuring ||
!sql.includes('WHERE EXISTS') ||
!(sql.includes('SELECT assignment.*') || sql.includes('SELECT lease.*'))
) {
return rows
}
indexedRows += rows.length
return rows.map(
(row): SqlRow =>
new Proxy(row, {
get(target, key) {
if (key === 'user_id' || key === 'relay_host_id') {
identityReads++
}
return Reflect.get(target, key)
}
})
)
},
transaction: (operation, options) =>
delegate.transaction((tx) => operation(instrument(tx)), options),
close: () => delegate.close()
})
let now = 100
const store = new RelayAssignmentStore(instrument(database), () => now, {
requireLiveCells: true
})
const cells = ['a', 'b'].map((id) => ({
id: `cell-${id}`,
url: `https://relay-${id}.example.com`,
capacityRequests: 500
}))
await store.reconcileCells(cells)
const incarnation = '11111111-1111-4111-8111-111111111111'
for (const cell of cells) {
await store.recordCellHeartbeat({
cellId: cell.id,
cellUrl: cell.url,
cellIncarnation: incarnation,
startedAt: 50,
ready: true,
observedRequests: 0
})
}
await store.setCellEnabled('cell-b', false)
const identities = Array.from({ length: 50 }, (_, index) => ({
userId: `user-${index % 5}`,
relayHostId: `host${String(index).padStart(12, '0')}`
}))
for (const identity of identities) {
await store.assign(identity)
}
await store.setCellEnabled('cell-b', true)
await store.setCellEnabled('cell-a', false)
for (const identity of identities) {
const migration = await store.startEvacuation(identity, 'cell-b')
await store.markMigrationTargetRegistered(identity, {
cellId: 'cell-b',
assignmentEpoch: migration.assignmentEpoch
})
}
const attempt = {
attemptId: '55555555-5555-4555-8555-555555555555',
cellId: 'cell-a',
cellIncarnation: incarnation,
traceValue: '66666666-6666-4666-8666-666666666666',
plannedGraceMs: 120_000
}
await store.prepareCellDrainAttempt(attempt)
if (expired) {
now += ASSIGNMENT_LIMITS.migrationLeaseMs + 1
await store.releaseExpiredActivityLeases()
for (const cell of cells) {
await store.recordCellHeartbeat({
cellId: cell.id,
cellUrl: cell.url,
cellIncarnation: incarnation,
startedAt: 50,
ready: true,
observedRequests: 0
})
}
}
measuring = true
await expect(store.beginCellDrainSend(attempt)).resolves.toMatchObject({
state: 'send-may-have-started',
shouldSend: true
})
expect(indexedRows).toBeGreaterThanOrEqual(100)
expect(identityReads).toBeLessThanOrEqual(indexedRows * 2)
const migrations = await database.query(
'SELECT expires_at FROM relay_assignment_migrations'
)
expect(migrations).toHaveLength(50)
expect(
migrations.every(
(row) => row.expires_at === now + ASSIGNMENT_LIMITS.migrationLeaseMs
)
).toBe(true)
}
)
@@ -1,282 +0,0 @@
import { createHash } from 'node:crypto'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment-store.js'
import {
encodeMembership,
type CellAdmissionMembership,
type CellAdmissionState
} from '../../cell/admission/cell-admission-selector.js'
import type { RelayCellConfig } from '../../config.js'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
const DIRECTOR_INSTANCES = 5
const DIRECTOR_POOL_MAX = 3
const DIRECTOR_APPLICATION = 'drain-release-contention-director'
const USER_PREFIX = 'drain-release-contention'
const NOW = 1_000_000
const CAPPED = {
capacityRequests: 6_000,
connectionHardCap: 3_000,
connectionUnobservedBound: 60
} as const
const SOURCE: RelayCellConfig = {
id: 'drain-release-contention-a-source',
url: 'https://drain-release-contention-source.example.test',
region: 'asia-east2',
...CAPPED
}
const TARGETS: RelayCellConfig[] = ['b', 'c', 'd'].map((suffix) => ({
id: `drain-release-contention-${suffix}-target`,
url: `https://drain-release-contention-${suffix}-target.example.test`,
region: 'asia-east2',
...CAPPED
}))
// Next to the database. Taken only once every Asia neighbour is at its cap.
const US_CELL: RelayCellConfig = {
id: 'drain-release-contention-e-us',
url: 'https://drain-release-contention-us.example.test',
region: 'us-central1',
...CAPPED
}
const CELLS = [SOURCE, ...TARGETS, US_CELL]
type Identity = { userId: string; relayHostId: string }
type Instance = { database: RelayDatabase; store: RelayAssignmentStore }
function hostIdentity(index: number): Identity {
return {
userId: `${USER_PREFIX}-${index}`,
// Relay host ids are fixed-width opaque ids.
relayHostId: `drainrel${String(index).padStart(8, '0')}`
}
}
describePostgres('PostgreSQL drain releases against director placement', () => {
const storeOptions = { requireLiveCells: true, heartbeatTtlMs: 45_000 }
const directors: Instance[] = []
let observer: RelayDatabase
function admin(): Instance {
return directors[0]!
}
async function applySelector(
states: Record<string, CellAdmissionState>,
rollIsolatedCells?: string[]
): Promise<void> {
const current = await admin().store.inspectCellAdmissionSelector()
// Built from relay_cells: the apply requires exact coverage of a shared fleet.
const fleet = await admin().database.query(
`SELECT cell_id FROM relay_cells ORDER BY cell_id ASC`
)
const membership: CellAdmissionMembership = {
existingOnly: [],
migrationOnly: [],
general: []
}
for (const row of fleet) {
const cellId = String(row['cell_id'])
const state =
states[cellId] ??
(current.selector.membership.existingOnly.includes(cellId)
? 'existing-only'
: current.selector.membership.migrationOnly.includes(cellId)
? 'migration-only'
: 'general')
if (state === 'existing-only') membership.existingOnly.push(cellId)
else if (state === 'migration-only') membership.migrationOnly.push(cellId)
else membership.general.push(cellId)
}
await admin().store.applyCellAdmissionSelector({
attemptId: `drain-release-${current.selector.generation}`,
expectedGeneration: current.selector.generation,
...(current.selector.generation === 0
? {
expectedMembershipSha256: createHash('sha256')
.update(encodeMembership(current.selector.membership))
.digest('hex')
}
: {}),
membership,
...(rollIsolatedCells ? { rollIsolatedCells } : {})
})
}
// Other Postgres files write admission through the generation-0 helpers, so
// the selector goes back to generation 0 exactly as found.
async function resetSelectorBoundary(): Promise<void> {
await admin().database.query(
`UPDATE relay_admission_selectors SET generation = 0, attempt_id = NULL
WHERE selector_id = 'general'`
)
await admin().database.query(
`DELETE FROM relay_admission_selector_intents WHERE attempt_id LIKE 'drain-release-%'`
)
await admin().store.reconcileCells([], false)
}
async function deleteHostRows(): Promise<void> {
for (const table of [
'relay_control_capabilities',
'relay_control_connection_reservations',
'relay_assignment_activity_leases',
'relay_assignment_migrations',
'relay_assignment_region_preferences',
'relay_assignments'
]) {
await admin().database.query(`DELETE FROM ${table} WHERE user_id LIKE '${USER_PREFIX}-%'`)
}
}
// `atCap` reports those cells at their connection cap: no headroom left.
async function heartbeatAll(atCap: RelayCellConfig[] = []): Promise<void> {
for (const [index, config] of CELLS.entries()) {
await admin().store.recordCellHeartbeat({
cellId: config.id,
cellUrl: config.url,
cellIncarnation: `2222222${index}-2222-4222-8222-222222222222`,
startedAt: NOW - 1_000,
ready: true,
observedRequests: 0,
region: config.region,
// The cell checks enforced = total + in-flight + reserved.
totalConnections: atCap.includes(config) ? CAPPED.connectionHardCap : 0,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: atCap.includes(config) ? CAPPED.connectionHardCap : 0,
connectionHardCap: CAPPED.connectionHardCap,
connectionUnobservedBound: CAPPED.connectionUnobservedBound
})
}
}
// Holds `cellIds`' relay_cells rows in an open transaction until the returned release runs.
async function holdCellRows(cellIds: string[]): Promise<() => Promise<void>> {
let release!: () => void
const released = new Promise<void>((resolve) => (release = resolve))
let held!: () => void
const acquired = new Promise<void>((resolve) => (held = resolve))
const holder = observer.transaction(async (transaction) => {
await transaction.queryLocked(
`SELECT cell_id FROM relay_cells WHERE cell_id IN (${cellIds.map(() => '?').join(', ')})
ORDER BY cell_id ASC`,
cellIds
)
held()
await released
})
await acquired
return async () => {
release()
await holder
}
}
// Every host holds a live control lease on the source, then the source is
// isolated for a roll: the state an Asia drain starts from.
async function seed(hosts: Identity[]): Promise<void> {
await deleteHostRows()
await admin().database.query(
`UPDATE relay_cells SET reserved_requests = 0 WHERE cell_id LIKE '${USER_PREFIX}-%'`
)
// Restoring the source to general also clears the previous run's roll stamp.
await applySelector({
[SOURCE.id]: 'general',
...Object.fromEntries(TARGETS.map((target) => [target.id, 'migration-only' as const]))
})
for (const identity of hosts) {
const grant = await admin().store.assign(identity, 'asia-east2', 'asia-east2')
expect(grant.cellId).toBe(SOURCE.id)
await admin().store.activateControl(identity, {
cellId: SOURCE.id,
assignmentEpoch: grant.assignmentEpoch,
generation: 1
})
}
await applySelector(
Object.fromEntries(TARGETS.map((target) => [target.id, 'general' as const]))
)
await applySelector({ [SOURCE.id]: 'migration-only' }, [SOURCE.id])
}
beforeAll(async () => {
for (let index = 0; index < DIRECTOR_INSTANCES; index += 1) {
const database = await openRelayDatabase({
databaseUrl,
dataDir: '',
poolMax: DIRECTOR_POOL_MAX,
applicationName: DIRECTOR_APPLICATION
})
directors.push({
database,
store: new RelayAssignmentStore(database, () => NOW, storeOptions)
})
}
observer = await openRelayDatabase({ databaseUrl, dataDir: '', poolMax: 1 })
await deleteHostRows()
await admin().store.reconcileCells(CELLS, false)
await resetSelectorBoundary()
await heartbeatAll()
})
afterAll(async () => {
if (directors.length > 0) {
await deleteHostRows()
// Cells before the selector rebuild, or its membership names missing rows.
for (const config of CELLS) {
for (const table of [
'relay_cell_connection_snapshots',
'relay_cell_connection_runtime',
'relay_cell_connection_limits',
'relay_cell_runtime',
'relay_cell_admission',
'relay_cell_regions',
'relay_cells'
]) {
await admin().database.query(`DELETE FROM ${table} WHERE cell_id = ?`, [config.id])
}
}
await resetSelectorBoundary()
}
for (const instance of directors) {
await instance.database.close()
}
await observer?.close()
})
it('places a reconnecting host off the isolated source while that source row is held', async () => {
await heartbeatAll()
const host = hostIdentity(9_000)
await seed([host])
// A drain release holds the source row for its whole transaction; target-only placement never asks for it.
const sourceHeld = await holdCellRows([SOURCE.id])
try {
const grant = await Promise.race([
directors[1]!.store.assign(host, 'asia-east2', 'asia-east2'),
new Promise<'blocked'>((resolve) => setTimeout(() => resolve('blocked'), 3_000))
])
expect(grant).not.toBe('blocked')
expect(TARGETS.map(({ id }) => id)).toContain(grant === 'blocked' ? null : grant.cellId)
} finally {
await sourceHeld()
}
// Control: the same hold on the target rows does block a placement, so the hold above was real.
const other = hostIdentity(9_001)
await seed([other])
const targetsHeld = await holdCellRows(TARGETS.map(({ id }) => id))
const placement = directors[2]!.store.assign(other, 'asia-east2', 'asia-east2')
try {
const raced = await Promise.race([
placement.then(() => 'placed' as const),
new Promise<'blocked'>((resolve) => setTimeout(() => resolve('blocked'), 1_000))
])
expect(raced).toBe('blocked')
} finally {
await targetsHeld()
}
expect((await placement).cellId).not.toBe(SOURCE.id)
}, 30_000)
})
@@ -256,7 +256,7 @@ describePostgres('PostgreSQL migration recovery', () => {
expect(
await database.query(
`SELECT assignment.cell_id, assignment.assignment_epoch,
migration.completed_at, migration.aborted_at
(migration.completed_at IS NOT NULL) AS completed, migration.aborted_at
FROM relay_assignments assignment
JOIN relay_assignment_migrations migration
ON migration.user_id = assignment.user_id
@@ -269,7 +269,7 @@ describePostgres('PostgreSQL migration recovery', () => {
{
cell_id: input.cells.failed.id,
assignment_epoch: '2',
completed_at: String(100 + 45_001 + ASSIGNMENT_LIMITS.migrationLeaseMs + 1),
completed: true,
aborted_at: null
}
])
@@ -309,7 +309,7 @@ describePostgres('PostgreSQL migration recovery', () => {
expect(
await database.query(
`SELECT assignment.cell_id, assignment.assignment_epoch,
migration.completed_at, migration.aborted_at
migration.completed_at, (migration.aborted_at IS NOT NULL) AS aborted
FROM relay_assignments assignment
JOIN relay_assignment_migrations migration
ON migration.user_id = assignment.user_id
@@ -323,9 +323,7 @@ describePostgres('PostgreSQL migration recovery', () => {
cell_id: input.cells.source.id,
assignment_epoch: '3',
completed_at: null,
aborted_at: String(
100 + ASSIGNMENT_LIMITS.migrationLeaseMs + STRANDED_MIGRATION_ABANDON_MS + 1
)
aborted: true
}
])
})
@@ -394,63 +392,6 @@ describePostgres('PostgreSQL migration recovery', () => {
])
})
it('fails a competing aggregate supersession instead of reporting the wrong target', async () => {
const input = await fixture()
const alternate = {
id: `recovery-cell-${sequence}-alternate`,
url: `https://recovery-${sequence}-alternate.example.com`,
capacityRequests: 20
}
await input.store.reconcileCells([...Object.values(input.cells), alternate], false)
await input.heartbeat(alternate)
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.heartbeat(alternate)
await input.store.attestCellFence(input.cells.failed.id, input.cellIncarnation)
const results = await Promise.allSettled([
input.store.supersedeRegisteredCellEvacuations(
input.cells.source.id,
input.cells.failed.id,
input.cells.replacement.id,
100
),
input.store.supersedeRegisteredCellEvacuations(
input.cells.source.id,
input.cells.failed.id,
alternate.id,
100
)
])
const fulfilled = results.filter(
(result): result is PromiseFulfilledResult<number> => result.status === 'fulfilled'
)
const rejected = results.filter(
(result): result is PromiseRejectedResult => result.status === 'rejected'
)
expect(fulfilled).toHaveLength(1)
expect(fulfilled[0]!.value).toBe(1)
expect(rejected).toHaveLength(1)
expect(rejected[0]!.reason).toMatchObject({ message: 'migration_already_superseded' })
const successor = await database.query(
`SELECT assignment.cell_id, migration.target_cell_id
FROM relay_assignments assignment
JOIN relay_assignment_migrations migration
ON migration.user_id = assignment.user_id
AND migration.relay_host_id = assignment.relay_host_id
AND migration.assignment_epoch = assignment.assignment_epoch
WHERE assignment.user_id = ? AND migration.previous_epoch = ?`,
[input.identity.userId, input.migration.assignmentEpoch]
)
expect(successor).toHaveLength(1)
expect(successor[0]!.cell_id).toBe(successor[0]!.target_cell_id)
expect([input.cells.replacement.id, alternate.id]).toContain(
successor[0]!.target_cell_id
)
})
it('rolls back every supersession change when replacement capacity is insufficient', async () => {
const input = await fixture(1)
await input.store.setCellEnabled(input.cells.failed.id, false)
@@ -184,7 +184,7 @@ describePostgres('PostgreSQL drain-send locking', () => {
})
now = migration.expiresAt + 1
expect(await store.releaseExpiredActivityLeases()).toBeGreaterThan(0)
await store.releaseExpiredActivityLeases()
await store.recordCellHeartbeat({
cellId: source.id,
cellUrl: source.url,
@@ -207,7 +207,7 @@ describePostgres('PostgreSQL drain-send locking', () => {
cellId: source.id,
cellIncarnation: sourceIncarnation
})
).resolves.toMatchObject({ state: 'send-may-have-started', shouldSend: true })
).resolves.toMatchObject({ shouldSend: true })
await expect(
database.query(
`SELECT activity_kind, cell_id FROM relay_assignment_activity_leases
@@ -1,139 +0,0 @@
import pg from 'pg'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
import { REGISTERED_MIGRATION_ABANDON_MS } from './registered-migration-abandonment.js'
import { readRegisteredMigrationInventory } from './registered-migration-inventory.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
const schema = 'relay_migration_inventory_test'
describePostgres('PostgreSQL registered migration inventory', () => {
let database: RelayDatabase | undefined
beforeAll(async () => {
const client = new pg.Client({ connectionString: databaseUrl })
await client.connect()
try {
await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
await client.query(`CREATE SCHEMA ${schema}`)
} finally {
await client.end()
}
const url = new URL(databaseUrl!)
url.searchParams.set('options', `-c search_path=${schema}`)
database = await openRelayDatabase({ databaseUrl: url.toString(), dataDir: '' })
})
afterAll(async () => {
await database?.close()
const client = new pg.Client({ connectionString: databaseUrl })
await client.connect()
try {
await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
} finally {
await client.end()
}
})
it('counts a disabled-target migration even while its source is active', async () => {
const now = REGISTERED_MIGRATION_ABANDON_MS + 10_000
await database!.query(
`INSERT INTO relay_cells
(cell_id, cell_url, enabled, capacity_requests, reserved_requests,
observed_requests, last_heartbeat_at, updated_at)
VALUES ('cell-a', 'https://a.example.test', 1, 4000, 1, 0, 0, 1),
('cell-b', 'https://b.example.test', 0, 4000, 0, 0, 0, 1)`
)
await database!.query(
`INSERT INTO relay_cell_admission (cell_id, admission_state, updated_at)
VALUES ('cell-a', 'general', 1), ('cell-b', 'existing-only', 1)`
)
await database!.query(
`INSERT INTO relay_assignments
(user_id, relay_host_id, cell_id, assignment_epoch, lease_expires_at,
last_activity_at, reserved_controls, reserved_splices, reserved_invites,
pending_installs, pending_confirmations, migration_leases)
VALUES ('user-1', '111111111111', 'cell-b', 2, ?, ?, 0, 0, 0, 0, 0, 0)`,
[now, now]
)
await database!.query(
`INSERT INTO relay_assignment_activity_leases
(user_id, relay_host_id, activity_id, activity_kind, cell_id,
request_units, expires_at, updated_at)
VALUES ('user-1', '111111111111', 'control:source:1', 'control', 'cell-a', 1, ?, ?)`,
[now, now]
)
await database!.query(
`INSERT INTO relay_assignment_migrations
(user_id, relay_host_id, source_cell_id, target_cell_id, previous_epoch,
assignment_epoch, source_request_units, target_reserved_units, expires_at,
target_registered_at, created_at, updated_at)
VALUES ('user-1', '111111111111', 'cell-a', 'cell-b', 1, 2, 1, 2, 2, 2, 1, 2)`
)
expect(await readRegisteredMigrationInventory(database!, now)).toEqual({
open: 1,
inactive: 1,
abandoned: 1,
pairs: [
{
sourceCellId: 'cell-a',
targetCellId: 'cell-b',
open: 1,
inactive: 1,
abandoned: 1
}
]
})
await database!.query(
`INSERT INTO relay_cells
(cell_id, cell_url, enabled, capacity_requests, reserved_requests,
observed_requests, last_heartbeat_at, updated_at)
VALUES ('cell-c', 'https://c.example.test', 1, 4000, 0, 0, 0, 1)`
)
await database!.query(
`INSERT INTO relay_cell_admission (cell_id, admission_state, updated_at)
VALUES ('cell-c', 'migration-only', 1)`
)
await database!.query(
`INSERT INTO relay_assignments
(user_id, relay_host_id, cell_id, assignment_epoch, lease_expires_at,
last_activity_at, reserved_controls, reserved_splices, reserved_invites,
pending_installs, pending_confirmations, migration_leases)
VALUES ('user-2', '222222222222', 'cell-c', 2, ?, ?, 0, 0, 0, 0, 0, 0)`,
[now, now]
)
await database!.query(
`INSERT INTO relay_assignment_migrations
(user_id, relay_host_id, source_cell_id, target_cell_id, previous_epoch,
assignment_epoch, source_request_units, target_reserved_units, expires_at,
target_registered_at, created_at, updated_at)
VALUES ('user-2', '222222222222', 'cell-a', 'cell-c', 1, 2, 1, 2, ?, NULL, 1, 2)`,
[now + 60_000]
)
expect(await readRegisteredMigrationInventory(database!, now)).toEqual({
open: 2,
inactive: 1,
abandoned: 1,
pairs: [
{
sourceCellId: 'cell-a',
targetCellId: 'cell-b',
open: 1,
inactive: 1,
abandoned: 1
},
{
sourceCellId: 'cell-a',
targetCellId: 'cell-c',
open: 1,
inactive: 0,
abandoned: 0
}
]
})
})
})
@@ -1,120 +0,0 @@
import { afterEach, describe, expect, it } from 'vitest'
import { openInMemoryRelayDatabase, type RelayDatabase } from '../../database/database.js'
import { REGISTERED_MIGRATION_ABANDON_MS } from './registered-migration-abandonment.js'
import {
formatRegisteredMigrationInventory,
readRegisteredMigrationInventory
} from './registered-migration-inventory.js'
describe('registered migration inventory', () => {
const databases: RelayDatabase[] = []
afterEach(async () => {
for (const database of databases.splice(0)) {
await database.close()
}
})
it('does not restart abandonment when an old source migration lease is refreshed', async () => {
const database = await openInMemoryRelayDatabase()
databases.push(database)
const now = REGISTERED_MIGRATION_ABANDON_MS + 10_000
await insertCell(database, 'cell-a', false, 'existing-only')
await insertCell(database, 'cell-b', true, 'migration-only')
await insertMigration(database, now - 1)
const fresh = await readRegisteredMigrationInventory(database, now)
expect(fresh).toEqual({
open: 1,
inactive: 1,
abandoned: 1,
pairs: [
{
sourceCellId: 'cell-a',
targetCellId: 'cell-b',
open: 1,
inactive: 1,
abandoned: 1
}
]
})
expect(formatRegisteredMigrationInventory(fresh)).toEqual([
'[orca-relay] migration inventory open=1 expiredRegisteredInactive=1 abandonedRegistered=1',
'[orca-relay] migration pair sourceCellId=cell-a targetCellId=cell-b open=1 expiredRegisteredInactive=1 abandoned=1'
])
await database.query(`UPDATE relay_assignment_migrations SET expires_at = ?, updated_at = ?`, [
now + 1,
now
])
expect((await readRegisteredMigrationInventory(database, now + 1)).abandoned).toBe(1)
})
it('includes unregistered open migrations without logging a host identity', async () => {
const database = await openInMemoryRelayDatabase()
databases.push(database)
const now = 10_000
await insertCell(database, 'cell-a', false, 'existing-only')
await insertCell(database, 'cell-b', true, 'migration-only')
await insertMigration(database, now + 60_000, null)
const inventory = await readRegisteredMigrationInventory(database, now)
expect(inventory).toEqual({
open: 1,
inactive: 0,
abandoned: 0,
pairs: [
{
sourceCellId: 'cell-a',
targetCellId: 'cell-b',
open: 1,
inactive: 0,
abandoned: 0
}
]
})
expect(formatRegisteredMigrationInventory(inventory).join('\n')).not.toContain('111111111111')
})
})
async function insertCell(
database: RelayDatabase,
cellId: string,
enabled: boolean,
admissionState: string
): Promise<void> {
await database.query(
`INSERT INTO relay_cells
(cell_id, cell_url, enabled, capacity_requests, reserved_requests,
observed_requests, last_heartbeat_at, updated_at)
VALUES (?, ?, ?, 4000, 0, 0, 0, 1)`,
[cellId, `https://${cellId}.example.test`, enabled ? 1 : 0]
)
await database.query(
`INSERT INTO relay_cell_admission (cell_id, admission_state, updated_at)
VALUES (?, ?, 1)`,
[cellId, admissionState]
)
}
async function insertMigration(
database: RelayDatabase,
expiresAt: number,
targetRegisteredAt: number | null = 2
): Promise<void> {
await database.query(
`INSERT INTO relay_assignments
(user_id, relay_host_id, cell_id, assignment_epoch, lease_expires_at,
last_activity_at, reserved_controls, reserved_splices, reserved_invites,
pending_installs, pending_confirmations, migration_leases)
VALUES ('user-1', '111111111111', 'cell-b', 2, ?, ?, 0, 0, 0, 0, 0, 0)`,
[expiresAt, expiresAt]
)
await database.query(
`INSERT INTO relay_assignment_migrations
(user_id, relay_host_id, source_cell_id, target_cell_id, previous_epoch,
assignment_epoch, source_request_units, target_reserved_units, expires_at,
target_registered_at, created_at, updated_at)
VALUES ('user-1', '111111111111', 'cell-a', 'cell-b', 1, 2, 1, 2, ?, ?, 1, 2)`,
[expiresAt, targetRegisteredAt]
)
}
@@ -1,23 +1,16 @@
import { describe, expect, it } from 'vitest'
import {
DRAIN_RETURN_MIN_RETRY_AFTER_SECONDS,
RelayDrainReturnAdmission,
type DrainReturnDeferral,
type DrainReturnGrant
} from './drain-return-admission.js'
import { RelayPublicAssignmentAdmission } from './public-assignment-admission.js'
type Timer = { at: number; callback: () => void; cancelled: boolean }
function harness(
overrides: {
maxQueued?: number
maxRetryAfterSeconds?: number
onServiceMs?: (durationMs: number) => void
} = {}
) {
let now = 0
const timers: Timer[] = []
const placement = new RelayPublicAssignmentAdmission({
maxConcurrent: 2,
maxQueued: 8,
@@ -26,32 +19,18 @@ function harness(
maxDrainReturnConcurrent: 1,
maxDrainReturnQueued: overrides.maxQueued ?? 0,
drainReturnWaitMs: 3_000,
drainReturnMinIntervalMs: DRAIN_RETURN_MIN_RETRY_AFTER_SECONDS * 1_000,
now: () => now,
schedule: (callback, delayMs) => {
const timer = { at: now + delayMs, callback, cancelled: false }
timers.push(timer)
return () => {
timer.cancelled = true
}
}
drainReturnMinIntervalMs: 2_000,
now: () => 0,
schedule: () => () => undefined
})
const lane = new RelayDrainReturnAdmission(placement, {
maxConcurrent: 1,
maxRetryAfterSeconds: overrides.maxRetryAfterSeconds ?? 300,
now: () => now,
onServiceMs: overrides.onServiceMs
maxRetryAfterSeconds: 300,
now: () => 0
})
return {
lane,
placement,
advance: (ms: number) => {
now += ms
for (const timer of timers.filter((entry) => !entry.cancelled && entry.at <= now)) {
timer.cancelled = true
timer.callback()
}
}
placement
}
}
@@ -60,64 +39,10 @@ function admitted(result: DrainReturnGrant | DrainReturnDeferral): DrainReturnGr
return result
}
function deferred(result: DrainReturnGrant | DrainReturnDeferral): DrainReturnDeferral {
if (result.kind !== 'deferred') throw new Error('expected a deferral')
return result
}
const host = (index: number): string => `host${String(index).padStart(12, '0')}`
describe('drain-return admission', () => {
it('shortens the pacing once the measured service time is short', async () => {
const { lane, advance } = harness()
for (let index = 0; index < 30; index++) {
const grant = admitted(await lane.acquire(host(index)))
advance(50)
grant.lease.release()
advance(2_000)
}
admitted(await lane.acquire(host(100)))
const retries = []
for (let index = 101; index <= 120; index++) {
retries.push(deferred(await lane.acquire(host(index))).retryAfterSeconds)
}
// ~50 ms per re-placement: twenty deferrals span about one second, where the
// 860 ms starting estimate would have spread them over seventeen.
expect(Math.max(...retries)).toBe(3)
})
// The unclamped sample: the EWMA's floor and ceiling would hide the real tail.
it('answers a host’s own early retry with its interval, not a place behind the cohort', async () => {
const { lane } = harness()
admitted(await lane.acquire(host(0))).lease.release()
// A row-busy refusal says "retry in 1 s"; that redial is the host's own.
const repeat = deferred(await lane.acquire(host(0)))
expect(repeat).toMatchObject({
reason: 'host-rate-limited',
retryAfterSeconds: DRAIN_RETURN_MIN_RETRY_AFTER_SECONDS
})
// And it booked nothing: the next overflow still gets the first slot.
admitted(await lane.acquire(host(1)))
expect(deferred(await lane.acquire(host(2))).retryAfterSeconds).toBe(2)
})
it('keeps a host’s reserved slot when it comes back early', async () => {
const { lane, advance } = harness()
admitted(await lane.acquire(host(0)))
for (let index = 1; index <= 4; index++) await lane.acquire(host(index))
expect(deferred(await lane.acquire(host(5))).retryAfterSeconds).toBe(6)
advance(1_000)
// Early and still full: the same instant, not a new slot after everyone else.
expect(deferred(await lane.acquire(host(5))).retryAfterSeconds).toBe(5)
})
it('lets a queued placement go before a queued drain return', async () => {
const { lane, placement } = harness({ maxQueued: 1 })
const drain = admitted(await lane.acquire(host(0)))
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest'
import { describe, expect, it } from 'vitest'
import { RelayPublicAssignmentAdmission } from './public-assignment-admission.js'
describe('public assignment admission', () => {
@@ -7,7 +7,7 @@ describe('public assignment admission', () => {
const admission = new RelayPublicAssignmentAdmission({
maxConcurrent: 1,
minIntervalMs: 0,
now: vi.fn(() => 0)
now: () => 0
})
const lease = await admission.acquire('host-a')
expect(lease).not.toBeNull()
@@ -46,19 +46,12 @@ describe('public assignment admission', () => {
})
it('gives a bounded reserved waiter the next public slot', async () => {
let expire!: () => void
let cancelled = false
const admission = new RelayPublicAssignmentAdmission({
maxConcurrent: 2,
maxReservedConcurrent: 1,
reservedWaitMs: 1_000,
minIntervalMs: 0,
schedule: (callback) => {
expire = callback
return () => {
cancelled = true
}
}
schedule: () => () => undefined
})
const first = await admission.acquire('host-a')
const second = await admission.acquire('host-b')
@@ -70,12 +63,10 @@ describe('public assignment admission', () => {
const reserved = await reservedPromise
expect(reserved).not.toBeNull()
expect(cancelled).toBe(true)
await expect(admission.acquire('host-d')).resolves.toBeNull()
reserved?.release()
await expect(admission.acquire('host-d')).resolves.not.toBeNull()
second?.release()
expire()
})
it('serializes same-host assignment and reserved recovery without losing priority', async () => {
@@ -100,34 +91,4 @@ describe('public assignment admission', () => {
await expect(admission.acquire('host-b')).resolves.not.toBeNull()
})
it('tells each caller its own rejection reason without crossing requests', async () => {
const admission = new RelayPublicAssignmentAdmission({
maxConcurrent: 1,
maxQueued: 1,
waitMs: 1_000,
maxReservedConcurrent: 1,
reservedWaitMs: 1_000,
minIntervalMs: 5_000,
now: () => 0,
schedule: () => () => undefined
})
const queued: string[] = []
const displaced: string[] = []
const throttled: string[] = []
const active = await admission.acquire('host-a')
// 'host-b' waits, then reserved recovery for the same host displaces it.
const ordinary = admission.acquire('host-b', (reason) => displaced.push(reason))
const reserved = admission.acquireReserved('host-b', (reason) => queued.push(reason))
await expect(ordinary).resolves.toBeNull()
active?.release()
const recovered = await reserved
recovered?.release()
await admission.acquire('host-a', (reason) => throttled.push(reason))
expect(displaced).toEqual(['superseded'])
expect(queued).toEqual([])
expect(throttled).toEqual(['host-rate-limited'])
})
})
@@ -1,55 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { createRelayApp } from '../../app.js'
import type { RelayConfig } from '../../config.js'
describe('public assignment circuit breaker', () => {
it('rejects assign and resolve without invoking relay state', async () => {
const assign = vi.fn()
const resolveResume = vi.fn()
const app = createRelayApp(config(), {
store: { resolveResume } as never,
assignments: { assign } as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
for (const path of ['/v1/assign', '/v1/resolve']) {
const response = await app.request(path, { method: 'POST' })
expect(response.status).toBe(503)
expect(response.headers.get('retry-after')).toBe('5')
expect(await response.json()).toEqual({ error: 'assignments_temporarily_unavailable' })
}
expect(assign).not.toHaveBeenCalled()
expect(resolveResume).not.toHaveBeenCalled()
expect((await app.request('/health')).status).toBe(200)
expect((await app.request('/v1/admin/drain', { method: 'POST' })).status).toBe(401)
})
})
function config(): RelayConfig {
return {
port: 8080,
publicUrl: 'https://relay.example.test',
cellUrl: 'https://relay.example.test',
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: new TextEncoder().encode('assignment-key-with-at-least-32-bytes'),
role: 'director',
cellId: 'director',
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
runtimeServiceAccount: 'runtime@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 3,
publicAssignmentsEnabled: false,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data'
}
}
@@ -1,6 +1,5 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
RelayAssignmentRowBusyError,
type RelayAssignment,
type ResolvedRelayAssignment
} from '../assignment-store.js'
@@ -21,7 +20,6 @@ vi.mock('../../authentication/relay-token-verifier.js', () => ({
import { createRelayApp } from '../../app.js'
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
})
@@ -36,15 +34,11 @@ describe('drain-return lane', () => {
const resolve = vi.fn(async ({ relayHostId }: { relayHostId: string }) =>
relayHostId === drained ? isolatedHome(relayHostId) : assignment('cell-o', relayHostId)
)
const outcomes: string[] = []
const servedLanes: string[] = []
const app = createRelayApp(config(), {
store: {} as never,
assignments: { assign, resolve } as never,
drain: vi.fn(),
ready: vi.fn(async () => true),
recordAssignmentAdmission: (outcome) => outcomes.push(outcome),
recordAdmissionServiceMs: (lane) => servedLanes.push(lane)
ready: vi.fn(async () => true)
})
// The re-placement holds the drain lane; the sticky lane's only slot is free.
@@ -56,125 +50,12 @@ describe('drain-return lane', () => {
)
expect(reconnect.status).toBe(200)
expect(resolve).toHaveBeenCalledWith(
{ userId: 'user-1', relayHostId: drained },
{ classifyHomeRollIsolation: true }
)
replacement.resolve(assignment('cell-new', drained))
expect((await moving).status).toBe(200)
expect(outcomes).toEqual(['drain-return', 'sticky'])
// The drained host held a sticky slot for its verification read too.
expect(servedLanes).toEqual(['sticky', 'sticky', 'drain-return'])
})
it('defers an overflowing drain return with a paced Retry-After and says why', async () => {
const holder = 'hhhhhhhhhhhhhhhh'
const replacement = deferred<RelayAssignment>()
const assign = vi.fn(async () => await replacement.promise)
const resolve = vi.fn(async ({ relayHostId }: { relayHostId: string }) =>
isolatedHome(relayHostId)
)
const outcomes: string[] = []
const reasons: string[] = []
const retryAfters: number[] = []
const causes: string[] = []
const app = createRelayApp(config({ drainReturnQueueMax: 1, drainReturnWaitMs: 50 }), {
store: {} as never,
assignments: { assign, resolve } as never,
drain: vi.fn(),
ready: vi.fn(async () => true),
recordAssignmentAdmission: (outcome) => outcomes.push(outcome),
recordAssignmentRejectionReason: (lane, reason) => reasons.push(`${lane}:${reason}`),
recordDrainReturnRetryAfter: (seconds) => retryAfters.push(seconds),
recordAssignmentUnavailable: (cause) => causes.push(cause)
})
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
const holding = app.request('/v1/assign', assignmentRequest(holder, { reconnect: true }))
await vi.waitFor(() => expect(assign).toHaveBeenCalledTimes(1))
const queued = app.request(
'/v1/assign',
assignmentRequest('qqqqqqqqqqqqqqqq', { reconnect: true })
)
const overflow = await app.request(
'/v1/assign',
assignmentRequest('ffffffffffffffff', { reconnect: true })
)
expect(overflow.status).toBe(503)
expect(overflow.headers.get('retry-after')).toBe('2')
const timedOut = await queued
expect(timedOut.status).toBe(503)
expect(Number(timedOut.headers.get('retry-after'))).toBeGreaterThanOrEqual(2)
expect(reasons).toEqual(['drain-return:queue-full', 'drain-return:wait-timeout'])
expect(retryAfters).toEqual([2, Number(timedOut.headers.get('retry-after'))])
expect(outcomes.filter((outcome) => outcome === 'drain-return-deferred')).toHaveLength(2)
expect(causes).toEqual(['drain-return-deferred', 'drain-return-deferred'])
expect(warn.mock.calls.flat().join('\n')).toMatch(
/lane=drain-return hinted=true reason=queue-full .*retryAfter=2/
)
replacement.resolve(assignment('cell-new', holder))
expect((await holding).status).toBe(200)
})
it('answers a drained host’s row-busy redial with the short interval', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] })
vi.spyOn(console, 'warn').mockImplementation(() => {})
const holder = 'hhhhhhhhhhhhhhhh'
const host = 'bbbbbbbbbbbbbbbb'
const holding = deferred<RelayAssignment>()
let busy = true
const assign = vi.fn(async ({ relayHostId }: { relayHostId: string }) => {
if (relayHostId === holder) return await holding.promise
if (relayHostId === host && busy) {
busy = false
throw new RelayAssignmentRowBusyError()
}
return assignment('cell-new', relayHostId)
})
const resolve = vi.fn(async ({ relayHostId }: { relayHostId: string }) =>
isolatedHome(relayHostId)
)
const app = createRelayApp(config({ drainReturnQueueMax: 1 }), {
store: {} as never,
assignments: { assign, resolve } as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
const request = (relayHostId: string) =>
Promise.resolve(
app.request('/v1/assign', assignmentRequest(relayHostId, { reconnect: true }))
)
const held = request(holder)
await vi.waitFor(() => expect(assign).toHaveBeenCalledTimes(1))
const queued = request(host)
await vi.advanceTimersByTimeAsync(10)
// A cohort behind it books the next ten service slots.
for (let index = 0; index < 10; index++) {
const overflow = await request(hostId('f', index))
expect(overflow.status).toBe(503)
}
await vi.advanceTimersByTimeAsync(2_490)
holding.resolve(assignment('cell-new', holder))
expect((await held).status).toBe(200)
const refused = await queued
expect(refused.headers.get('retry-after')).toBe('1')
await vi.advanceTimersByTimeAsync(1_000)
const redial = await request(host)
// Its own lane interval, not a slot after the ten hosts that came later.
expect(redial.status).toBe(503)
expect(redial.headers.get('retry-after')).toBe('2')
})
})
function hostId(prefix: string, index: number): string {
return `${prefix}${String(index).padStart(15, '0')}`
}
function assignmentRequest(relayHostId: string, extra: { reconnect?: boolean } = {}): RequestInit {
return {
method: 'POST',
@@ -209,7 +90,7 @@ function deferred<T>() {
return { promise, resolve }
}
function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
function config(): RelayConfig {
return {
port: 8080,
publicUrl: 'https://relay.example.test',
@@ -233,7 +114,6 @@ function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data',
...overrides
dataDir: './data'
}
}
@@ -18,68 +18,10 @@ import { createRelayApp } from '../../app.js'
describe('public assignment overload', () => {
it('fairly drains a bounded incident-scale burst without raising database concurrency', async () => {
const pending = new Map<string, ReturnType<typeof deferred<RelayAssignment>>>()
const admittedHosts: string[] = []
let active = 0
let highWater = 0
const assign = vi.fn(async ({ relayHostId }: { relayHostId: string }) => {
admittedHosts.push(relayHostId)
active++
highWater = Math.max(highWater, active)
const operation = deferred<RelayAssignment>()
pending.set(relayHostId, operation)
try {
return await operation.promise
} finally {
active--
pending.delete(relayHostId)
}
})
const app = createRelayApp(config(), {
store: {} as never,
assignments: { assign } as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
const hosts = Array.from(
{ length: 430 },
(_, index) => `host-${String(index).padStart(11, '0')}`
)
const firstWave = hosts.map((host) =>
app.request('/v1/assign', assignmentRequest(host))
)
await vi.waitFor(() => expect(assign).toHaveBeenCalledTimes(2))
const retryWave = await Promise.all(
hosts.map((host) => app.request('/v1/assign', assignmentRequest(host)))
)
expect(retryWave.every((response) => response.status === 503)).toBe(true)
expect(assign).toHaveBeenCalledTimes(2)
while (assign.mock.calls.length < 130) {
const activeOperations = [...pending]
const expectedCalls = assign.mock.calls.length + activeOperations.length
for (const [host, operation] of activeOperations) {
operation.resolve(assignment(`cell-${host}`, host))
}
await vi.waitFor(() => expect(assign).toHaveBeenCalledTimes(expectedCalls))
}
for (const [host, operation] of pending) {
operation.resolve(assignment(`cell-${host}`, host))
}
const firstResponses = await Promise.all(firstWave)
expect(firstResponses.filter((response) => response.status === 200)).toHaveLength(130)
expect(firstResponses.filter((response) => response.status === 503)).toHaveLength(300)
expect(admittedHosts).toEqual(hosts.slice(0, 130))
expect(highWater).toBe(2)
})
it('refuses an oversized chunked body before parsing it, sparing only the reserve routes', async () => {
const resolve = vi.fn()
const app = createRelayApp(config(), {
store: {} as never,
assignments: { resolve } as never,
assignments: {} as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
@@ -98,15 +40,11 @@ describe('public assignment overload', () => {
const refused = await app.request('/v1/resolve', chunked(64 * 1024))
expect(refused.status).toBe(413)
expect(await refused.json()).toEqual({ error: 'request_too_large' })
expect(resolve).not.toHaveBeenCalled()
// Reserve bodies may reach 16 KiB, so the 4 KiB app limit must not answer for them. An
// unavailable route refuses before its own limit, including a body past 16 KiB.
// unavailable route refuses before its own limit.
const reserve = await app.request('/internal/reserve', chunked(6 * 1024))
expect(reserve.status).toBe(404)
expect(await reserve.json()).toEqual({ error: 'reserve_unavailable' })
const oversized = await app.request('/internal/reserve', chunked(20 * 1024))
expect(oversized.status).toBe(404)
expect(await oversized.json()).toEqual({ error: 'reserve_unavailable' })
})
it('reserves one bounded resolve lane while assignment work is saturated', async () => {
@@ -133,8 +71,6 @@ describe('public assignment overload', () => {
await vi.waitFor(() => expect(assign).toHaveBeenCalledTimes(2))
const recovery = app.request('/v1/resolve', resolveRequest('cccccccccccccccc', 1))
await Promise.resolve()
expect(resolveResume).not.toHaveBeenCalled()
const excessResolve = await app.request(
'/v1/resolve',
resolveRequest('dddddddddddddddd', 2)
@@ -143,8 +79,6 @@ describe('public assignment overload', () => {
expect(excessResolve.status).toBe(503)
expect(excessAssign.status).toBe(503)
expect(assign).toHaveBeenCalledTimes(2)
expect(resolveResume).not.toHaveBeenCalled()
pendingAssignments
.get('aaaaaaaaaaaaaaaa')
@@ -153,7 +87,6 @@ describe('public assignment overload', () => {
await vi.waitFor(() => expect(resolveResume).toHaveBeenCalledTimes(1))
pendingResolve.resolve({ userId: 'user-1', relayDeviceId: 'device-1' })
expect((await recovery).status).toBe(200)
expect(resolve).toHaveBeenCalledTimes(1)
for (const [host, operation] of pendingAssignments) {
operation.resolve(assignment(`cell-${host}`, host))
@@ -37,13 +37,11 @@ describe('public assignment reconnect lane', () => {
const resolve = vi.fn(async ({ relayHostId }: { relayHostId: string }) =>
relayHostId === reconnecting ? assignment('cell-r', reconnecting) : null
)
const outcomes: string[] = []
const app = createRelayApp(config({ publicAssignmentQueueMax: 0 }), {
store: {} as never,
assignments: { assign, resolve } as never,
drain: vi.fn(),
ready: vi.fn(async () => true),
recordAssignmentAdmission: (outcome) => outcomes.push(outcome)
ready: vi.fn(async () => true)
})
// Saturate the placement lane with two in-flight newcomers, zero queue.
@@ -59,18 +57,6 @@ describe('public assignment reconnect lane', () => {
)
expect(reconnected.status).toBe(200)
expect(resolve).toHaveBeenCalledWith(
{ userId: 'user-1', relayHostId: reconnecting },
{ classifyHomeRollIsolation: true }
)
expect(assign).toHaveBeenCalledWith(
{ userId: 'user-1', relayHostId: reconnecting },
undefined,
undefined,
undefined,
{ hostRequested: true }
)
expect(outcomes).toContain('sticky')
pending.get(newcomerA)?.resolve(assignment('cell-a', newcomerA))
pending.get(newcomerB)?.resolve(assignment('cell-b', newcomerB))
@@ -78,48 +64,6 @@ describe('public assignment reconnect lane', () => {
expect((await second).status).toBe(200)
})
it('rate-limits repeat fast-lane attempts with the short retry-after', async () => {
const host = 'rrrrrrrrrrrrrrrr'
const assign = vi.fn(async () => assignment('cell-r', host))
const resolve = vi.fn(async () => assignment('cell-r', host))
const app = createRelayApp(config(), {
store: {} as never,
assignments: { assign, resolve } as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
expect(
(await app.request('/v1/assign', assignmentRequest(host, { reconnect: true }))).status
).toBe(200)
const repeat = await app.request('/v1/assign', assignmentRequest(host, { reconnect: true }))
expect(repeat.status).toBe(503)
expect(repeat.headers.get('retry-after')).toBe('2')
expect(resolve).toHaveBeenCalledTimes(1)
expect(assign).toHaveBeenCalledTimes(1)
})
it('rejects on the fast lane when the verification probe fails transiently', async () => {
const host = 'tttttttttttttttt'
const assign = vi.fn()
const resolve = vi.fn(async () => {
throw Object.assign(new Error('deadlock detected'), { code: '40P01' })
})
const app = createRelayApp(config(), {
store: {} as never,
assignments: { assign, resolve } as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
const response = await app.request('/v1/assign', assignmentRequest(host, { reconnect: true }))
expect(response.status).toBe(503)
expect(response.headers.get('retry-after')).toBe('2')
expect(assign).not.toHaveBeenCalled()
})
it('refuses a dial whose assignment row is busy at once, and frees the fast lane', async () => {
const busy = 'bbbbbbbbbbbbbbbb'
const next = 'nnnnnnnnnnnnnnnn'
@@ -143,7 +87,6 @@ describe('public assignment reconnect lane', () => {
expect(refused.status).toBe(503)
expect(refused.headers.get('retry-after')).toBe('1')
expect(await refused.json()).toEqual({ error: 'assignment_row_busy' })
expect(warn.mock.calls.flat().join('\n')).toContain('reason=relay_assignment_row_busy')
// The single fast-lane slot went back with the refusal.
expect(
(await app.request('/v1/assign', assignmentRequest(next, { reconnect: true }))).status
@@ -151,38 +94,6 @@ describe('public assignment reconnect lane', () => {
warn.mockRestore()
})
it('asks a busy resume to retry in a second', async () => {
const host = 'rrrrrrrrrrrrrrrr'
const app = createRelayApp(config(), {
store: {
resolveResume: vi.fn(async () => ({ userId: 'user-1', relayDeviceId: 'device-1' }))
} as never,
assignments: {
assign: vi.fn(async () => {
throw new RelayAssignmentRowBusyError()
}),
resolve: vi.fn(async () => null)
} as never,
drain: vi.fn(),
ready: vi.fn(async () => true)
})
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
const response = await app.request('/v1/resolve', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
v: 1,
relayHostId: host,
resumeToken: Buffer.alloc(32, 1).toString('base64url')
})
})
expect(response.status).toBe(503)
expect(response.headers.get('retry-after')).toBe('1')
expect(await response.json()).toEqual({ error: 'relay_assignment_row_busy' })
warn.mockRestore()
})
})
function assignmentRequest(relayHostId: string, extra: { reconnect?: boolean } = {}): RequestInit {
@@ -39,23 +39,6 @@ async function setup() {
}
describe('idle cutover durable reconciliation', () => {
it('only permits reopening when the exact source still owns the assignment', async () => {
const { store } = await setup()
expect(await store.reconcileIdleRegionalRehome(request)).toBe('not-committed')
await store.activateControl(request, {
cellId: request.sourceCellId,
assignmentEpoch: request.sourceAssignmentEpoch,
generation: request.sourceGeneration + 1,
cellIncarnation: request.sourceCellIncarnation
})
expect(await store.reconcileIdleRegionalRehome(request)).toBe('stale')
})
it('does not reopen an obsolete source after an assignment change', async () => {
const { store } = await setup()
await store.startEvacuation(request, request.targetCellId)
expect(await store.reconcileIdleRegionalRehome(request)).toBe('stale')
})
it('waits for an outstanding assignment transaction before deciding authority', async () => {
const { database, store } = await setup()
@@ -111,51 +111,6 @@ async function setup() {
}
describe('constrained idle regional assignment transaction', () => {
it.each(['missing', 'disabled', 'future'] as const)(
'does only one read per tick with %s durable control and sees later enablement',
async (state) => {
const { store, database, safety } = await setup()
const control = (await database.query(
"SELECT * FROM relay_region_rehome_control WHERE control_id = 'global'"
))[0]!
if (state === 'missing') {
await database.query('DELETE FROM relay_region_rehome_control')
} else {
await database.query(
"UPDATE relay_region_rehome_control SET enabled = ?, not_before = ? WHERE control_id = 'global'",
[state === 'disabled' ? 0 : 1, safety.observedAt + (state === 'future' ? 1 : 0)]
)
}
const query = vi.spyOn(database, 'query')
const transaction = vi.spyOn(database, 'transaction')
for (let tick = 0; tick < 3; tick++) {
query.mockClear()
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toEqual([])
expect(query).toHaveBeenCalledTimes(1)
expect(query.mock.calls[0]![0]).toMatch(/^SELECT .*FROM relay_region_rehome_control/s)
expect(transaction).not.toHaveBeenCalled()
}
if (state === 'missing') {
const columns = Object.keys(control)
await database.query(
`INSERT INTO relay_region_rehome_control (${columns.join(',')}) VALUES (${columns.map(() => '?').join(',')})`,
Object.values(control)
)
} else {
await database.query(
"UPDATE relay_region_rehome_control SET enabled = 1, not_before = ? WHERE control_id = 'global'",
[safety.observedAt]
)
}
query.mockClear()
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toHaveLength(1)
expect(query.mock.calls.length).toBeGreaterThan(1)
await database.query("UPDATE relay_region_rehome_control SET enabled = 0 WHERE control_id = 'global'")
query.mockClear()
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toEqual([])
expect(query).toHaveBeenCalledTimes(1)
}
)
it.each([10, 11])('reserves source activity plus assignment at target capacity %i', async (capacity) => {
const { store, database, safety, request } = await setup()
@@ -184,33 +139,6 @@ describe('constrained idle regional assignment transaction', () => {
})
})
it.each(['next_dispatch_at', 'paused_until'] as const)(
'skips the candidate join while %s holds the durable dispatch budget closed',
async (column) => {
const { store, database, safety } = await setup()
const query = vi.spyOn(database, 'query')
// One assignment only: setup leaves both fields at 0, and naming the other
// one too would assign this column twice, which Postgres rejects.
await database.query(
`UPDATE relay_region_rehome_worker_state SET ${column} = ? WHERE worker_id = 'global'`,
[safety.observedAt + 1]
)
for (let tick = 0; tick < 3; tick++) {
query.mockClear()
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toEqual([])
expect(query).toHaveBeenCalledTimes(2)
expect(query.mock.calls[1]![0]).toMatch(/FROM relay_region_rehome_worker_state/s)
}
await database.query(
`UPDATE relay_region_rehome_worker_state SET ${column} = ? WHERE worker_id = 'global'`,
[safety.observedAt]
)
query.mockClear()
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toHaveLength(1)
expect(query.mock.calls.length).toBeGreaterThan(2)
}
)
// sourceRequestUnits reads a reserve cell's missing phone leases as zero units; selection must drop it.
it.each(['source', 'target'])('drops every candidate while the %s cell is recorded as reserve', async (cellId) => {
const { store, safety } = await setup()
@@ -267,51 +195,6 @@ describe('constrained idle regional assignment transaction', () => {
expect(first.some((c) => c.relayHostId === next[0]!.relayHostId)).toBe(false)
})
it('progresses past a full page of busy candidates without writing eligibility state', async () => {
const { store, database, safety } = await setup()
for (const table of [
'relay_assignments',
'relay_assignment_activity_leases',
'relay_control_capabilities',
'relay_region_decisions'
]) {
const template = (
await database.query(`SELECT * FROM ${table} WHERE user_id = ? AND relay_host_id = ?`, [
identity.userId,
identity.relayHostId
])
)[0]!
const columns = Object.keys(template)
for (let index = 0; index < 100; index++) {
const values = columns.map((column) =>
column === 'user_id' || column === 'relay_host_id' ? '?' : column
)
await database.query(
`INSERT INTO ${table} (${columns.join(', ')}) SELECT ${values.join(', ')} FROM ${table}
WHERE user_id = ? AND relay_host_id = ?`,
[
`idle-store-test-${String(index).padStart(3, '0')}`,
`pagehost${String(index).padStart(8, '0')}`,
identity.userId,
identity.relayHostId
]
)
}
}
const first = await store.selectIdleRegionalRehomeCandidates(safety)
const next = await store.selectIdleRegionalRehomeCandidates(safety)
expect(first).toHaveLength(100)
expect(next).toHaveLength(1)
expect(next[0]!.relayHostId).toBe('pagehost00000099')
const restarted = new RelayAssignmentStore(database, () => safety.observedAt, {
regionalRehomeCohortPercent: 100
})
expect(await restarted.selectIdleRegionalRehomeCandidates(safety)).toEqual(first)
expect(await database.query('SELECT * FROM relay_region_rehome_attempts')).toEqual([])
const decisions = await database.query('SELECT last_considered_at FROM relay_region_decisions')
expect(decisions.every((decision) => Number(decision.last_considered_at) === 0)).toBe(true)
})
it.runIf(Boolean(process.env.ORCA_IDLE_REHOME_POSTGRES_URL))(
'rechecks generation when replacement wins after the initial authority lookup',
async () => {
@@ -429,21 +312,6 @@ describe('constrained idle regional assignment transaction', () => {
})
expect(await database.query('SELECT * FROM relay_region_rehome_attempts')).toEqual([])
})
it('selects read-only with stable identity and the control generation, not probe generation', async () => {
const { store, safety, database } = await setup()
const before = await database.query('SELECT * FROM relay_assignments')
const candidates = await store.selectIdleRegionalRehomeCandidates(safety)
expect(candidates).toHaveLength(1)
expect(candidates[0]).toMatchObject({
sourceGeneration: 7,
sourceCellId: 'source',
targetCellId: 'target'
})
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toEqual(candidates)
expect(await database.query('SELECT * FROM relay_assignments')).toEqual(before)
expect(await database.query('SELECT * FROM relay_region_rehome_attempts')).toEqual([])
})
})
function holdStatement(database: RelayDatabase, fragment: string) {
@@ -151,52 +151,6 @@ describe('idle regional rehome candidate window', () => {
expect(new Set(candidates.map((candidate) => candidate.attemptId)).size).toBe(3)
})
it('drops only the targets without room for the host plus its source activity', async () => {
const { store, database, safety } = await setup()
await seedHost(store, identity)
await database.query(
'UPDATE relay_assignment_activity_leases SET request_units = 4 WHERE user_id = ?',
[identity.userId]
)
// Five units needed: four source units plus the assignment the move reserves.
await database.query("UPDATE relay_cells SET capacity_requests = 4 WHERE cell_id = 'asia-idle'")
const short = await store.selectIdleRegionalRehomeCandidates(safety)
expect(short.map((candidate) => candidate.targetCellId)).toEqual(['asia-mid', 'asia-busy'])
// Exactly enough room is enough; it ranks last because the ratio is per capacity.
await database.query("UPDATE relay_cells SET capacity_requests = 5 WHERE cell_id = 'asia-idle'")
const exact = await store.selectIdleRegionalRehomeCandidates(safety)
expect(exact.map((candidate) => candidate.targetCellId)).toEqual([
'asia-mid',
'asia-busy',
'asia-idle'
])
})
it('reads a bounded window of decisions however many hosts are eligible', async () => {
const { store, database, safety } = await setup()
await seedHost(store, identity)
await cloneHosts(database, identity, 700)
const query = database.query.bind(database)
const decisionRows: number[] = []
vi.spyOn(database, 'query').mockImplementation(async (sql, params) => {
const rows = await query(sql, params)
if (/^SELECT user_id, relay_host_id FROM relay_region_decisions/.test(sql)) {
decisionRows.push(rows.length)
}
return rows
})
const first = await store.selectIdleRegionalRehomeCandidates(safety)
const next = await store.selectIdleRegionalRehomeCandidates(safety)
expect(decisionRows.length).toBeGreaterThan(0)
for (const count of decisionRows) {
expect(count).toBeLessThanOrEqual(500)
}
expect(first).toHaveLength(102)
expect(next).toHaveLength(102)
const firstKeys = new Set(first.map((c) => `${c.userId}/${c.targetCellId}`))
expect(next.every((c) => !firstKeys.has(`${c.userId}/${c.targetCellId}`))).toBe(true)
})
it("keeps the window's last host when a decision turns eligible between the two reads", async () => {
const { store, database, safety, now } = await setup()
await seedHost(store, identity)
@@ -252,7 +206,6 @@ describe('idle regional rehome candidate window', () => {
for (let poll = 0; poll < 20 && !wrapped; poll++) {
const page = await store.selectIdleRegionalRehomeCandidates(safety)
expect(page.length).toBeGreaterThan(0)
expect(page.length).toBeLessThanOrEqual(102)
for (const candidate of page) {
const key = `${candidate.userId}/${candidate.targetCellId}`
if (seen.has(key)) {
@@ -267,42 +220,4 @@ describe('idle regional rehome candidate window', () => {
expect(seen.size).toBe(363)
expect(wrapped).toBe(true)
})
it('excludes a host inside its rehome cooldown and takes it back after', async () => {
const { store, database, safety, now } = await setup()
await seedHost(store, identity)
await database.query(
`INSERT INTO relay_region_rehome_attempts
(attempt_id, user_id, relay_host_id, preferred_region, source_cell_id, source_cell_incarnation,
target_cell_id, target_cell_incarnation, previous_epoch, assignment_epoch, drain_grace_ms,
send_attempts, created_at, updated_at)
VALUES ('cooled', ?, ?, 'asia-east2', 'us', ?, 'asia-idle', ?, 0, 9, 0, 0, ?, ?)`,
[identity.userId, identity.relayHostId, incarnations[0], incarnations[2], now - 1_000, now]
)
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toEqual([])
await database.query('UPDATE relay_region_rehome_attempts SET created_at = ?', [
now - 604_800_000 - 1
])
expect(await store.selectIdleRegionalRehomeCandidates(safety)).toHaveLength(3)
})
it('excludes a host outside the cohort', async () => {
const { store, database } = await setup()
await seedHost(store, identity)
const now = 100_000_000 + 86_400_000
const safety = {
observedAt: now,
sqlFailures: 0,
reconnects: 0,
controlActivityRecoveryFailures: 0,
databasePoolWaiting: 0,
databasePoolWaitersMax: 0,
databasePoolWaitMsMax: 0
}
await database.query('UPDATE relay_region_decisions SET cohort_bucket = 40')
const narrow = new RelayAssignmentStore(database, () => now, { regionalRehomeCohortPercent: 40 })
expect(await narrow.selectIdleRegionalRehomeCandidates(safety)).toEqual([])
const wide = new RelayAssignmentStore(database, () => now, { regionalRehomeCohortPercent: 41 })
expect(await wide.selectIdleRegionalRehomeCandidates(safety)).toHaveLength(3)
})
})
@@ -80,20 +80,11 @@ describe('persisted region decisions across director restart', () => {
expect(row.outcome).toBe('inconclusive')
expect(Number(row.expires_at)).toBe(context.window.expiresAt)
const successor = (await context.issue(restarted.store)).window!
expect(successor.generation).toBe(context.window.generation + 1)
expect(successor.generation).toBe(2)
expect(await restarted.store.exchangeRegionCorrection(identity, context.report, epoch))
.toMatchObject({ reportStatus: 'stale' })
})
it('uses server expiry after a restart regardless of an old client report', async () => {
const context = await setup()
context.setNow(context.window.expiresAt)
const restarted = await context.restart()
expect(await restarted.store.exchangeRegionCorrection(identity, context.report, context.window.assignmentEpoch))
.toMatchObject({ reportStatus: 'expired' })
expect(await restarted.store.previewRegionCorrection()).toEqual({ expired: 1 })
})
it('does not interpret a persisted future-policy window using the old policy after rollback', async () => {
const context = await setup()
await context.database.query('UPDATE relay_region_decisions SET policy_version = 2')
@@ -105,15 +96,4 @@ describe('persisted region decisions across director restart', () => {
expect(row.preferred_region).toBeNull()
expect(row.report_json).toBeNull()
})
it('keeps generation ordering when the server clock moves backwards across restart', async () => {
const context = await setup()
context.setNow(1_000_000_000 - 60_000)
const restarted = await context.restart()
const successor = (await context.issue(restarted.store)).window!
expect(successor.generation).toBe(context.window.generation + 1)
expect(successor.expiresAt).toBe(context.window.expiresAt - 60_000)
expect(await restarted.store.exchangeRegionCorrection(identity, context.report, context.window.assignmentEpoch))
.toMatchObject({ reportStatus: 'stale' })
})
})
@@ -124,104 +124,7 @@ async function window(context: Awaited<ReturnType<typeof setup>>) {
return result.window!
}
async function regionalMigration(context: Awaited<ReturnType<typeof setup>>) {
const migration = await context.store.startEvacuation(identity, cells[1]!.id)
const attemptId = '33333333-3333-4333-8333-333333333333'
await context.database.query(
`INSERT INTO relay_region_rehome_attempts
(attempt_id,user_id,relay_host_id,preferred_region,source_cell_id,source_cell_incarnation,
target_cell_id,target_cell_incarnation,previous_epoch,assignment_epoch,drain_grace_ms,send_attempts,created_at,updated_at)
VALUES (?,?,?,'asia-east2',?,?,?,?,?,?,60000,1,?,?)`,
[
attemptId,
identity.userId,
identity.relayHostId,
cells[0]!.id,
incarnations[0],
cells[1]!.id,
incarnations[1],
migration.previousEpoch,
migration.assignmentEpoch,
context.now(),
context.now()
]
)
return { migration }
}
describe('ordered region decisions and migration outcomes', () => {
it('reports aggregate migration lifecycle and reservations without identity disclosure or writes', async () => {
const context = await setup()
const { migration } = await regionalMigration(context)
context.advance(1_000)
const before = await context.database.query('SELECT * FROM relay_region_rehome_attempts')
const outcomes = await context.store.regionCorrectionOutcomes()
expect(outcomes).toEqual([
expect.objectContaining({
sourceCellId: cells[0]!.id,
targetCellId: cells[1]!.id,
state: 'registering',
count: 1,
oldestOpenMs: 1_000
})
])
expect(outcomes[0]!.targetReservedUnits).toBeGreaterThan(0)
expect(JSON.stringify(outcomes)).not.toContain(identity.relayHostId)
expect(JSON.stringify(outcomes)).not.toContain(identity.userId)
expect(await context.database.query('SELECT * FROM relay_region_rehome_attempts')).toEqual(
before
)
await context.store.activateControl(identity, {
cellId: cells[1]!.id,
assignmentEpoch: migration.assignmentEpoch,
generation: 1
})
await context.store.markMigrationTargetRegistered(identity, {
cellId: cells[1]!.id,
assignmentEpoch: migration.assignmentEpoch
})
expect(await context.store.regionCorrectionOutcomes()).toEqual([
expect.objectContaining({ state: 'registered' })
])
await context.store.releaseActivity(identity, context.activityId)
expect(await context.store.completeReadyRegionalRehomes()).toBe(1)
expect(await context.store.regionCorrectionOutcomes()).toEqual([
expect.objectContaining({
state: 'completed',
targetReservedUnits: 0,
oldestOpenMs: 0
})
])
})
it('previews the uncapped fleet without writes, claims, or locked reads', async () => {
const context = await setup()
const query = context.database.query.bind(context.database)
const transaction = context.database.transaction.bind(context.database)
const queryLocked = context.database.queryLocked.bind(context.database)
context.database.query = async (sql, params) => {
expect(sql.trim()).toMatch(/^(SELECT|WITH)/i)
return query(sql, params)
}
context.database.transaction = async () => {
throw new Error('preview_must_not_open_mutating_transaction')
}
context.database.queryLocked = async () => {
throw new Error('preview_must_not_lock')
}
try {
const preview = await context.store.previewRegionalRehomeEligibility()
expect(preview.counts['no-verified-decision']).toBeGreaterThanOrEqual(1)
expect(preview.globalSafetyFailure).toBe('process-safety-unavailable')
expect(JSON.stringify(preview)).not.toContain(identity.relayHostId)
expect(JSON.stringify(preview)).not.toContain(identity.userId)
} finally {
context.database.query = query
context.database.transaction = transaction
context.database.queryLocked = queryLocked
}
})
it('allocates distinct ordered generations for concurrent window issuers', async () => {
const context = await setup()
const replies = await Promise.all([window(context), window(context), window(context)])
@@ -244,53 +147,6 @@ describe('ordered region decisions and migration outcomes', () => {
).toMatchObject({ reportStatus: 'stale' })
})
it('compares with assigned region, preserves hints, and never extends a window on report', async () => {
const context = await setup()
await context.store.assign(identity, 'asia-east2')
const issued = await window(context)
expect(issued.incumbentRegion).toBe('us-central1')
context.advance(50)
await context.store.exchangeRegionCorrection(
identity,
{
v: 1,
action: 'report',
generation: issued.generation,
assignmentEpoch: issued.assignmentEpoch,
policyVersion: 1,
outcome: 'conclusive',
measurements: { 'us-central1': 110, 'asia-east2': 90 }
},
issued.assignmentEpoch
)
expect(await context.store.previewRegionCorrection()).toEqual({ ineligible: 1 })
const row = (await context.database.query(`SELECT * FROM relay_region_decisions`))[0]!
expect(Number(row.expires_at)).toBe(issued.expiresAt)
const hint = (
await context.database.query(
`SELECT preferred_region FROM relay_assignment_region_preferences WHERE user_id = ?`,
[identity.userId]
)
)[0]
expect(hint?.preferred_region).toBe('asia-east2')
context.advance(24 * 60 * 60_000)
expect(
await context.store.exchangeRegionCorrection(
identity,
{
v: 1,
action: 'report',
generation: issued.generation,
assignmentEpoch: issued.assignmentEpoch,
policyVersion: 1,
outcome: 'inconclusive',
reason: 'late'
},
issued.assignmentEpoch
)
).toMatchObject({ reportStatus: 'expired' })
})
it('rejects stale assignment basis and requires both thresholds', async () => {
const context = await setup()
for (const measurements of [
@@ -1,10 +1,6 @@
import pg from 'pg'
import { afterAll, beforeEach, describe, expect, it } from 'vitest'
import {
openRelayDatabase,
REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS,
type RelayDatabase
} from '../../database/database.js'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
@@ -127,25 +123,15 @@ describePostgres('PostgreSQL regional rehome constraint migration', () => {
await expect(
client.query(INSERT_ATTEMPT, attemptValues('attempt-3', 'europe-west1'))
).rejects.toMatchObject({ code: '23514' })
const constraints = await client.query(
`SELECT conname FROM pg_constraint
WHERE conrelid = 'relay_region_rehome_attempts'::regclass
AND conname LIKE '%preferred_region%'
ORDER BY conname`
)
expect(constraints.rows).toEqual([
{ conname: 'relay_region_rehome_attempts_preferred_region_valid' }
])
// The existing control row keeps its tuning and gains the cooldown.
// Preserve existing control tuning across the upgrade.
const control = await client.query(
`SELECT generation, preference_max_age_ms, host_cooldown_ms
`SELECT generation, preference_max_age_ms
FROM relay_region_rehome_control WHERE control_id = 'global'`
)
expect(control.rows).toEqual([
{
generation: '3',
preference_max_age_ms: '86400000',
host_cooldown_ms: String(REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS)
preference_max_age_ms: '86400000'
}
])
})
@@ -182,14 +168,6 @@ describePostgres('PostgreSQL regional rehome constraint migration', () => {
await withClient(async (client) => {
await client.query(`SET search_path = ${schema}`)
await client.query(INSERT_ATTEMPT, attemptValues('attempt-4', 'us-central1'))
const constraints = await client.query(
`SELECT conname FROM pg_constraint
WHERE conrelid = 'relay_region_rehome_attempts'::regclass
AND conname LIKE '%preferred_region%'`
)
expect(constraints.rows).toEqual([
{ conname: 'relay_region_rehome_attempts_preferred_region_valid' }
])
})
}, 60_000)
})
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment-store.js'
import { openIdleRehomeTestDatabase } from './idle-regional-rehome-test-database.js'
@@ -134,10 +134,6 @@ const moves = (candidates: { userId: string; sourceCellId: string; targetCellId:
to: cells.find(({ id }) => id === targetCellId)!.region
}))
afterEach(() => {
vi.restoreAllMocks()
})
describe('rehome source-region filter', () => {
it('keeps US to Asia moves and drops Asia to US moves', async () => {
const context = await setup('us-central1')
@@ -151,13 +147,5 @@ describe('rehome source-region filter', () => {
await context.database.close()
})
it('reports the filtered hosts in the preview, agreeing with selection', async () => {
const context = await setup('us-central1')
const preview = await context.store.previewRegionalRehomeEligibility(context.safety())
expect(preview.counts).toEqual({
'eligible:us-central1-to-asia-east2': 1,
'source-outside-director-region': 1
})
await context.database.close()
})
})
@@ -30,88 +30,6 @@ const targetIncarnation = '22222222-2222-4222-8222-222222222222'
describe('regional rehome assignment state', () => {
it('moves one live preferred host and completes without disabling its source cell', async () => {
const context = await setup()
const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
const neighbor = { userId: 'user-2', relayHostId: 'ponmlkjihgfedcba' }
const sourceControl = await activatePreferredSource(context, identity)
await activateSource(context, neighbor)
const attempt = await context.store.tryIdleRehome()
expect(attempt).toMatchObject({
userId: identity.userId,
relayHostId: identity.relayHostId,
sourceCellId: source.id,
sourceCellIncarnation: sourceIncarnation,
targetCellId: target.id,
targetCellIncarnation: targetIncarnation,
previousEpoch: 1,
assignmentEpoch: 2,
sendAttempts: 1
})
expect(await context.store.resolve(neighbor)).toMatchObject({ cellId: source.id })
expect(await context.store.completeReadyRegionalRehomes()).toBe(0)
expect(
await context.database.query(
'SELECT drain_outcome FROM relay_region_rehome_attempts WHERE attempt_id = ?',
[attempt!.attemptId]
)
).toEqual([{ drain_outcome: 'accepted' }])
const targetControl = await context.store.activateControl(identity, {
cellId: target.id,
assignmentEpoch: 2,
generation: 1
})
await context.store.markMigrationTargetRegistered(identity, {
cellId: target.id,
assignmentEpoch: 2
})
expect(await context.store.completeReadyRegionalRehomes()).toBe(0)
await context.store.releaseActivity(identity, sourceControl)
expect(await context.store.completeReadyRegionalRehomes()).toBe(1)
expect(await context.store.resolve(identity)).toMatchObject({
cellId: target.id,
assignmentEpoch: 2
})
expect(await context.store.resolve(neighbor)).toMatchObject({ cellId: source.id })
expect(
await context.database.query(
`SELECT completed_at, aborted_at FROM relay_assignment_migrations
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
).toEqual([{ completed_at: context.now(), aborted_at: null }])
expect(
await context.database.query(
`SELECT drain_receipt_at, completed_at, aborted_at FROM relay_region_rehome_attempts`
)
).toEqual([{ drain_receipt_at: context.now(), completed_at: context.now(), aborted_at: null }])
expect(targetControl).toMatch(/^control:/)
await context.database.close()
})
it('does not roll an unregistered target back to a stale regional source', async () => {
const context = await setup()
const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
await activatePreferredSource(context, identity)
await context.store.tryIdleRehome()
context.advance(6 * 60_000)
await heartbeat(context.store, target, targetIncarnation, 3, 2)
expect(await context.store.refreshRegionalRehomeLeases()).toBe(0)
expect(await context.store.abortExpiredEvacuations()).toBe(0)
expect(
await context.database.query(
`SELECT cell_id, assignment_epoch FROM relay_assignments
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
).toEqual([{ cell_id: target.id, assignment_epoch: 2 }])
await context.database.close()
})
it('completes healthy candidates past a poisoned attempt and logs it', async () => {
const context = await setup()
const poisoned = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
@@ -166,52 +84,6 @@ describe('regional rehome assignment state', () => {
).toEqual([{ completed_at: context.now() }])
await context.database.close()
})
it('keeps dual-control accounting when the drained host re-resolves mid-rehome', async () => {
const context = await setup()
const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
const sourceControl = await activatePreferredSource(context, identity)
const attempt = await context.store.tryIdleRehome()
expect(await controlAccounting(context, identity)).toEqual({
reservedControls: 2,
controlLeases: 2
})
// The drained host re-resolves through the director while both the source
// control and the target's pending control are still live.
await context.store.assign(identity, 'asia-east2')
expect(await controlAccounting(context, identity)).toEqual({
reservedControls: 2,
controlLeases: 2
})
await context.store.activateControl(identity, {
cellId: target.id,
assignmentEpoch: attempt!.assignmentEpoch,
generation: 1
})
await context.store.markMigrationTargetRegistered(identity, {
cellId: target.id,
assignmentEpoch: attempt!.assignmentEpoch
})
await context.store.releaseActivity(identity, sourceControl)
expect(await controlAccounting(context, identity)).toEqual({
reservedControls: 1,
controlLeases: 1
})
expect(await context.store.completeReadyRegionalRehomes()).toBe(1)
expect(
await context.database.query(
`SELECT completed_at FROM relay_assignment_migrations
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
).toEqual([{ completed_at: context.now() }])
expect(await cellReservations(context)).toEqual({ [source.id]: 0, [target.id]: 1 })
await context.database.close()
})
})
type Context = Awaited<ReturnType<typeof setup>>
@@ -281,35 +153,6 @@ function collectCandidateFailureWarnings(
}
}
async function controlAccounting(
context: Context,
identity: { userId: string; relayHostId: string }
): Promise<{ reservedControls: number; controlLeases: number }> {
const assignment = (
await context.database.query(
`SELECT reserved_controls FROM relay_assignments
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
)[0]!
const leases = await context.database.query(
`SELECT COUNT(*) AS controls FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ? AND activity_kind = 'control'`,
[identity.userId, identity.relayHostId]
)
return {
reservedControls: Number(assignment.reserved_controls),
controlLeases: Number(leases[0]!.controls)
}
}
async function cellReservations(context: Context): Promise<Record<string, number>> {
const rows = await context.database.query(
`SELECT cell_id, reserved_requests FROM relay_cells ORDER BY cell_id`
)
return Object.fromEntries(rows.map((row) => [String(row.cell_id), Number(row.reserved_requests)]))
}
async function activatePreferredSource(
context: Context,
identity: { userId: string; relayHostId: string }
@@ -344,18 +187,6 @@ async function activatePreferredSource(
return control
}
async function activateSource(
context: Context,
identity: { userId: string; relayHostId: string }
): Promise<string> {
const assignment = await context.store.assign(identity, undefined, 'us-central1')
return await context.store.activateControl(identity, {
cellId: source.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
}
async function heartbeat(
store: IdleRehomeTestStore,
cell: typeof source | typeof target,
@@ -1,7 +1,6 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment-store.js'
import {
consumeRelayCellInventoryHold,
openRelayDatabase,
type PostgresDatabase,
type RelayDatabase
@@ -18,21 +17,6 @@ const describePostgres = databaseUrl ? describe : describe.skip
// Roughly the round trip from an Asia cell to the us-central1 database.
const STATEMENT_DELAY_MS = 150
// Probe the formerly fleet-locked tables plus admission/region tables read by commit.
const CELL_TABLES = [
'relay_cells',
'relay_cell_runtime',
'relay_cell_capabilities',
'relay_cell_rehome_safety',
'relay_cell_admission',
'relay_cell_regions'
] as const
// The target-row statement locks exactly these, held from it to COMMIT.
const TARGET_LOCKED = ['target:relay_cells', 'target:relay_cell_admission']
type Trip = { sql: string; lockable: Record<string, boolean>; probeMs: number }
type DelayControl = StatementDelay & { beforeTrip: (sql: string) => Promise<void> }
describePostgres('PostgreSQL regional rehome target-row lock', () => {
@@ -97,27 +81,6 @@ describePostgres('PostgreSQL regional rehome target-row lock', () => {
}
}
async function lockable(table: string, cellId: string): Promise<boolean> {
try {
await observer.transaction(
async (transaction) => {
await transaction.queryLocked(
`SELECT cell_id FROM ${table} WHERE cell_id = ?`,
[cellId],
{
failIfUnavailable: true
}
)
},
{ reportRetries: false }
)
return true
} catch (error) {
if (error instanceof Error && error.message === 'database_lock_unavailable') return false
throw error
}
}
async function reservedRequests(cellId: string): Promise<number> {
const row = (
await primary.query(`SELECT reserved_requests FROM relay_cells WHERE cell_id = ?`, [cellId])
@@ -152,59 +115,6 @@ describePostgres('PostgreSQL regional rehome target-row lock', () => {
)
}
it('holds only the target cell and admission rows until the next COMMIT boundary', async () => {
const context = await fixture()
const request = await context.select()
const trips: Trip[] = []
const probed = {
source: context.source.id,
target: context.target.id,
bystander: context.bystander.id
}
control.beforeTrip = async (sql) => {
const probeStartedAt = performance.now()
const state: Record<string, boolean> = {}
for (const [role, cellId] of Object.entries(probed)) {
for (const table of CELL_TABLES) state[`${role}:${table}`] = await lockable(table, cellId)
}
trips.push({ sql, lockable: state, probeMs: performance.now() - probeStartedAt })
}
consumeRelayCellInventoryHold(delayed)
control.enabled = true
const result = await context.delayedStore.commitIdleRegionalRehome(request, context.safety())
control.enabled = false
expect(result).toEqual({ outcome: 'committed' })
expect(trips.length).toBeGreaterThanOrEqual(15)
expect(trips[trips.length - 1]!.sql).toBe('COMMIT')
for (const trip of trips) {
for (const [key, free] of Object.entries(trip.lockable)) {
if (TARGET_LOCKED.includes(key)) continue
expect({ sql: trip.sql, key, free }).toEqual({ sql: trip.sql, key, free: true })
}
}
for (const key of TARGET_LOCKED) {
expect(trips.filter((trip) => !trip.lockable[key]).map((trip) => trip.sql)).toEqual([
'COMMIT'
])
}
const counts = consumeRelayCellInventoryHold(delayed)
const commitProbeMs = trips.at(-1)!.probeMs
console.info(
JSON.stringify({
event: 'rehome_target_row_hold',
trips: trips.length,
commitProbeMs,
...counts
})
)
expect(counts.rehomeTargetRowHolds).toBe(1)
expect(counts.cellInventoryHoldMaxSite).toBe('rehome-target-row')
expect(counts.rehomeTargetRowHoldMsMax).toBeGreaterThanOrEqual(STATEMENT_DELAY_MS)
expect(await reservedRequests(context.target.id)).toBe(context.targetReservedBefore + 2)
}, 45_000)
it('defers when the target leaves general admission after selection', async () => {
const context = await fixture()
const request = await context.select()
@@ -240,23 +150,14 @@ describePostgres('PostgreSQL regional rehome target-row lock', () => {
})
await lockedPromise
}
consumeRelayCellInventoryHold(delayed)
control.enabled = true
const startedAt = performance.now()
const result = await context.delayedStore.commitIdleRegionalRehome(request, context.safety())
const elapsedMs = performance.now() - startedAt
control.enabled = false
release()
await writer
expect(result).toEqual({ outcome: 'deferred', reason: 'candidate-ineligible' })
expect(consumeRelayCellInventoryHold(delayed)).toMatchObject({
cellInventoryLockUnavailable: 1,
rehomeTargetRowHolds: 0
})
// NOWAIT: no 1s lock_timeout wait and no transaction retry behind the writer.
expect(elapsedMs).toBeLessThan(40 * STATEMENT_DELAY_MS)
await expectNothingCommitted(context)
})
@@ -1,202 +0,0 @@
import { describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment-store.js'
import { openInMemoryRelayDatabase } from '../../database/database.js'
import { REGIONAL_REHOME_SQL_FAILURES_PER_CELL_LIMIT } from './regional-rehome-safety.js'
const cell = (id: string, region: 'us-central1' | 'asia-east2') => ({
id,
url: `https://${id}.relay.example.test`,
region,
capacityRequests: 100,
connectionHardCap: 1_000 as const,
connectionUnobservedBound: 60
})
const source = cell('us-c1', 'us-central1')
const noHeadroom = cell('asia-a', 'asia-east2')
const unclean = cell('asia-b', 'asia-east2')
const highLoad = cell('asia-c', 'asia-east2')
const lowLoad = cell('asia-d', 'asia-east2')
const incarnation = (n: number) =>
`${String(n).repeat(8)}-${String(n).repeat(4)}-4${String(n).repeat(3)}` +
`-8${String(n).repeat(3)}-${String(n).repeat(12)}`
async function setup() {
let clock = 1_000_000
const database = await openInMemoryRelayDatabase()
const store = new RelayAssignmentStore(database, () => clock, {
regionalRehomeCohortPercent: 100,
requireLiveCells: true,
heartbeatTtlMs: 45_000
})
await store.inspectRegionalRehomeControl()
clock += 24 * 60 * 60_000
await store.applyRegionalRehomeControl({
expectedGeneration: 0,
enabled: true,
notBefore: clock,
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
hostCooldownMs: 7 * 24 * 60 * 60_000,
drainGraceMs: 60 * 60_000
})
await store.reconcileCells([source, noHeadroom, unclean, highLoad, lowLoad])
const beat = async (
config: typeof source,
n: number,
protocol: number,
state: { observedRequests: number; enforcedConnections: number; sqlFailures: number }
) => {
await store.recordCellHeartbeat({
cellId: config.id,
cellUrl: config.url,
region: config.region,
cellIncarnation: incarnation(n),
startedAt: 900_000,
ready: true,
observedRequests: state.observedRequests,
totalConnections: state.enforcedConnections,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: state.enforcedConnections,
connectionInclusionWatermark: clock,
connectionHardCap: 1_000,
connectionUnobservedBound: 60
})
await store.recordCellRegionalRehomeStatus({
cellId: config.id,
cellIncarnation: incarnation(n),
regionalRehomeProtocol: protocol,
safety: {
observedAt: clock,
sqlFailures: state.sqlFailures,
reconnects: 0,
controlActivityRecoveryFailures: 0,
databasePoolWaiting: 0,
databasePoolWaitersMax: 0,
databasePoolWaitMsMax: 0
}
})
}
const activatePreferredSource = async () => {
const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
const assignment = await store.assign(identity, undefined, 'us-central1')
await store.activateControl(identity, {
cellId: source.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1,
idleRegionalRehome: true,
cellIncarnation: incarnation(1)
})
const { window } = await store.exchangeRegionCorrection(
identity,
{ v: 1, action: 'issue-window' },
assignment.assignmentEpoch
)
expect(window).toBeDefined()
await store.exchangeRegionCorrection(
identity,
{
v: 1,
action: 'report',
generation: window!.generation,
assignmentEpoch: assignment.assignmentEpoch,
policyVersion: 1,
outcome: 'conclusive',
measurements: { 'us-central1': 180, 'asia-east2': 40 }
},
assignment.assignmentEpoch
)
}
const safety = () => ({
observedAt: clock,
sqlFailures: 0,
reconnects: 0,
controlActivityRecoveryFailures: 0,
databasePoolWaiting: 0,
databasePoolWaitersMax: 0,
databasePoolWaitMsMax: 0
})
return { database, store, beat, activatePreferredSource, safety }
}
const UNCLEAN = REGIONAL_REHOME_SQL_FAILURES_PER_CELL_LIMIT + 1
describe('regional rehome target selection', () => {
it('never selects a target without connection headroom, even at lowest load', async () => {
const context = await setup()
await context.beat(source, 1, 3, {
observedRequests: 0,
enforcedConnections: 0,
sqlFailures: 0
})
// Lowest load but the connection hard cap is exhausted.
await context.beat(noHeadroom, 2, 3, {
observedRequests: 0,
enforcedConnections: 999,
sqlFailures: 0
})
await context.beat(unclean, 3, 3, {
observedRequests: 0,
enforcedConnections: 0,
sqlFailures: UNCLEAN
})
await context.beat(highLoad, 4, 3, {
observedRequests: 50,
enforcedConnections: 0,
sqlFailures: 0
})
await context.beat(lowLoad, 5, 3, {
observedRequests: 10,
enforcedConnections: 0,
sqlFailures: 0
})
await context.activatePreferredSource()
const candidates = await context.store.selectIdleRegionalRehomeCandidates(context.safety())
const attempt = candidates[0]
expect(attempt?.targetCellId).toBe(lowLoad.id)
expect(await context.store.commitIdleRegionalRehome(attempt!, context.safety(), 100)).toEqual({
outcome: 'committed'
})
await context.database.close()
})
it('falls to the next clean target when the load winner goes unclean', async () => {
const context = await setup()
await context.beat(source, 1, 3, {
observedRequests: 0,
enforcedConnections: 0,
sqlFailures: 0
})
await context.beat(noHeadroom, 2, 3, {
observedRequests: 0,
enforcedConnections: 999,
sqlFailures: 0
})
await context.beat(unclean, 3, 3, {
observedRequests: 0,
enforcedConnections: 0,
sqlFailures: UNCLEAN
})
await context.beat(highLoad, 4, 3, {
observedRequests: 50,
enforcedConnections: 0,
sqlFailures: 0
})
await context.beat(lowLoad, 5, 3, {
observedRequests: 10,
enforcedConnections: 0,
sqlFailures: UNCLEAN
})
await context.activatePreferredSource()
const candidates = await context.store.selectIdleRegionalRehomeCandidates(context.safety())
const attempt = candidates[0]
expect(attempt?.targetCellId).toBe(highLoad.id)
expect(await context.store.commitIdleRegionalRehome(attempt!, context.safety(), 100)).toEqual({
outcome: 'committed'
})
await context.database.close()
})
})
@@ -1,189 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { RelayAssignmentStore } from '../assignment-store.js'
import type { RelayConfig } from '../../config.js'
import { startRegionalRehomeWorker } from './regional-rehome-worker.js'
describe('regional rehome worker', () => {
afterEach(() => vi.restoreAllMocks())
it('bounds empty polling to the six-second cadence and stops its timer', async () => {
vi.useFakeTimers()
const selectIdleRegionalRehomeCandidates = vi.fn().mockResolvedValue([])
const worker = startRegionalRehomeWorker(
config(),
{
selectIdleRegionalRehomeCandidates
} as unknown as RelayAssignmentStore,
{
safetySnapshot: () => safety(Date.now()),
random: () => 0
}
)!
try {
await vi.advanceTimersByTimeAsync(0)
expect(selectIdleRegionalRehomeCandidates).toHaveBeenCalledTimes(1)
await vi.advanceTimersByTimeAsync(5_999)
expect(selectIdleRegionalRehomeCandidates).toHaveBeenCalledTimes(1)
await vi.advanceTimersByTimeAsync(1)
expect(selectIdleRegionalRehomeCandidates).toHaveBeenCalledTimes(2)
worker.stop()
await vi.advanceTimersByTimeAsync(60_000)
expect(selectIdleRegionalRehomeCandidates).toHaveBeenCalledTimes(2)
} finally {
worker.stop()
vi.useRealTimers()
}
})
it('stops walking the page when the source names a deferral no candidate can pass', async () => {
const fetchImpl = respondWith([{ outcome: 'deferred', reason: 'concurrency-limit' }])
const summaries = collectSummaries()
try {
await runOnePoll(fetchImpl, 3, summaries)
} finally {
summaries.restore()
}
expect(fetchImpl).toHaveBeenCalledTimes(1)
expect(summaries.entries).toEqual([
{
event: 'orca_relay_idle_rehome_dispatch_summary',
candidates: 3,
dispatched: 1,
stoppedBy: 'concurrency-limit',
outcomes: { 'deferred:concurrency-limit': 1 }
}
])
})
it('keeps its whole-page walk when the source sends no reason at all', async () => {
const fetchImpl = respondWith([{ outcome: 'deferred' }])
const summaries = collectSummaries()
try {
await runOnePoll(fetchImpl, 3, summaries)
} finally {
summaries.restore()
}
expect(fetchImpl).toHaveBeenCalledTimes(3)
expect(summaries.entries[0]).toMatchObject({
stoppedBy: null,
outcomes: { deferred: 3 }
})
})
it('walks past a deferral that only concerns the one candidate', async () => {
const fetchImpl = respondWith([
{ outcome: 'deferred', reason: 'host-unsupported' },
{ outcome: 'busy' },
{ outcome: 'committed' }
])
const summaries = collectSummaries()
try {
await runOnePoll(fetchImpl, 4, summaries)
} finally {
summaries.restore()
}
expect(fetchImpl).toHaveBeenCalledTimes(3)
expect(summaries.entries[0]).toMatchObject({
dispatched: 3,
stoppedBy: 'committed',
outcomes: { 'deferred:host-unsupported': 1, busy: 1, committed: 1 }
})
})
})
function respondWith(bodies: { outcome: string; reason?: string }[]) {
let index = 0
return vi.fn(async () => {
const body = bodies[Math.min(index++, bodies.length - 1)]!
return new Response(JSON.stringify({ v: 1, ...body }), {
headers: { 'content-type': 'application/json' }
})
})
}
function collectSummaries() {
const entries: Record<string, unknown>[] = []
let arrived: (() => void) | undefined
// Construction immediately polls; await first instead of waiting for a timer tick.
const first = new Promise<void>((resolve) => {
arrived = resolve
})
const original = console.warn
console.warn = (line: unknown, ...rest: unknown[]) => {
try {
const parsed = JSON.parse(line as string) as Record<string, unknown>
if (parsed.event === 'orca_relay_idle_rehome_dispatch_summary') {
entries.push(parsed)
arrived?.()
return
}
} catch {
// Not a JSON log line; fall through to the original writer.
}
original(line as string, ...rest)
}
return { entries, first, restore: () => (console.warn = original) }
}
async function runOnePoll(
fetchImpl: typeof fetch,
candidates: number,
summaries: { first: Promise<void> }
): Promise<void> {
const assignments = {
selectIdleRegionalRehomeCandidates: vi.fn(async () =>
Array.from({ length: candidates }, (_, index) => ({
v: 1 as const,
attemptId: `00000000-0000-4000-8000-00000000000${index}`,
userId: `user-${index}`,
relayHostId: 'abcdefghijklmnop',
sourceCellId: 'us-c1',
sourceCellUrl: 'https://us-c1.relay.example.test',
sourceCellIncarnation: '11111111-1111-4111-8111-111111111111',
sourceAssignmentEpoch: 1,
sourceGeneration: 1,
targetCellId: 'asia-c1'
}))
)
} as unknown as RelayAssignmentStore
const worker = startRegionalRehomeWorker(config(), assignments, {
fetch: fetchImpl,
identityToken: async () => 'token',
safetySnapshot: () => safety(Date.now()),
intervalMs: 60_000
})!
try {
await summaries.first
} finally {
worker.stop()
}
}
function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
return {
role: 'director',
rehomeAudience: 'https://relay.example.test/v1/admin/host-drain',
rehomeDirectorServiceAccount: 'relay-director@example.test',
...overrides
} as RelayConfig
}
function safety(observedAt: number) {
return {
requiredCells: 2,
missingCells: 0,
observedAt,
sqlFailures: 0,
reconnects: 0,
controlActivityRecoveryFailures: 0,
databasePoolTotal: 3,
databasePoolIdle: 3,
databasePoolWaiting: 0,
databasePoolWaitersMax: 0,
databasePoolOldestWaitMs: 0,
databasePoolWaitMsMax: 0
}
}
@@ -45,16 +45,6 @@ describe('Relay regional placement', () => {
afterEach(async () => await database.close())
it('falls back globally only when the target region has no safe general cell', async () => {
await store.configureCell(CELLS[1]!, 'migration-only')
await store.configureCell(CELLS[2]!, 'migration-only')
await store.configureCell(CELLS[3]!, 'migration-only')
await expect(
store.assign({ userId: 'fallback-user', relayHostId: 'fallbackhost0001' }, 'asia-east2')
).resolves.toMatchObject({ cellId: 'us-c1', region: 'us-central1' })
})
it('preserves sticky assignments while updating only explicit preferences', async () => {
const identity = { userId: 'sticky-user', relayHostId: 'stickyhost000001' }
await expect(store.assign(identity)).resolves.toMatchObject({ cellId: 'us-c1' })
@@ -219,20 +209,4 @@ describe('Relay regional placement', () => {
})
})
it('expires identity-linked region preferences after 30 days', async () => {
const identity = { userId: 'expiry-user', relayHostId: 'expiryhost000001' }
await store.assign(identity, 'asia-east2')
now += 30 * 24 * 60 * 60_000
await expect(store.releaseExpiredRegionPreferences()).resolves.toBe(0)
now += 1
await expect(store.releaseExpiredRegionPreferences()).resolves.toBe(1)
await expect(
database.query(
`SELECT preferred_region FROM relay_assignment_region_preferences
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
).resolves.toEqual([])
})
})
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { PLACEMENT_INTAKE_SHARE, PlacementLoadBand } from './placement-load-band.js'
import { PlacementLoadBand } from './placement-load-band.js'
import { RelayAssignmentStore } from '../assignment-store.js'
import { openInMemoryRelayDatabase } from '../../database/database.js'
@@ -13,74 +13,8 @@ function seededRandom(seed: number): () => number {
}
}
type SimulatedCell = { cellId: string; reserved: number; capacity: number }
const quiet = () => undefined
// Each pick goes to one of the directors at random, as the load balancer spreads requests.
function simulate(
directors: PlacementLoadBand[],
cells: SimulatedCell[],
picks: number,
at: (index: number) => number,
route: () => number = seededRandom(99)
) {
const counts = new Map(cells.map((cell) => [cell.cellId, 0]))
for (let index = 0; index < picks; index += 1) {
const ranked = cells
.filter((cell) => cell.reserved < cell.capacity)
.map((cell) => ({ cellId: cell.cellId, loadRatio: cell.reserved / cell.capacity, cell }))
.sort(
(left, right) => left.loadRatio - right.loadRatio || left.cellId.localeCompare(right.cellId)
)
const director = directors[Math.floor(route() * directors.length)]
const picked = director?.pick(ranked, at(index))
if (!picked) throw new Error('no candidate')
picked.cell.reserved += 1
counts.set(picked.cellId, (counts.get(picked.cellId) ?? 0) + 1)
}
return counts
}
// c27's drain on 10-08: 2,770 hosts onto one empty Asia cell (2,805 free)
// and four fuller ones (2,311 free between them).
function c27DrainCells(): SimulatedCell[] {
return [
{ cellId: 'cell-empty', reserved: 0, capacity: 4000 },
...['cell-b', 'cell-c', 'cell-d', 'cell-e'].map((cellId, index) => ({
cellId,
reserved: 4000 - 577 - (index === 0 ? 3 : 0),
capacity: 4000
}))
]
}
function drainShare(paceMs: number, seed: number) {
const cells = c27DrainCells()
const directors = Array.from(
{ length: 5 },
(_, index) => new PlacementLoadBand(seededRandom(seed + index), quiet)
)
const hosts = 2770
const counts = simulate(directors, cells, hosts, (index) => (index * paceMs) / hosts)
return { counts, cells, share: (counts.get('cell-empty') ?? 0) / hosts }
}
describe('PlacementLoadBand', () => {
it.each([
['1,200 s', 1_200_000],
['300 s', 300_000]
])('keeps the empty cell near the share across five directors at the %s pace', (_, paceMs) => {
for (const seed of [1, 2, 3]) {
const { counts, cells, share } = drainShare(paceMs, seed * 10)
expect([...counts.values()].reduce((sum, count) => sum + count, 0)).toBe(2770)
expect(share).toBeGreaterThan(0.3)
expect(share).toBeLessThan(0.5)
for (const cell of cells) expect(cell.reserved).toBeLessThanOrEqual(cell.capacity)
}
})
})
describe('leastLoadedCell with the load band', () => {
async function setupRegions() {
const database = await openInMemoryRelayDatabase()
@@ -121,20 +55,6 @@ describe('leastLoadedCell with the load band', () => {
return counts
}
it('spreads within the preferred region and keeps an empty US cell out', async () => {
const { database, store } = await setupRegions()
try {
const counts = await placements(store, 200)
expect(counts.get('us-a')).toBeUndefined()
expect(counts.get('asia-a')).toBeLessThanOrEqual(PLACEMENT_INTAKE_SHARE * 200 + 1)
const used = [...counts.values()].filter((count) => count >= 20)
expect(used.length).toBeGreaterThanOrEqual(3)
} finally {
await database.close()
}
})
it('fills the preferred region to its hard cap before spilling, never past it', async () => {
const { database, store } = await setupRegions()
try {
@@ -26,20 +26,6 @@ describe('reserve ledger writer', () => {
)
)[0]
it('writes a booked join, and only a higher epoch replaces it', async () => {
const { database: db, writer } = await setup()
writer.enqueue({ ...HOST, cellId: 'c1', epoch: 5 })
await writer.flush()
expect(await row(db)).toMatchObject({ cell_id: 'c1', assignment_epoch: 5 })
writer.enqueue({ ...HOST, cellId: 'c2', epoch: 4 })
writer.enqueue({ ...HOST, cellId: 'c3', epoch: 5 })
await writer.flush()
expect(await row(db)).toMatchObject({ cell_id: 'c1', assignment_epoch: 5 })
writer.enqueue({ ...HOST, cellId: 'c2', epoch: 6 })
await writer.flush()
expect(await row(db)).toMatchObject({ cell_id: 'c2', assignment_epoch: 6 })
})
it('drops a moved row’s counters but keeps a same-cell row’s', async () => {
const { database: db, writer } = await setup()
await db.query(
@@ -57,30 +43,15 @@ describe('reserve ledger writer', () => {
expect(await row(db, 'bbbbbbbbbbbbbbbb')).toMatchObject({ cell_id: 'c1', reserved_controls: 2 })
})
it('keeps its queue through a database stall and drops the oldest past the bound', async () => {
it('persists a booked join after a database stall', async () => {
let now = 1_000
const { database: db, writer } = await setup(() => now)
const query = vi.spyOn(db, 'query').mockRejectedValueOnce(new Error('timeout'))
vi.spyOn(db, 'query').mockRejectedValueOnce(new Error('timeout'))
writer.enqueue({ ...HOST, cellId: 'c1', epoch: 5 })
await writer.flush()
expect(writer.pending()).toBe(1)
await writer.flush()
expect(query).toHaveBeenCalledTimes(1)
now += 1_000
await writer.flush()
expect(writer.pending()).toBe(0)
expect(await row(db)).toMatchObject({ cell_id: 'c1', assignment_epoch: 5 })
const bounded = new ReserveLedgerWriter(db, { queueMax: 2, log: () => undefined })
const hosts = ['oldest0000000000', 'middle0000000000', 'newest0000000000']
for (const relayHostId of hosts) {
bounded.enqueue({ ...HOST, relayHostId, cellId: 'c1', epoch: 1 })
}
expect(bounded.pending()).toBe(2)
await bounded.flush()
expect(await row(db, hosts[0])).toBeUndefined()
expect(await row(db, hosts[1])).toMatchObject({ cell_id: 'c1', assignment_epoch: 1 })
expect(await row(db, hosts[2])).toMatchObject({ cell_id: 'c1', assignment_epoch: 1 })
})
it('reconciles the map’s seats and never lowers an epoch', async () => {
@@ -1,179 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
runReservePlacementSimulation,
type SimulationConfig,
type SimulationReport
} from './reserve-placement-simulation.js'
const SEEDS = [11, 23, 47]
const rtt = (region: string) => (region === 'asia-east2' ? 170 : 10)
function fleet(): SimulationConfig['cells'] {
const cells: SimulationConfig['cells'] = []
for (let index = 0; index < 24; index += 1) {
cells.push({
cellId: `us${String(index).padStart(2, '0')}`,
region: 'us-central1',
hardCap: 600,
ceiling: 480,
intakePerSec: 5,
// A third of the fleet stays in database mode, one cell on an image without step 5.
mode: index % 3 === 0 ? 'db' : 'reserve',
old: index === 0,
preseated: 150
})
}
for (let index = 0; index < 6; index += 1) {
cells.push({
cellId: `asia${index}`,
region: 'asia-east2',
hardCap: 600,
ceiling: 480,
intakePerSec: 2,
mode: index === 0 ? 'db' : 'reserve',
preseated: 200
})
}
return cells
}
function expectInvariants(report: SimulationReport): void {
expect(report.violations).toEqual([])
}
describe('step 5 deterministic simulation', () => {
// Invariant 9 (dead-man) never trips here; its own tests below cover it.
it.each(SEEDS)(
'holds invariants 1-8 across mixed versions, lagging polls, restarts and database stalls (seed %i)',
async (seed) => {
const report = await runReservePlacementSimulation({
seed,
durationMs: 25 * 60_000,
directors: 5,
// One director still on the database-only revision.
oldDirectors: 1,
cells: fleet(),
hosts: 6_600,
hostRegion: (index) => (index % 5 === 0 ? 'asia-east2' : 'us-central1'),
arrivalPerSec: 20,
meanSessionMs: 8 * 60_000,
reconnectDelayMs: [500, 5_000],
duplicateAssignShare: 0.05,
halfOpenShare: 0.05,
pollLagMs: (director) => [0, 200, 1_000, 4_000, 10_000][director]!,
rttMs: rtt,
cellRestarts: [{ at: 6 * 60_000, cellId: 'us04' }, { at: 14 * 60_000, cellId: 'asia2' }],
directorRestarts: [{ at: 8 * 60_000, director: 2 }, { at: 16 * 60_000, director: 4 }],
// Drained hosts land elsewhere while their old seat lingers: placement supersedes it.
drains: [{ at: 12 * 60_000, cellId: 'us05', paceMs: 60_000 }],
databaseStalls: Array.from({ length: 3 }, (_, index) => ({
at: (index * 610 + 120) * 1_000,
durationMs: 7_000
}))
})
expectInvariants(report)
expect(report.reservePlacements).toBeGreaterThan(500)
expect(report.stickyAnswers).toBeGreaterThan(500)
expect(report.seatedAtEnd).toBeGreaterThan(4_000)
// The run reached the paths the invariants guard, so a pass is not vacuous.
expect(report.supersedes).toBeGreaterThan(0)
expect(report.rowAheadDemotions).toBeGreaterThan(0)
expect(report.directorRestarts).toBe(2)
expect(report.stickyReserves).toBeGreaterThan(0)
expect(report.databasePlacements).toBeGreaterThan(0)
},
120_000
)
it.each(SEEDS)(
'near full: extra reserve calls stay within 10% and exhausted requests within 1% (seed %i)',
async (seed) => {
const cells = Array.from({ length: 17 }, (_, index) => ({
cellId: `us${index}`,
region: 'us-central1' as const,
hardCap: 700,
ceiling: 600,
intakePerSec: 50,
mode: 'reserve' as const,
preseated: 582
}))
const report = await runReservePlacementSimulation({
seed,
durationMs: 3 * 60_000,
directors: 5,
cells,
hosts: 17 * 582 + 300,
hostRegion: () => 'us-central1',
arrivalPerSec: 10,
meanSessionMs: 0,
reconnectDelayMs: [1_000, 3_000],
duplicateAssignShare: 0,
halfOpenShare: 0,
pollLagMs: () => 0,
rttMs: rtt
})
expectInvariants(report)
expect(report.reservePlacements).toBeGreaterThan(250)
expect(report.reserveCalls / report.reservePlacements - 1).toBeLessThanOrEqual(0.1)
expect(report.exhausted / report.requests).toBeLessThanOrEqual(0.01)
for (const share of Object.values(report.maxOccupancyShare)) expect(share).toBeLessThanOrEqual(1)
},
120_000
)
it.each(SEEDS.flatMap((seed) => [[1_200_000, seed], [300_000, seed]] as const))(
'a drain onto an empty cell at the %i ms pace spreads within each cell intake budget (seed %i)',
async (paceMs, seed) => {
// Production c27 drain: 1,200s pacing toward cells with 2,805/841/777/667/26 free seats.
const free = [2_805, 841, 777, 667, 26]
const cells: SimulationConfig['cells'] = free.map((seats, index) => ({
cellId: `asia${index + 1}`,
region: 'asia-east2',
hardCap: 3_000,
ceiling: 2_830,
intakePerSec: 2,
mode: 'reserve',
preseated: 2_830 - seats
}))
cells.unshift({
cellId: 'asia0',
region: 'asia-east2',
hardCap: 3_000,
ceiling: 2_830,
intakePerSec: 2,
mode: 'reserve',
preseated: 2_770
})
const preseated = cells.reduce((sum, cell) => sum + (cell.preseated ?? 0), 0)
const report = await runReservePlacementSimulation({
seed,
durationMs: 30 * 60_000,
directors: 5,
cells,
hosts: preseated,
hostRegion: () => 'asia-east2',
arrivalPerSec: 1,
meanSessionMs: 0,
reconnectDelayMs: [1_000, 3_000],
duplicateAssignShare: 0.02,
halfOpenShare: 0,
pollLagMs: () => 0,
rttMs: rtt,
drains: [{ at: 10_000, cellId: 'asia0', paceMs }]
})
expectInvariants(report)
// Below every cell's budget nothing waits; above it the cells' buckets pace the drain.
if (paceMs === 300_000) expect(report.intakeRefusals + report.paced).toBeGreaterThan(0)
const moved = Object.entries(report.arrivalsByCell)
const total = moved.reduce((sum, [, count]) => sum + count, 0)
expect(total).toBeGreaterThan(2_000)
// Spread: the emptiest cell takes a share, not the drain.
expect((report.arrivalsByCell.asia1 ?? 0) / total).toBeLessThan(0.6)
expect(moved.filter(([, count]) => count / total > 0.1).length).toBeGreaterThanOrEqual(3)
},
120_000
)
})
@@ -2,7 +2,7 @@ import { exportJWK, generateKeyPair, SignJWT, type CryptoKey, type JWK } from 'j
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RelayConfig } from '../config.js'
import { createRelayTokenVerifier } from './relay-token-verifier.js'
import { createStaleIfErrorJWKSet, RELAY_JWKS_MAX_STALE_MS } from './stale-if-error-jwks.js'
import { createStaleIfErrorJWKSet } from './stale-if-error-jwks.js'
const config = {
jwksUrl: 'https://auth.example.test/.well-known/jwks.json',
@@ -43,37 +43,7 @@ beforeEach(() => vi.useFakeTimers({ now: Date.parse('2026-10-11T13:00:00Z') }))
afterEach(() => vi.useRealTimers())
describe('relay token keys while auth is down', () => {
it('accepts a valid token from the last served set once the cache is past its 10 min age', async () => {
const key = await signingKey('auth-1')
const authServer = auth(key)
const jwks = createStaleIfErrorJWKSet(new URL(config.jwksUrl), { fetch: authServer.fetch })
const verify = createRelayTokenVerifier(config as RelayConfig, jwks)
expect(await verify(await relayToken(key))).not.toBeNull()
vi.setSystemTime(Date.now() + 20 * MINUTE)
authServer.state.up = false
const fetches = authServer.fetch.mock.calls.length
expect(await verify(await relayToken(key))).toMatchObject({ sub: 'user-1' })
expect(authServer.fetch.mock.calls.length).toBe(fetches + 1)
// Inside the retry window the stale set answers without waiting on another fetch.
expect(await verify(await relayToken(key))).not.toBeNull()
expect(authServer.fetch.mock.calls.length).toBe(fetches + 1)
})
it('rejects a token signed by a key the last served set never had', async () => {
const key = await signingKey('auth-1')
const authServer = auth(key)
const jwks = createStaleIfErrorJWKSet(new URL(config.jwksUrl), { fetch: authServer.fetch })
const verify = createRelayTokenVerifier(config as RelayConfig, jwks)
expect(await verify(await relayToken(key))).not.toBeNull()
vi.setSystemTime(Date.now() + MINUTE)
authServer.state.up = false
expect(await verify(await relayToken(await signingKey('unknown')))).toBeNull()
})
it('bounds the stale set at 30 minutes since the last served set', async () => {
expect(RELAY_JWKS_MAX_STALE_MS).toBe(30 * MINUTE)
const key = await signingKey('auth-1')
const authServer = auth(key)
const verify = createRelayTokenVerifier(
@@ -1,314 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { RelayConfig } from './config.js'
vi.mock('./authentication/admin-token-verifier.js', () => ({
createAdminTokenVerifier: () => async (token: string) => token === 'deploy-token',
createReadOnlyAdminTokenVerifier: () => async () => false,
createRegionalRehomeControlApplyTokenVerifier: () => async () => false,
createRegionalRehomeRuntimeTokenVerifier: () => async () => false,
createRegionalRehomeTokenVerifier: () => async () => false,
createRuntimeTokenVerifier: () => async () => false
}))
vi.mock('./authentication/relay-token-verifier.js', () => ({
createRelayTokenVerifier: () => async () => null,
readBearer: (value: string | undefined) => value?.replace(/^Bearer /, '') ?? null
}))
import { createRelayApp } from './app.js'
import { RelayAssignmentStore } from './assignment/assignment-store.js'
import type { CellReserveClient } from './cell/reservations/cell-reserve-client.js'
import { openInMemoryRelayDatabase, type RelayDatabase } from './database/database.js'
import { ReserveAssignment, type ReservePlacementMode } from './assignment/reserve-placement/reserve-assignment.js'
import { ReservePlacer } from './assignment/reserve-placement/reserve-placement.js'
import {
ShadowSeatDirectory,
type SeatFeedCell,
type SeatFeedResponse
} from './cell/seats/shadow-seat-directory.js'
const INCARNATION = '11111111-1111-4111-8111-111111111111'
const CELLS = [
{ id: 'cell-a', url: 'https://relay-a.example.com', capacityRequests: 10 },
{ id: 'cell-b', url: 'https://relay-b.example.com', capacityRequests: 10 }
]
const PER_HOST_FIELDS = [
'reservedRequests',
'assignments',
'activityLeases',
'activityRequestUnits',
'restartBlockingActivityLeases',
'restartBlockingActivityRequestUnits',
'restartBlockingReservedRequests',
'outgoingMigrations',
'incomingMigrations'
]
function feed(cellId: string, overrides: Partial<SeatFeedResponse> = {}): SeatFeedResponse {
return {
v: 1,
cellId,
incarnation: INCARNATION,
seq: 1,
at: 0,
draining: false,
counts: { seats: 40, controls: 42, bookings: 3, units: 45, ceiling: 500 },
intake: { perSec: 5, burst: 20, tokens: 20 },
flagsApplied: { generation: 3, flags: { admitMode: 'reserve' } },
admitModeEffective: 'reserve',
admitModeRaw: 'reserve',
full: [],
...overrides
}
}
describe('reserve-aware cell status', () => {
let database: RelayDatabase | undefined
afterEach(async () => {
await database?.close()
database = undefined
})
async function setup(
options: { mode?: ReservePlacementMode; startedAt?: number; placement?: boolean } = {}
) {
const now = { value: 100_000 }
database = await openInMemoryRelayDatabase()
const store = new RelayAssignmentStore(database, () => now.value, { heartbeatTtlMs: 45_000 })
await store.reconcileCells(CELLS)
for (const cell of CELLS) {
await store.recordCellHeartbeat({
cellId: cell.id,
cellUrl: cell.url,
cellIncarnation: INCARNATION,
startedAt: 50,
ready: true,
observedRequests: 0
})
}
await store.assign({ userId: 'user-a', relayHostId: 'host000000000001' })
await store.setCellAdmitMode('cell-a', 'reserve')
const cells: SeatFeedCell[] = CELLS.map((cell) => ({
cellId: cell.id,
cellUrl: cell.url,
region: 'us-central1',
heartbeatExpiresAt: Number.MAX_SAFE_INTEGER,
requiredForComplete: true,
general: true
}))
const directory = new ShadowSeatDirectory()
directory.setCells(cells.map((cell) => cell.cellId))
const reservePlacement = new ReserveAssignment({
mode: options.mode ?? 'on',
directory,
cells: () => cells,
startedAt: options.startedAt ?? 0,
placer: new ReservePlacer(
() => now.value,
() => 0.25
),
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the status route never books or demotes.
client: {} as CellReserveClient,
readRow: async () => null,
now: () => now.value
})
const app = createRelayApp(config(), {
store: {} as never,
assignments: store,
...(options.placement === false ? {} : { reservePlacement }),
drain: vi.fn(),
ready: vi.fn(async () => true)
})
const status = async (body: Record<string, unknown>) =>
await app.request('/v1/admin/cell-status', {
method: 'POST',
headers: { authorization: 'Bearer deploy-token', 'content-type': 'application/json' },
body: JSON.stringify({ v: 1, ...body })
})
return { store, directory, now, status }
}
it('reads a database cell exactly as today, and leaves a reserve cell without per-host counts', async () => {
const { store } = await setup()
expect(await store.reserveAwareCellDeploymentStatus('cell-b')).toEqual({
admitMode: 'db',
status: await store.cellDeploymentStatus('cell-b')
})
await expect(store.cellDeploymentStatus('cell-a')).rejects.toThrow('cell_in_reserve_mode')
const reserve = await store.reserveAwareCellDeploymentStatus('cell-a')
expect(reserve).toMatchObject({
admitMode: 'reserve',
status: {
cellId: 'cell-a',
runtime: { cellIncarnation: INCARNATION, ready: true, heartbeatFresh: true }
}
})
expect(Object.keys(reserve.status).filter((key) => PER_HOST_FIELDS.includes(key))).toEqual([])
await database!.query(
'ALTER TABLE relay_cell_admit_modes RENAME TO relay_cell_admit_modes_gone'
)
const unknown = new RelayAssignmentStore(database!, () => 100_000)
await expect(unknown.reserveAwareCellDeploymentStatus('cell-b')).rejects.toThrow(
'reserve_mode_unknown'
)
})
it('keeps the 404 for a reserve cell without the opt-in, and 400s an unknown view', async () => {
const { status } = await setup()
const refused = await status({ cellId: 'cell-a' })
expect(refused.status).toBe(404)
expect(await refused.json()).toEqual({ error: 'cell_in_reserve_mode' })
expect((await status({ cellId: 'cell-a', view: 'all' })).status).toBe(400)
})
it('answers a reserve cell from the map, with perHost null and never zeros', async () => {
const { directory, now, status } = await setup()
directory.apply('cell-a', feed('cell-a', { draining: true }), now.value, now.value - 1_500)
directory.apply(
'cell-b',
feed('cell-b', { admitModeEffective: 'db', admitModeRaw: 'db' }),
now.value,
now.value
)
now.value += 20_000
const response = await status({ cellId: 'cell-a', view: 'reserve-aware' })
expect(response.status).toBe(200)
const body = await statusBody(response)
expect(body.status).toMatchObject({
cellId: 'cell-a',
admissionState: 'general',
runtime: { cellIncarnation: INCARNATION },
perHost: null,
reserve: {
admitMode: 'reserve',
feedStatus: 'live',
// Reported, not enforced: the caller rejects a stale map.
polledAgeMs: 21_500,
incarnation: INCARNATION,
draining: true,
controls: 42,
seats: 40,
bookings: 3,
units: 45,
ceiling: 500,
intakePerSec: 5,
free: 500 - (45 - 3) - 3
}
})
expect(Object.keys(body.status).filter((key) => PER_HOST_FIELDS.includes(key))).toEqual([])
})
it('reports nulls, not zeros, for a restarted cell that has not applied its reserve switch', async () => {
const { directory, now, status } = await setup()
directory.apply(
'cell-a',
feed('cell-a', {
counts: { seats: 0, controls: 0 },
intake: undefined,
flagsApplied: { generation: 0, flags: {} },
admitModeEffective: 'db',
admitModeRaw: 'db'
}),
now.value,
now.value
)
const body = await statusBody(await status({ cellId: 'cell-a', view: 'reserve-aware' }))
expect(body.status.reserve).toMatchObject({
admitMode: 'db',
bookings: null,
units: null,
ceiling: null,
intakePerSec: null,
free: null
})
})
it('answers a retryable 503 while this director cannot vouch for the cell', async () => {
const unavailable = async (response: Response) => ({
status: response.status,
retryAfter: response.headers.get('Retry-After'),
body: await response.json()
})
const expected = (reason: string) => ({
status: 503,
retryAfter: '5',
body: { error: 'reserve_status_unavailable', reason }
})
for (const options of [{ mode: 'dry-run' as const }, { placement: false }]) {
const { directory, now, status } = await setup(options)
directory.apply('cell-a', feed('cell-a'), now.value, now.value)
expect(await unavailable(await status({ cellId: 'cell-a', view: 'reserve-aware' }))).toEqual(
expected('placement-off')
)
await database?.close()
}
// A starting director with one cell unheard.
const starting = await setup({ startedAt: 99_000 })
starting.directory.apply('cell-a', feed('cell-a'), starting.now.value, starting.now.value)
expect(
await unavailable(await starting.status({ cellId: 'cell-a', view: 'reserve-aware' }))
).toEqual(expected('map-incomplete'))
await database?.close()
const { directory, now, status } = await setup()
directory.apply('cell-b', feed('cell-b'), now.value, now.value)
directory.markNoFeed('cell-a', now.value)
expect(await unavailable(await status({ cellId: 'cell-a', view: 'reserve-aware' }))).toEqual(
expected('no-feed')
)
// A feed without the drain flag or counts is an older image: no feed, never an empty cell.
directory.apply('cell-a', feed('cell-a', { draining: undefined }), now.value, now.value)
expect(await unavailable(await status({ cellId: 'cell-a', view: 'reserve-aware' }))).toEqual(
expected('no-feed')
)
directory.apply('cell-a', feed('cell-a', { counts: undefined }), now.value, now.value)
expect(await unavailable(await status({ cellId: 'cell-a', view: 'reserve-aware' }))).toEqual(
expected('no-feed')
)
// The process that answered the feed is not the one heartbeating the runtime row.
directory.apply(
'cell-a',
feed('cell-a', { incarnation: '22222222-2222-4222-8222-222222222222' }),
now.value,
now.value
)
expect(await unavailable(await status({ cellId: 'cell-a', view: 'reserve-aware' }))).toEqual(
expected('incarnation-mismatch')
)
})
})
async function statusBody(response: Response) {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the route answers { v, status } on 200.
return (await response.json()) as { v: number; status: Record<string, unknown> }
}
function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
return {
port: 8080,
publicUrl: 'https://relay.example.test',
cellUrl: 'https://relay.example.test',
region: 'us-central1',
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: new Uint8Array(32),
role: 'director',
cellId: 'director',
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
runtimeServiceAccount: 'relay-cell@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 10,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data',
...overrides
}
}
@@ -1,4 +1,3 @@
import { ASSIGNMENT_LIMITS } from '@orca-cloud/relay-contract/assignment-invariants'
import { createHash } from 'node:crypto'
import { afterEach, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../../assignment/assignment-store.js'
@@ -27,41 +26,6 @@ const BASE_MEMBERSHIP = {
general: ['c1', 'c2', 'c3']
}
class FailAfterIntentDatabase implements RelayDatabase {
private transactionsUntilFailure = Number.POSITIVE_INFINITY
constructor(private readonly delegate: RelayDatabase) {}
failSecondTransaction(): void {
this.transactionsUntilFailure = 2
}
async query(sql: string, params?: unknown[]): Promise<SqlRow[]> {
return await this.delegate.query(sql, params)
}
async queryLocked(
sql: string,
params?: unknown[],
options?: RelayLockOptions
): Promise<SqlRow[]> {
return await this.delegate.queryLocked(sql, params, options)
}
async transaction<T>(operation: (transaction: RelayDatabase) => Promise<T>): Promise<T> {
this.transactionsUntilFailure--
if (this.transactionsUntilFailure === 0) {
this.transactionsUntilFailure = Number.POSITIVE_INFINITY
throw new Error('injected_commit_ambiguity')
}
return await this.delegate.transaction(operation)
}
async close(): Promise<void> {
await this.delegate.close()
}
}
class AfterTransactionDatabase implements RelayDatabase {
private afterTransaction: (() => Promise<void>) | undefined
@@ -154,34 +118,6 @@ describe('relay cell admission selector', () => {
})
})
it('excludes existing-only and migration-only cells from dormant placement', async () => {
let now = 100
const store = await setup(() => now)
const identity = { userId: 'dormant', relayHostId: 'host000000000001' }
const assignment = await store.assign(identity)
const control = await store.activateControl(identity, {
cellId: assignment.cellId,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
await store.releaseActivity(identity, control)
now += ASSIGNMENT_LIMITS.dormantTtlMs + 1
await store.applyCellAdmissionSelector({
attemptId: 'cutover_002',
expectedGeneration: 0,
expectedMembershipSha256: BASE_MEMBERSHIP_SHA256,
membership: INITIAL_MEMBERSHIP
})
await expect(store.rebalanceDormant(identity, 'c2')).rejects.toThrow(
'target_cell_unavailable'
)
await expect(store.rebalanceDormant(identity, 'c3')).resolves.toMatchObject({
cellId: 'c3'
})
})
it('does not recover an unfenced dead existing-only assignment', async () => {
let now = 100
const store = await setup(() => now, true)
@@ -283,94 +219,6 @@ describe('relay cell admission selector', () => {
])
})
it('persists failed CAS intent without mutating current membership', async () => {
const store = await setup(() => 100)
await store.applyCellAdmissionSelector({
attemptId: 'cutover_005',
expectedGeneration: 0,
expectedMembershipSha256: BASE_MEMBERSHIP_SHA256,
membership: INITIAL_MEMBERSHIP
})
await expect(
store.applyCellAdmissionSelector({
attemptId: 'stale_attempt',
expectedGeneration: 5,
membership: {
existingOnly: ['c1'],
migrationOnly: [],
general: ['c2', 'c3']
}
})
).rejects.toThrow('admission_selector_generation_mismatch')
await expect(store.inspectCellAdmissionSelector('stale_attempt')).resolves.toMatchObject({
selector: { generation: 1, membership: INITIAL_MEMBERSHIP },
intent: { state: 'diverged' }
})
})
it('rejects duplicate or incomplete selector membership before mutation', async () => {
const store = await setup(() => 100)
await expect(
store.applyCellAdmissionSelector({
attemptId: 'duplicate_001',
expectedGeneration: 0,
expectedMembershipSha256: BASE_MEMBERSHIP_SHA256,
membership: {
existingOnly: ['c1', 'c1'],
migrationOnly: ['c2'],
general: ['c3']
}
})
).rejects.toThrow('admission_selector_duplicate_cell')
await expect(
store.applyCellAdmissionSelector({
attemptId: 'incomplete_001',
expectedGeneration: 0,
expectedMembershipSha256: BASE_MEMBERSHIP_SHA256,
membership: {
existingOnly: ['c1'],
migrationOnly: ['c2'],
general: []
}
})
).rejects.toThrow('admission_selector_incomplete_membership')
await expect(store.inspectCellAdmissionSelector()).resolves.toMatchObject({
selector: {
generation: 0,
membership: { existingOnly: [], migrationOnly: [], general: ['c1', 'c2', 'c3'] }
}
})
})
it('inspects an unchanged durable intent after an ambiguous apply failure', async () => {
const underlying = await openInMemoryRelayDatabase()
const failingDatabase = new FailAfterIntentDatabase(underlying)
database = failingDatabase
const store = new RelayAssignmentStore(failingDatabase, () => 100)
await store.reconcileCells(CELLS)
failingDatabase.failSecondTransaction()
const input = {
attemptId: 'ambiguous_001',
expectedGeneration: 0,
expectedMembershipSha256: BASE_MEMBERSHIP_SHA256,
membership: INITIAL_MEMBERSHIP
}
await expect(store.applyCellAdmissionSelector(input)).rejects.toThrow(
'injected_commit_ambiguity'
)
await expect(store.inspectCellAdmissionSelector(input.attemptId)).resolves.toMatchObject({
selector: { generation: 0 },
intent: { state: 'unchanged' }
})
await expect(store.applyCellAdmissionSelector(input)).resolves.toMatchObject({
changed: true,
selector: { generation: 1, membership: INITIAL_MEMBERSHIP }
})
})
it('allows only one concurrent CAS and never re-enables legacy cells', async () => {
const store = await setup(() => 100)
await store.applyCellAdmissionSelector({
@@ -416,24 +264,6 @@ describe('relay cell admission selector', () => {
).rejects.toThrow('admission_selector_legacy_reenable')
})
it('preserves the selector boundary across compatible reconciliation', async () => {
const store = await setup(() => 100)
await store.applyCellAdmissionSelector({
attemptId: 'cutover_007',
expectedGeneration: 0,
expectedMembershipSha256: BASE_MEMBERSHIP_SHA256,
membership: INITIAL_MEMBERSHIP
})
await expect(store.reconcileCells(CELLS, false)).resolves.toBeUndefined()
await expect(store.reconcileCells([CELLS[0]!, CELLS[2]!])).rejects.toThrow(
'admission_selector_boundary_active'
)
await expect(store.inspectCellAdmissionSelector()).resolves.toMatchObject({
selector: { generation: 1, membership: INITIAL_MEMBERSHIP }
})
})
it('atomically adds exact migration-only cells after the selector boundary', async () => {
const store = await setup(() => 100)
await store.applyCellAdmissionSelector({
@@ -1,83 +0,0 @@
import pg from 'pg'
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import { RelayAssignmentStore } from '../../assignment/assignment-store.js'
import { reconcileCellAdmissionAtStartup } from './cell-admission-startup.js'
import type { RelayCellConfig } from '../../config.js'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
const schema = 'relay_startup_retry_test'
const cell: RelayCellConfig = {
id: 'startup-retry-cell',
url: 'https://startup-retry.example.test',
capacityRequests: 4_000
}
afterEach(() => vi.restoreAllMocks())
describePostgres('PostgreSQL director startup reconciliation', () => {
const databases: RelayDatabase[] = []
let scopedDatabaseUrl = ''
beforeAll(async () => {
const client = new pg.Client({ connectionString: databaseUrl })
await client.connect()
try {
await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
await client.query(`CREATE SCHEMA ${schema}`)
} finally {
await client.end()
}
const url = new URL(databaseUrl!)
url.searchParams.set('options', `-c search_path=${schema}`)
scopedDatabaseUrl = url.toString()
databases.push(
await openRelayDatabase({ databaseUrl: scopedDatabaseUrl, dataDir: '' }),
await openRelayDatabase({ databaseUrl: scopedDatabaseUrl, dataDir: '' })
)
})
afterAll(async () => {
for (const database of databases) await database.close()
const client = new pg.Client({ connectionString: databaseUrl })
await client.connect()
try {
await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
} finally {
await client.end()
}
})
it('recovers after the cell inventory lock outlasts transaction retries', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const store = new RelayAssignmentStore(databases[0]!, () => 100)
await store.reconcileCells([cell], false)
let releaseLock: () => void = () => undefined
let reportLocked: () => void = () => undefined
const lockReleased = new Promise<void>((resolve) => (releaseLock = resolve))
const lockAcquired = new Promise<void>((resolve) => (reportLocked = resolve))
const holder = databases[1]!.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT cell_id FROM relay_cells ORDER BY cell_id ASC`)
reportLocked()
await lockReleased
})
await lockAcquired
const releaseTimer = setTimeout(releaseLock, 3_500)
try {
await reconcileCellAdmissionAtStartup({ role: 'director', cells: [cell] }, store)
} finally {
clearTimeout(releaseTimer)
releaseLock()
await holder
}
expect(await databases[0]!.query(`SELECT cell_id FROM relay_cells`)).toEqual([
{ cell_id: cell.id }
])
const warnings = warn.mock.calls.flat().join('\n')
expect(warnings).toContain('orca_relay_startup_reconcile_recovered')
expect(warnings).not.toContain('orca_relay_postgres_transaction_exhausted')
}, 10_000)
})
@@ -1,44 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
reconcileCellAdmissionAtStartup
} from './cell-admission-startup.js'
import type { RelayCellConfig } from '../../config.js'
const cells: RelayCellConfig[] = [
{
id: 'candidate',
url: 'https://candidate.relay.example.com',
capacityRequests: 4_000,
initiallyEnabled: false
}
]
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
})
describe('cell admission startup authority', () => {
it('bounds retry wall time when each reconciliation is slow', async () => {
vi.useFakeTimers()
vi.setSystemTime(0)
vi.spyOn(Math, 'random').mockReturnValue(0)
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const lockUnavailable = Object.assign(new Error('lock unavailable'), { code: '55P03' })
const reconcileCellsAtStartup = vi.fn().mockImplementation(async () => {
vi.setSystemTime(Date.now() + 10_000)
throw lockUnavailable
})
const startup = reconcileCellAdmissionAtStartup(
{ role: 'director', cells },
{ reconcileCellsAtStartup }
)
const rejection = expect(startup).rejects.toBe(lockUnavailable)
await vi.runAllTimersAsync()
await rejection
expect(reconcileCellsAtStartup).toHaveBeenCalledTimes(5)
})
})
@@ -1,119 +0,0 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../../assignment/assignment-store.js'
import type { RelayCellConfig } from '../../config.js'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
const CELL_PREFIX = 'connection-cap-min-postgres'
const FLEET_SIZE = 16
function cellConfig(id: string, connectionHardCap: 1_000 | 3_000): RelayCellConfig {
return {
id,
url: `https://${id}.example.com`,
capacityRequests: 1_000,
connectionHardCap,
connectionUnobservedBound: 60
}
}
async function removeCells(database: RelayDatabase): Promise<void> {
for (const table of [
'relay_cell_connection_snapshots',
'relay_cell_connection_runtime',
'relay_cell_connection_limits',
'relay_cell_capabilities',
'relay_cell_rehome_safety',
'relay_cell_runtime',
'relay_cell_admission',
'relay_cell_regions',
'relay_cells'
]) {
await database.query(`DELETE FROM ${table} WHERE cell_id LIKE ?`, [`${CELL_PREFIX}-%`])
}
}
function storeOn(database: RelayDatabase): RelayAssignmentStore {
return new RelayAssignmentStore(database, () => 100, {
requireLiveCells: true,
heartbeatTtlMs: 45_000
})
}
async function heartbeat(
store: RelayAssignmentStore,
cell: RelayCellConfig,
connectionHardCap: number,
units: number
): Promise<void> {
await store.recordCellHeartbeat({
cellId: cell.id,
cellUrl: cell.url,
cellIncarnation: '11111111-1111-4111-8111-111111111111',
startedAt: 50,
ready: true,
observedRequests: 0,
totalConnections: units,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: units,
connectionHardCap,
connectionUnobservedBound: 60
})
}
describePostgres('PostgreSQL effective connection cap', () => {
let database: RelayDatabase
let racer: RelayDatabase
beforeAll(async () => {
database = await openRelayDatabase({ databaseUrl, dataDir: '' })
racer = await openRelayDatabase({ databaseUrl, dataDir: '' })
await removeCells(database)
})
afterAll(async () => {
await removeCells(database)
await racer.close()
await database.close()
})
it('keeps 16 raised cells placeable while a heartbeat races the raise', async () => {
const ids = Array.from({ length: FLEET_SIZE }, (_, index) => `${CELL_PREFIX}-fleet-${index}`)
const lower = ids.map((id) => cellConfig(id, 1_000))
const director = storeOn(database)
const otherInstance = storeOn(racer)
await director.reconcileCells(lower, true)
for (const cell of lower) await heartbeat(director, cell, 1_000, 10)
// The heartbeat lands before or after the raise; either order must leave the same state.
await Promise.all([
director.reconcileCells(
ids.map((id) => cellConfig(id, 3_000)),
true
),
heartbeat(otherInstance, lower[0]!, 1_000, 11)
])
for (const id of ids) {
await expect(director.cellDeploymentStatus(id)).resolves.toMatchObject({
connectionCapacity: {
hardCap: 3_000,
reportedHardCap: 1_000,
effectiveHardCap: 1_000,
heartbeatFresh: true
}
})
const rows = await database.query(
`SELECT cell_id FROM relay_cell_connection_snapshots WHERE cell_id = ?`,
[id]
)
expect(rows).toHaveLength(1)
}
// The old processes keep heartbeating their 1,000 under the raised config.
for (const cell of lower) {
await expect(heartbeat(otherInstance, cell, 1_000, 12)).resolves.toBeUndefined()
}
})
})
@@ -1,14 +1,11 @@
import { createHash } from 'node:crypto'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { RelayAssignmentStore } from '../../assignment/assignment-store.js'
import { encodeMembership, type CellAdmissionMembership } from './cell-admission-selector.js'
import type { RelayCellConfig } from '../../config.js'
import { openInMemoryRelayDatabase, type RelayDatabase } from '../../database/database.js'
const HEARTBEAT_TTL_MS = 45_000
const BOUND = 60
const OLD_INCARNATION = '11111111-1111-4111-8111-111111111111'
const NEW_INCARNATION = '22222222-2222-4222-8222-222222222222'
function cell(index: number, connectionHardCap: 1_000 | 3_000): RelayCellConfig {
const id = `us-c${String(index).padStart(2, '0')}`
@@ -40,7 +37,7 @@ type Report = {
async function setup(cells: RelayCellConfig[]) {
const database = await openInMemoryRelayDatabase()
databases.push(database)
let now = 100
const now = 100
const store = new RelayAssignmentStore(database, () => now, {
requireLiveCells: true,
heartbeatTtlMs: HEARTBEAT_TTL_MS
@@ -81,32 +78,13 @@ async function setup(cells: RelayCellConfig[]) {
}
return rows
}
const isolateForRoll = async (cellId: string): Promise<void> => {
const current = await store.inspectCellAdmissionSelector()
const membership: CellAdmissionMembership = {
existingOnly: [],
migrationOnly: [cellId],
general: cells.map(({ id }) => id).filter((id) => id !== cellId)
}
await store.applyCellAdmissionSelector({
attemptId: `isolate-${cellId}`,
expectedGeneration: current.selector.generation,
expectedMembershipSha256: createHash('sha256')
.update(encodeMembership(current.selector.membership))
.digest('hex'),
membership,
rollIsolatedCells: [cellId]
})
}
return {
database,
store,
heartbeat,
capacity,
reportedHardCap,
telemetryRows,
isolateForRoll,
setNow: (value: number) => (now = value)
telemetryRows
}
}
@@ -215,63 +193,4 @@ describe('effective connection cap = min(configured, reported)', () => {
harness.store.assign({ userId: 'user-a', relayHostId: 'host000000000001' })
).rejects.toThrow('relay_capacity_exhausted')
})
it('places a mixed fleet against each cell’s own reported cap', async () => {
const small = cell(1, 3_000)
const large = cell(2, 3_000)
const harness = await setup([small, large])
await harness.heartbeat(small, { connectionHardCap: 1_000, units: 840 })
await harness.heartbeat(large, { connectionHardCap: 3_000, units: 840 })
expect(await harness.capacity(small.id)).toMatchObject({ normalAdmissionPause: 840 })
expect(await harness.capacity(large.id)).toMatchObject({ normalAdmissionPause: 2_840 })
for (let index = 0; index < 4; index++) {
await expect(
harness.store.assign({ userId: `user-${index}`, relayHostId: `host00000000000${index}` })
).resolves.toMatchObject({ cellId: large.id })
}
})
it('has no unavailable window while a cell rolls 1,000 -> 3,000', async () => {
const cells = [cell(1, 1_000), cell(2, 1_000)]
const [rolled, neighbour] = cells
const harness = await setup(cells)
const identity = { userId: 'user-a', relayHostId: 'host000000000001' }
await harness.heartbeat(rolled!, { connectionHardCap: 1_000 })
await harness.heartbeat(neighbour!, { connectionHardCap: 1_000, units: 500 })
const home = await harness.store.assign(identity)
expect(home.cellId).toBe(rolled!.id)
await harness.store.reconcileCells(
cells.map((_, index) => cell(index + 1, 3_000)),
true
)
// The old process keeps heartbeating its 1,000 well past the TTL; the host stays sticky.
for (const at of [30_000, 60_000, 90_000]) {
harness.setNow(at)
await harness.heartbeat(rolled!, { connectionHardCap: 1_000 })
await harness.heartbeat(neighbour!, { connectionHardCap: 1_000, units: 500 })
await expect(harness.store.assign(identity)).resolves.toMatchObject({
cellId: rolled!.id,
assignmentEpoch: home.assignmentEpoch
})
}
// The roll isolates the live old process: the isolated-incumbent lane re-places the host.
await harness.isolateForRoll(rolled!.id)
const moved = await harness.store.assign(identity)
expect(moved).toMatchObject({ cellId: neighbour!.id, placed: true })
// The replacement boots and reports the new cap.
harness.setNow(120_000)
await harness.heartbeat(rolled!, {
connectionHardCap: 3_000,
incarnation: NEW_INCARNATION,
startedAt: 110_000
})
expect(await harness.capacity(rolled!.id)).toMatchObject({
effectiveHardCap: 3_000,
heartbeatFresh: true
})
})
})
@@ -1,55 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { RelayConfig } from '../config.js'
import { startCellHeartbeat } from './cell-heartbeat-client.js'
const CONFIG = {
role: 'cell',
cellId: 'cell-a',
cellUrl: 'https://relay-a.example.com',
directorUrl: 'https://relay.example.com',
heartbeatAudience: 'https://relay.example.com/v1/admin/cell-heartbeat'
} as RelayConfig
describe('cell heartbeat client', () => {
afterEach(() => vi.restoreAllMocks())
it('sends an immediate authenticated heartbeat without putting credentials in the URL', async () => {
const requests: Array<{ url: string; init?: RequestInit }> = []
const fetchImpl = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
requests.push({ url: String(url), init })
return new Response('{}', { status: 200 })
}) as typeof fetch
const client = startCellHeartbeat(CONFIG, {
ready: async () => true,
observedRequests: () => 7,
connectionCounts: () => ({
totalConnections: 0,
inFlightConnections: 0,
reservedConnectionUnits: 0,
enforcedConnectionUnits: 0
}),
fetch: fetchImpl,
identityToken: async () => 'secret-token',
now: () => 123,
incarnation: '11111111-1111-4111-8111-111111111111',
intervalMs: 60_000
})!
await vi.waitFor(() => expect(requests).toHaveLength(1))
client.stop()
expect(requests[0]!.url).toBe('https://relay.example.com/v1/admin/cell-heartbeat')
expect(requests[0]!.url).not.toContain('secret-token')
expect(requests[0]!.init?.headers).toMatchObject({ authorization: 'Bearer secret-token' })
expect(JSON.parse(String(requests[0]!.init?.body))).toEqual({
v: 1,
cellId: 'cell-a',
cellUrl: 'https://relay-a.example.com',
region: 'us-central1',
cellIncarnation: '11111111-1111-4111-8111-111111111111',
startedAt: 123,
ready: true,
observedRequests: 7
})
})
})
@@ -5,7 +5,7 @@ import {
cellFlagObjectName,
cellFlagParser
} from './cell-flags.js'
import { CONTROL_FLAG_POLL_MS, startControlFlagChannel } from './relay-control-flag-channel.js'
import { startControlFlagChannel } from './relay-control-flag-channel.js'
const CELL_ID = 'production-gce-c7'
const ACCESS_TOKEN = 'ya29.secret-access-token'
@@ -46,7 +46,7 @@ function cellObject(generation: number, flags: Record<string, unknown>, cellId =
}
}
function cellChannel(fetchImpl: typeof fetch, autoStart = false) {
function cellChannel(fetchImpl: typeof fetch) {
return startControlFlagChannel({
objectName: cellFlagObjectName(CELL_ID),
defaults: CELL_FLAG_DEFAULTS,
@@ -54,7 +54,7 @@ function cellChannel(fetchImpl: typeof fetch, autoStart = false) {
appliedEvent: CELL_FLAGS_APPLIED_EVENT,
fetch: fetchImpl,
random: () => 0.5,
autoStart
autoStart: false
})
}
@@ -77,25 +77,6 @@ describe('control flag channel', () => {
.filter((line) => line.includes(`"event":"${CELL_FLAGS_APPLIED_EVENT}"`))
.map((line): unknown => JSON.parse(line))
it('flips a flag on and back off, logging each applied generation once', async () => {
const google = fakeGoogle(cellObject(5, { readinessLocal: true, ticketCheck: 'shadow' }))
const channel = cellChannel(google.fetchImpl)
await channel.poll()
await channel.poll()
expect(google.state.storageRequests[1]?.searchParams.get('ifGenerationNotMatch')).toBe('5')
google.state.object = cellObject(7, { readinessLocal: false, ticketCheck: 'off' })
await channel.poll()
expect(channel.applied()).toEqual({ generation: 7, flags: CELL_FLAG_DEFAULTS })
expect(appliedLines()).toEqual([
{
event: CELL_FLAGS_APPLIED_EVENT,
generation: 5,
flags: { ...CELL_FLAG_DEFAULTS, readinessLocal: true, ticketCheck: 'shadow' }
},
{ event: CELL_FLAGS_APPLIED_EVENT, generation: 7, flags: CELL_FLAG_DEFAULTS }
])
})
it('keeps the applied values through every kind of lost contact, logging once per cause', async () => {
const google = fakeGoogle(cellObject(5, { readinessLocal: true }))
const channel = cellChannel(google.fetchImpl)
@@ -230,19 +211,4 @@ describe('control flag channel', () => {
expect(channel.applied()).toMatchObject({ generation: 5, flags: { readinessLocal: true } })
})
it('reads one at a time and on a jittered 5 s cadence', async () => {
vi.useFakeTimers()
const google = fakeGoogle(cellObject(5, { readinessLocal: true }))
const channel = cellChannel(google.fetchImpl)
await Promise.all([channel.poll(), channel.poll()])
expect(google.state.storageRequests).toHaveLength(1)
const running = cellChannel(google.fetchImpl, true)
await vi.advanceTimersByTimeAsync(0)
expect(google.state.storageRequests).toHaveLength(2)
await vi.advanceTimersByTimeAsync(CONTROL_FLAG_POLL_MS)
expect(google.state.storageRequests).toHaveLength(3)
running.stop()
await vi.advanceTimersByTimeAsync(CONTROL_FLAG_POLL_MS * 3)
expect(google.state.storageRequests).toHaveLength(3)
})
})
@@ -4,69 +4,34 @@ import {
CellAdmitEffectiveWriter,
DEAD_MAN_SLOT_CLAIM_RETRY_MS
} from './cell-admit-effective-writer.js'
import { DEAD_MAN_SLOT_HOLD_MS } from './cell-dead-man-trip-slot.js'
function setup(
options: { fail?: boolean; busy?: boolean; busyWhen?: () => boolean; slotFree?: () => boolean } = {}
options: { slotFree?: () => boolean } = {}
) {
const clock = { value: 1_000 }
const writes: ('db' | 'reserve' | 'claim')[] = []
const pending: Array<() => void> = []
const logs: string[] = []
const writer = new CellAdmitEffectiveWriter({
write: async (mode) => {
writes.push(mode)
await new Promise<void>((resolve) => pending.push(resolve))
if (options.fail) throw new Error('connection refused')
},
claimTrip: async (slots) => {
writes.push('claim')
await new Promise<void>((resolve) => pending.push(resolve))
return slots > 0 && (options.slotFree?.() ?? true)
},
databaseBusy: () => options.busy === true || options.busyWhen?.() === true,
now: () => clock.value,
log: (line) => logs.push(line)
databaseBusy: () => false,
now: () => clock.value
})
const settle = async () => {
while (pending.length > 0) pending.shift()!()
await new Promise((resolve) => setTimeout(resolve, 0))
}
return { clock, writes, logs, writer, settle }
return { clock, writes, writer, settle }
}
describe('CellAdmitEffectiveWriter', () => {
it('writes on the first tick, on every change, and every 15 s, one write at a time', async () => {
const { clock, writes, writer, settle } = setup()
writer.tick('reserve', true)
writer.tick('db', true)
expect(writes).toEqual(['reserve'])
await settle()
// Tripped: the switch still says reserve, the raw mode db.
writer.tick('db', true)
await settle()
writer.tick('db', true)
expect(writes).toEqual(['reserve', 'db'])
clock.value += CELL_ADMIT_EFFECTIVE_REFRESH_MS
writer.tick('db', true)
expect(writes).toEqual(['reserve', 'db', 'db'])
})
// Step 5 off: a db switch never writes, so the dark roll adds no database write.
// Best-effort: a failed write never throws into the cell, logs at most once a minute, and retries.
it('retries a failed write on the next tick and rate-limits its log line', async () => {
const { clock, writes, logs, writer, settle } = setup({ fail: true })
writer.tick('db', true)
await settle()
clock.value += 1_000
writer.tick('db', true)
await settle()
expect(writes).toEqual(['db', 'db'])
expect(logs).toHaveLength(1)
expect(JSON.parse(logs[0]!)).toMatchObject({ event: 'orca_relay_cell_admit_effective_write_failed', mode: 'db' })
})
it('claims a held trip slot in place of its refresh, and permits the trip only once it lands', async () => {
let free = false
const { clock, writes, writer, settle } = setup({ slotFree: () => free })
@@ -97,20 +62,4 @@ describe('CellAdmitEffectiveWriter', () => {
expect(writes).toEqual(['reserve', 'claim', 'reserve', 'claim'])
})
it('refreshes a tripped row past a saturated pool before its slot hold runs out', async () => {
let busy = false
const { clock, writes, writer, settle } = setup({ busyWhen: () => busy })
writer.tick('db', true)
await settle()
busy = true
clock.value += 4 * 60_000
writer.tick('db', true)
expect(writes).toEqual(['db'])
clock.value += 60_000
writer.tick('db', true)
await settle()
expect(writes).toEqual(['db', 'db'])
expect(DEAD_MAN_SLOT_HOLD_MS).toBeGreaterThan(5 * 60_000)
})
})
@@ -8,8 +8,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
import WebSocket from 'ws'
import type { RawData } from 'ws'
import { CELL_FLAG_DEFAULTS, type CellFlags } from '../control-flags/cell-flags.js'
import { CELL_SEAT_DRIFT_SWEEP_MS } from '../seats/cell-seat-drift.js'
import type { CellSeatLog } from '../seats/cell-seat-log.js'
import type { RelayConfig } from '../../config.js'
import { PostgresDatabase } from '../../database/database.js'
import type { AppliedControlFlags } from '../control-flags/relay-control-flag-channel.js'
@@ -73,20 +71,6 @@ function cellConfig(port: number, issuer: string): RelayConfig {
}
}
// The registry's private feed log, to plant the drift the cell must repair.
function seatLogOf(sessions: object): CellSeatLog {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: HostSessionRegistry holds its feed log as `seatLog`.
return (sessions as { seatLog: CellSeatLog }).seatLog
}
// The JSON lines a test's console.warn spy saw for one event.
function warned(event: string): Array<Record<string, unknown>> {
return vi
.mocked(console.warn)
.mock.calls.map(([line]) => (typeof line === 'string' && line.startsWith('{') ? JSON.parse(line) : null))
.filter((entry) => entry?.event === event)
}
function nextMessage(socket: WebSocket): Promise<Record<string, unknown>> {
return new Promise((resolve, reject) => {
socket.once('message', (data: RawData) => {
@@ -265,9 +249,6 @@ describe('a reserve-mode cell admits from memory with its database wedged', () =
book,
setFlags: (next: Partial<CellFlags>) => {
applied = { generation: applied.generation + 1, flags: { ...CELL_FLAG_DEFAULTS, ...next } }
},
advance: (ms: number) => {
elapsedMs += ms
}
}
}
@@ -338,97 +319,6 @@ describe('a reserve-mode cell admits from memory with its database wedged', () =
return (page as { full: Array<{ relayHostId: string; epoch: number }> }).full.map((entry) => entry.epoch)
}
it('repairs at once the seat a director names when no session backs it', async () => {
const cell = await startCell({ admitMode: 'reserve' })
const identity = await cell.host()
cell.book(identity, 9)
await cell.connect(identity, 9)
// A stale seat at a generation above the live session's: its changes could never replace it.
const log = seatLogOf(cell.relay.sessions)
log.append({ kind: 'join', userId: 'user-1', relayHostId: identity.hostId, epoch: 8, generation: 5, at: 4_242 })
const before = cell.relay.sessions.seatFeed(null)
const seq = 'full' in before ? before.seq : 0
const seat = { v: 1 as const, userId: 'user-1', relayHostId: identity.hostId, epoch: 8, joinedAt: 4_242 }
expect(cell.relay.sessions.demote(seat)).toBe('not-seated')
expect(cell.relay.sessions.seatFeed(seq)).toMatchObject({
changes: [
{ kind: 'leave', epoch: 8, generation: 5, closeCode: 4409 },
{ kind: 'join', epoch: 9, generation: 1 }
]
})
expect(seatEpochs(cell)).toEqual([9])
expect(warned('orca_relay_cell_seat_log_drift')).toMatchObject([
{ kind: 'unbacked-seat', via: 'demote', reason: 'epoch', seatEpoch: 8, sessionEpoch: 9, relisted: true }
])
expect(JSON.stringify(warned('orca_relay_cell_seat_log_drift'))).not.toContain(identity.hostId)
// Asked again, there is nothing left to repair.
expect(cell.relay.sessions.demote(seat)).toBe('not-seated')
expect(cell.relay.sessions.seatFeed(seq + 2)).toMatchObject({ changes: [] })
})
it('leaves a seat no session backs once two sweeps in a row find it, with no director asking', async () => {
const cell = await startCell({ admitMode: 'db' })
const ghost = await cell.host()
seatLogOf(cell.relay.sessions).append({
kind: 'join',
userId: 'user-1',
relayHostId: ghost.hostId,
epoch: 10,
generation: 3,
at: 1_000
})
expect(seatEpochs(cell)).toEqual([10])
cell.advance(CELL_SEAT_DRIFT_SWEEP_MS)
expect(seatEpochs(cell)).toEqual([])
expect(warned('orca_relay_cell_seat_log_drift')).toMatchObject([
{ kind: 'unbacked-seat', via: 'sweep', reason: 'no-session', seatEpoch: 10, seatGeneration: 3, relisted: false }
])
// Left as moved: the cell will not seat it again at 10 from memory.
expect(cell.relay.sessions.seatFeed(0)).toMatchObject({ changes: [{}, { kind: 'leave', closeCode: 4409 }] })
})
it('lists again a live session the feed lost, once two sweeps in a row find it', async () => {
const cell = await startCell({ admitMode: 'reserve' })
const identity = await cell.host()
cell.book(identity, 9)
await cell.connect(identity, 9)
seatLogOf(cell.relay.sessions).append({
kind: 'leave',
userId: 'user-1',
relayHostId: identity.hostId,
epoch: 9,
generation: 1,
at: 5_000
})
expect(seatEpochs(cell)).toEqual([])
cell.advance(CELL_SEAT_DRIFT_SWEEP_MS)
expect(seatEpochs(cell)).toEqual([9])
expect(warned('orca_relay_cell_seat_log_drift')).toMatchObject([{ kind: 'unlisted-session', sessionEpoch: 9 }])
})
// A drained session's seat stays listed, with no session, until its socket's close lands.
it('repairs nothing for a seat that one sweep finds unbacked and is gone by the next', async () => {
const cell = await startCell({ admitMode: 'reserve' })
const identity = await cell.host()
cell.book(identity, 9)
const live = await cell.connect(identity, 9)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: ws keeps its net.Socket as `_socket`.
;(live.socket as unknown as { _socket: { pause(): void } })._socket.pause()
cell.relay.sessions.drainHost({
attemptId: 'attempt-1',
userId: 'user-1',
relayHostId: identity.hostId,
sourceAssignmentEpoch: 9,
graceMs: 0
})
await new Promise((resolve) => setTimeout(resolve, 20))
expect(seatEpochs(cell)).toEqual([9])
await new Promise((resolve) => setTimeout(resolve, RELAY_WEBSOCKET_FORCE_CLOSE_MS + 300))
cell.advance(CELL_SEAT_DRIFT_SWEEP_MS)
expect(seatEpochs(cell)).toEqual([])
expect(warned('orca_relay_cell_seat_log_drift')).toEqual([])
}, 10_000)
// Drained sockets can close seconds after map removal; reusing their generation let late leaves erase reconnected live seats.
it('keeps a session that reconnected while its drained predecessor was still closing', async () => {
const cell = await startCell({ admitMode: 'reserve' })
@@ -456,19 +346,6 @@ describe('a reserve-mode cell admits from memory with its database wedged', () =
expect(seatEpochs(cell)).toEqual([10])
}, 10_000)
// Repeated fresh joins for one identity replace session and seat together, leaving no drift to repair.
it('stays on the live session when the same identity connects afresh each turn', async () => {
const cell = await startCell({ admitMode: 'reserve' })
const identity = await cell.host()
for (const epoch of [9, 10, 11]) {
cell.book(identity, epoch)
expect((await cell.connect(identity, epoch)).ack).toMatchObject({ type: 'host-hello-ack' })
expect(seatEpochs(cell)).toEqual([epoch])
cell.advance(CELL_SEAT_DRIFT_SWEEP_MS)
}
expect(seatEpochs(cell)).toEqual([11])
expect(warned('orca_relay_cell_seat_log_drift')).toEqual([])
})
})
it('gives every booking unit back when the cell starts to drain', async () => {
@@ -542,44 +419,4 @@ describe('a reserve-mode cell admits from memory with its database wedged', () =
}).toEqual(before)
}, 30_000)
it('never crosses the hard cap under seeded bookings, hellos, rebooks and closes', async () => {
const hardCap = 6
const cell = await startCell({ admitMode: 'reserve' }, { hardCap, controlReserve: 1 })
const hosts = await Promise.all(Array.from({ length: 12 }, () => cell.host()))
const epochs = new Map<string, number>()
const open: WebSocket[] = []
let seed = 7
const random = () => {
seed = (seed * 1_103_515_245 + 12_345) % 2_147_483_648
return seed / 2_147_483_648
}
let peak = 0
for (let step = 0; step < 240; step += 1) {
const identity = hosts[Math.floor(random() * hosts.length)]!
const roll = random()
if (roll < 0.4) {
// A director's fresh booking, or a re-book at a newer epoch.
const epoch = (epochs.get(identity.hostId) ?? 1) + (random() < 0.5 ? 0 : 1)
if (cell.book(identity, epoch)[0]?.outcome === 'ok') epochs.set(identity.hostId, epoch)
} else if (roll < 0.8) {
const reply = await cell.connect(identity, epochs.get(identity.hostId) ?? 1)
if (reply.ack?.type === 'host-hello-ack') open.push(reply.socket!)
} else if (open.length > 0) {
const socket = open.splice(Math.floor(random() * open.length), 1)[0]!
// A newer control for the same host may already have replaced it.
if (socket.readyState !== WebSocket.CLOSED) {
const closed = new Promise((resolve) => socket.once('close', resolve))
socket.close()
await closed
}
}
const counts = cell.relay.connectionSnapshot()!
peak = Math.max(peak, counts.physicalConnections)
expect(counts.physicalConnections).toBeLessThanOrEqual(hardCap)
expect(counts.enforcedConnectionUnits).toBeLessThanOrEqual(hardCap)
}
// The run did reach the cap, so the bound was exercised.
expect(peak).toBeGreaterThanOrEqual(hardCap - 1)
}, 60_000)
})
@@ -1,41 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { CellReserveClient } from './cell-reserve-client.js'
const target = { cellId: 'c1', cellUrl: 'https://c1.example' }
const item = (userId: string, epoch = 3) => ({
userId,
relayHostId: 'abcdefghijklmnop',
epoch,
ttlMs: 30_000
})
describe('cell reserve client', () => {
it('batches a cell’s bookings into one request and maps each answer back', async () => {
const fetchImpl = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
const body = JSON.parse(String(init?.body)) as { items: Array<{ userId: string }> }
return Response.json({
v: 1,
results: body.items.map((entry) =>
entry.userId === 'b' ? { outcome: 'seated-newer', epoch: 9 } : { outcome: 'ok' }
)
})
})
const client = new CellReserveClient({
directorId: 'director-a',
identityToken: async () => 'token',
fetch: fetchImpl
})
const answers = await Promise.all([
client.reserve(target, item('a')),
client.reserve(target, item('b')),
client.reserve(target, item('c'), true)
])
expect(answers).toEqual([{ outcome: 'ok' }, { outcome: 'seated-newer', epoch: 9 }, { outcome: 'ok' }])
// One booking batch and one dry-run batch.
expect(fetchImpl).toHaveBeenCalledTimes(2)
const first = JSON.parse(String(fetchImpl.mock.calls[0]![1]?.body)) as Record<string, unknown>
expect(first).toMatchObject({ v: 1, directorId: 'director-a', items: [{ userId: 'a' }, { userId: 'b' }] })
expect(String(fetchImpl.mock.calls[0]![0])).toBe('https://c1.example/internal/reserve')
})
})
@@ -128,13 +128,4 @@ describe('dead-man trip slots', () => {
expect(seated.socket.readyState).toBe(WebSocket.OPEN)
}, 30_000)
it('without the flag, trips with every slot taken, as before', async () => {
let now = Date.now()
const { database, reserve } = await cell(() => now)
await trippedPeer(database, 'production-gce-c90', now)
await reserve.bookedHost(3)
expect(reserve.relay.sessions.inReserveMode()).toBe(true)
now += reserveDeadManWindowMs(reserve.config.cellId) + 1_000
expect(reserve.relay.sessions.inReserveMode()).toBe(false)
}, 30_000)
})
@@ -55,15 +55,6 @@ describe('ReserveDeadMan', () => {
])
})
it('keeps the plain window for a later reserve write once a director has been in touch', () => {
const { clock, deadMan } = setup(3 * WINDOW_MS)
deadMan.mode(reserve(1))
deadMan.contact()
expect(deadMan.mode(reserve(2))).toBe('reserve')
clock.value += WINDOW_MS + 1
expect(deadMan.mode(reserve(2))).toBe('db')
})
// A claim answered after the director came back must not trip the cell later without a slot.
it('drops a permit for a silence that contact has ended', () => {
const { clock, deadMan, events } = setup()
@@ -1,114 +0,0 @@
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import WebSocket from 'ws'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
import { openDelayedPostgresDatabase, type StatementDelay } from '../../test-fixtures/delayed-postgres-database.js'
import { RESERVE_MODE_CELL_ID, startReserveModeCell } from '../../test-fixtures/reserve-mode-cell.js'
// PostgreSQL flip-back keeps controls connected, reserves most pool capacity for DB work and folds cell deltas per tick even across cross-region round trips (E-mix).
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
async function until(check: () => Promise<boolean> | boolean, timeoutMs: number): Promise<void> {
const deadline = Date.now() + timeoutMs
while (!(await check())) {
if (Date.now() > deadline) throw new Error('condition not reached')
await new Promise((resolve) => setTimeout(resolve, 50))
}
}
describePostgres('flipping a reserve-mode cell back on PostgreSQL', () => {
const cleanup: Array<() => Promise<void> | void> = []
afterEach(async () => {
for (const close of cleanup.splice(0).reverse()) await close()
vi.restoreAllMocks()
// The database is shared with the other PostgreSQL suites, some of which read every cell.
const database = await openRelayDatabase({ databaseUrl, dataDir: tmpdir() })
try {
const tables = await database.query(
`SELECT table_name FROM information_schema.columns
WHERE table_schema = current_schema() AND column_name = 'cell_id' AND table_name LIKE 'relay_%'`
)
for (const row of tables) {
await database.query(`DELETE FROM ${String(row.table_name)} WHERE cell_id = ?`, [RESERVE_MODE_CELL_ID])
}
} finally {
await database.close()
}
})
async function cellOn(database: RelayDatabase, databasePoolMax = 10) {
vi.spyOn(console, 'log').mockImplementation(() => undefined)
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const dataDir = mkdtempSync(join(tmpdir(), 'orca-relay-flip-back-pg-'))
cleanup.push(() => rmSync(dataDir, { recursive: true, force: true }))
return await startReserveModeCell({ database, dataDir, cleanup, databasePoolMax })
}
it('re-registers every memory-admitted control, ten flips in a row', async () => {
const database = await openRelayDatabase({ databaseUrl, dataDir: tmpdir() })
cleanup.push(() => database.close())
for (let flip = 0; flip < 10; flip += 1) {
const cell = await cellOn(database)
const hosts = []
for (let index = 0; index < 3; index += 1) hosts.push(await cell.bookedHost(5 + index))
const ids = new Set(hosts.map((entry) => entry.identity.hostId))
const leased = async () =>
(await cell.controlLeases()).filter((row) => ids.has(String(row.relay_host_id)))
expect(await leased()).toEqual([])
cell.setAdmitMode('db')
await until(async () => (await leased()).length === 3, 10_000)
await until(() => cell.relay.sessions.reregistrationPending() === 0, 10_000)
expect(hosts.every((entry) => entry.socket.readyState === WebSocket.OPEN)).toBe(true)
for (const entry of hosts) entry.socket.close()
}
}, 120_000)
it('keeps re-registration to a third of the pool and one cell-row write per tick, at 170 ms a trip', async () => {
const delay: StatementDelay = { enabled: false, delayMs: 170 }
let cellRowWrites = 0
let leaseWritesInFlight = 0
let peakInFlight = 0
delay.beforeTrip = async (sql) => {
if (/UPDATE relay_cells\b/.test(sql)) cellRowWrites += 1
}
const database = openDelayedPostgresDatabase(databaseUrl!, delay, 10)
cleanup.push(() => database.close())
// The schema and the cell row, without the delay.
const setup = await openRelayDatabase({ databaseUrl, dataDir: tmpdir() })
await setup.close()
const cell = await cellOn(database, 10)
const deferred = cell.relay.assignments.activateControlDeferringCell.bind(cell.relay.assignments)
vi.spyOn(cell.relay.assignments, 'activateControlDeferringCell').mockImplementation(async (...args) => {
leaseWritesInFlight += 1
peakInFlight = Math.max(peakInFlight, leaseWritesInFlight)
try {
return await deferred(...args)
} finally {
leaseWritesInFlight -= 1
}
})
const hosts = []
for (let index = 0; index < 9; index += 1) hosts.push(await cell.bookedHost(5 + index))
const ids = new Set(hosts.map((entry) => entry.identity.hostId))
delay.enabled = true
const startedAt = Date.now()
cell.setAdmitMode('db')
await until(() => cell.relay.sessions.reregistrationPending() > 0, 5_000)
await until(() => cell.relay.sessions.reregistrationPending() === 0, 45_000)
const elapsedMs = Date.now() - startedAt
delay.enabled = false
const leased = (await cell.controlLeases()).filter((row) => ids.has(String(row.relay_host_id)))
expect(leased).toHaveLength(9)
expect(hosts.every((entry) => entry.socket.readyState === WebSocket.OPEN)).toBe(true)
// Pool of 10: at most 3 activations at once, leaving 7 for hellos and renewals.
expect(peakInFlight).toBeLessThanOrEqual(3)
// Far fewer cell-row writes than activations: deltas fold into one write per tick.
expect(cellRowWrites).toBeLessThan(9)
process.stderr.write(`flip_back_latency ${JSON.stringify({ elapsedMs, peakInFlight, cellRowWrites })}\n`)
}, 60_000)
})
@@ -3,7 +3,6 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import WebSocket from 'ws'
import { CELL_ADMIT_EFFECTIVE_REFRESH_MS } from './cell-admit-effective-writer.js'
import { RESERVE_DEAD_MAN_MS, reserveDeadManWindowMs } from './cell-reserve-dead-man.js'
import { openRelayDatabase } from '../../database/database.js'
import {
@@ -34,7 +33,7 @@ describe('flipping a reserve-mode cell back to the database', () => {
vi.restoreAllMocks()
})
async function cell(now?: () => number, options: { flagsUnread?: boolean } = {}) {
async function cell(now?: () => number) {
vi.spyOn(console, 'log').mockImplementation(() => undefined)
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
vi.spyOn(console, 'error').mockImplementation(() => undefined)
@@ -42,7 +41,7 @@ describe('flipping a reserve-mode cell back to the database', () => {
cleanup.push(() => rmSync(dataDir, { recursive: true, force: true }))
const database = await openRelayDatabase({ dataDir })
cleanup.push(() => database.close())
return await startReserveModeCell({ database, dataDir, cleanup, ...options, ...(now ? { now } : {}) })
return await startReserveModeCell({ database, dataDir, cleanup, ...(now ? { now } : {}) })
}
it('registers every memory-admitted control without disconnecting any, matching the seat report', async () => {
@@ -159,47 +158,6 @@ describe('flipping a reserve-mode cell back to the database', () => {
expect(host.socket.readyState).toBe(WebSocket.OPEN)
}, 60_000)
it('takes its flip-back pace from the switch file', async () => {
const reserve = await cell()
for (let index = 0; index < 4; index += 1) await reserve.bookedHost(5 + index)
const real = reserve.relay.assignments.activateControlDeferringCell.bind(reserve.relay.assignments)
let inFlight = 0
let peak = 0
vi.spyOn(reserve.relay.assignments, 'activateControlDeferringCell').mockImplementation(async (...args) => {
inFlight += 1
peak = Math.max(peak, inFlight)
await new Promise((resolve) => setTimeout(resolve, 50))
try {
return await real(...args)
} finally {
inFlight -= 1
}
})
reserve.setAdmitMode('db', { reregisterInFlight: 1 })
await until(async () => (await reserve.controlLeases()).length === 4)
expect(peak).toBe(1)
}, 30_000)
// Step 5 off adds no writes before/after flag reads; leaving reserve owes exactly one db write.
it('writes an admit-effective row only while its switch says reserve, plus one db write on the way out', async () => {
let now = Date.now()
const booting = await cell(() => now, { flagsUnread: true })
await new Promise((resolve) => setTimeout(resolve, 2_500))
expect(await booting.admitEffectiveRow()).toBeUndefined()
booting.setAdmitMode('db')
now += 3 * CELL_ADMIT_EFFECTIVE_REFRESH_MS
await new Promise((resolve) => setTimeout(resolve, 2_500))
expect(await booting.admitEffectiveRow()).toBeUndefined()
booting.setAdmitMode('reserve')
await until(async () => (await booting.admitEffectiveRow())?.mode === 'reserve')
booting.setAdmitMode('db')
await until(async () => (await booting.admitEffectiveRow())?.mode === 'db')
const last = (await booting.admitEffectiveRow())!.updated_at
now += 3 * CELL_ADMIT_EFFECTIVE_REFRESH_MS
await new Promise((resolve) => setTimeout(resolve, 2_500))
expect((await booting.admitEffectiveRow())!.updated_at).toEqual(last)
}, 30_000)
it('flips itself back to db when no placing director has been in touch for the dead-man window', async () => {
let now = Date.now()
const reserve = await cell(() => now)
@@ -50,7 +50,7 @@ const answer = (status: number) => new Response('{}', { status })
// Every cell serves a feed page on the first round; `respond` answers every round after it.
function poller(
respond: (cellId: string) => Response | Promise<Response>,
options: { identityFails?: boolean; cells?: SeatFeedCell[]; neverServed?: ReadonlySet<string> } = {}
options: { cells?: SeatFeedCell[]; neverServed?: ReadonlySet<string> } = {}
) {
let now = 1_000
let round = 0
@@ -66,7 +66,6 @@ function poller(
return round === 0 ? page(cellId) : await respond(cellId)
}),
identityToken: vi.fn(async () => {
if (options.identityFails && round > 0) throw new Error('metadata_unavailable')
return 'token-1'
}),
now: () => now,
@@ -107,13 +106,6 @@ describe('seat-feed poller reachability', () => {
}
})
it('does not blame a cell for a token this director cannot mint', async () => {
const cells = poller((cellId) => page(cellId), { identityFails: true })
const at = await cells.run(CELL_POLL_DEAD_STREAK_MS)
cells.stop()
expect(cells.deadCellVerdict('c2', at)).toBeNull()
})
it('counts only cells that served a feed: legacy 404 rows neither fill the sample nor dilute it', async () => {
// Staging's shape: a few real cells and 16 legacy rows. Every real cell fails.
const cells = poller(() => answer(502), { cells: [...CELLS.slice(0, 3), ...LEGACY] })
@@ -1,336 +0,0 @@
import { EventEmitter } from 'node:events'
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type WebSocket from 'ws'
import { z } from 'zod'
import type { RelayAssignmentStore } from '../../assignment/assignment-store.js'
import type { CellFlags } from '../control-flags/cell-flags.js'
import type { RelayConfig } from '../../config.js'
import type { RelayCredentialStore } from '../../credentials/credential-store.js'
import { HostSessionRegistry, type HostSession } from '../../connections/host-session-registry.js'
import type { AppliedControlFlags } from '../control-flags/relay-control-flag-channel.js'
import type { RelayRuntimeObserver } from '../../observability/relay-observability.js'
import type { RelayTokenClaims } from '../../authentication/relay-token-verifier.js'
import { ProcessQueuedByteBudget } from '../../connections/splice-forwarder.js'
const adminTokenChecks = vi.hoisted(() => ({ count: 0 }))
vi.mock('../../authentication/admin-token-verifier.js', () => ({
createAdminTokenVerifier: () => async (token: string) => {
adminTokenChecks.count += 1
return token === 'deploy-token'
},
createReadOnlyAdminTokenVerifier: () => async () => false,
createRegionalRehomeControlApplyTokenVerifier: () => async () => false,
createRegionalRehomeRuntimeTokenVerifier: () => async () => false,
createRegionalRehomeTokenVerifier: () => async (token: string) => token === 'rehome-token',
createRuntimeTokenVerifier: () => async () => false
}))
import { createRelayApp } from '../../app.js'
class FakeSocket extends EventEmitter {
readonly OPEN = 1
readonly CLOSED = 3
readyState = this.OPEN
readonly send = vi.fn()
readonly close = vi.fn((code?: number, reason?: string) => {
this.readyState = this.CLOSED
this.emit('close', code, Buffer.from(reason ?? ''))
})
}
const incarnation = '11111111-1111-4111-8111-111111111111'
// The published v1 shape, so a field rename fails here before it reaches a director.
const SeatChangeSchema = z.object({
seq: z.number(),
kind: z.enum(['join', 'leave', 'drain-only', 'active']),
userId: z.string(),
relayHostId: z.string(),
epoch: z.number(),
generation: z.number(),
state: z.enum(['active', 'drain-only']).optional(),
closeCode: z.number().optional(),
at: z.number()
})
const SeatFeedReplySchema = z.object({
v: z.literal(1),
cellId: z.string(),
incarnation: z.string(),
at: z.number(),
draining: z.boolean(),
counts: z.object({
controls: z.number(),
seats: z.number()
}),
flagsApplied: z
.object({
generation: z.number(),
flags: z.object({
readinessLocal: z.boolean(),
ticketCheck: z.enum(['off', 'shadow', 'enforce']),
rejectionFence: z.boolean(),
admitMode: z.enum(['db', 'reserve']),
reserveDryRun: z.boolean(),
phonePath: z.enum(['db', 'memory'])
}),
ignoredKeys: z.array(z.string()).optional()
})
.optional(),
seq: z.number(),
changes: z.array(SeatChangeSchema).optional(),
more: z.boolean().optional(),
full: z
.array(
z.object({
userId: z.string(),
relayHostId: z.string(),
epoch: z.number(),
generation: z.number(),
state: z.enum(['active', 'drain-only']),
joinedAt: z.number()
})
)
.optional()
})
function config(overrides: Partial<RelayConfig> = {}): RelayConfig {
return {
port: 8080,
publicUrl: 'https://c7.relay.example.test',
cellUrl: 'https://c7.relay.example.test',
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: new Uint8Array(32),
role: 'cell',
cellId: 'production-gce-c7',
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
rehomeDirectorServiceAccount: 'relay-director@example.test',
rehomeAudience: 'https://relay.example.test/v1/admin/host-drain',
runtimeServiceAccount: 'relay-cell@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 10,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data',
...overrides
}
}
function hostIdentity(index: number, exp = 4_102_444_800): RelayTokenClaims {
return {
sub: `user-${index}`,
prof: 'profile-1',
relayHostId: `host${String(index).padStart(12, '0')}`,
purpose: 'host-control',
exp
}
}
// What a host's auth-refresh token verifies as.
const verifyRelayToken = vi.fn<(token: string) => Promise<RelayTokenClaims | null>>()
function createCell(relayConfig = config(), cellFlags?: () => AppliedControlFlags<CellFlags>) {
const assignments = {
activateControl: vi.fn().mockResolvedValue('control:production-gce-c7:1'),
markMigrationTargetRegistered: vi.fn().mockResolvedValue(undefined),
releaseActivity: vi.fn().mockResolvedValue(true),
renewControlActivities: vi.fn().mockResolvedValue([])
} as unknown as RelayAssignmentStore
const observer = {
recordAuth: vi.fn(),
recordForwardedBytes: vi.fn(),
recordHttp: vi.fn(),
recordReconnect: vi.fn(),
recordSql: vi.fn()
} satisfies RelayRuntimeObserver
const registry = new HostSessionRegistry(
relayConfig,
verifyRelayToken,
{} as RelayCredentialStore,
assignments,
new ProcessQueuedByteBudget(),
observer,
Date.now,
Math.random,
incarnation
)
const activate = (
registry as unknown as {
activate: (
socket: WebSocket,
identity: RelayTokenClaims,
existing: HostSession | null,
generation: number,
rebind: boolean,
assignmentEpoch: number,
appVersion: string
) => Promise<void>
}
).activate.bind(registry)
const connect = async (
index: number,
options: { generation?: number; epoch?: number; rebind?: boolean; exp?: number } = {}
): Promise<FakeSocket> => {
const socket = new FakeSocket()
const identity = hostIdentity(index, options.exp)
await activate(
socket as unknown as WebSocket,
identity,
registry.get({ userId: identity.sub, relayHostId: identity.relayHostId }),
options.generation ?? 1,
options.rebind ?? false,
options.epoch ?? 1,
'1.4.200'
)
return socket
}
const app = createRelayApp(relayConfig, {
store: {} as never,
assignments: {} as never,
drain: (graceMs, options) => registry.drain(graceMs, options ?? {}),
cellIncarnation: incarnation,
cellSeatFeed: (sinceSeq) => registry.seatFeed(sinceSeq),
cellFlags,
cellPhonePathEffective: () => registry.phonePathEffective(),
isDraining: () => registry.isDraining(),
runtimeCounts: () => ({
totalConnections: 0,
preAuthConnections: 0,
queuedBytes: 0,
...registry.runtimeCounts()
}),
ready: vi.fn(async () => true)
})
const poll = async (since?: string, token = 'rehome-token') =>
await app.request(
`/v1/admin/cell-seats${since === undefined ? '' : `?since=${encodeURIComponent(since)}`}`,
{ headers: { authorization: `Bearer ${token}` } }
)
const read = async (since?: string) => SeatFeedReplySchema.parse(await (await poll(since)).json())
return { registry, connect, app, poll, read }
}
describe('cell seat feed', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
vi.clearAllTimers()
vi.useRealTimers()
})
it('keeps a closing socket seated until its close lands, as the change log does', async () => {
const { connect, read } = createCell()
const socket = await connect(1)
// The ws close handshake: no longer OPEN, close event not yet emitted.
socket.readyState = 2
const closing = await read()
expect(closing.counts).toMatchObject({ controls: 0, seats: 1 })
expect(closing.full).toHaveLength(1)
socket.readyState = socket.CLOSED
socket.emit('close', 1000, Buffer.from(''))
expect((await read()).counts).toMatchObject({ controls: 0, seats: 0 })
})
it('reports join, drain-only and leave as changes after the cursor', async () => {
const { registry, connect, read } = createCell()
const socket = await connect(1)
const { seq } = await read()
registry.drain(60_000)
socket.close(RELAY_CLOSE_CODE.DRAINING, 'relay draining')
await connect(2)
const body = await read(`${incarnation}:${seq}`)
expect(body.full).toBeUndefined()
expect(body.draining).toBe(true)
expect(body.changes?.map((change) => change.kind)).toEqual(['drain-only', 'leave'])
expect(body.changes?.[1]).toMatchObject({
userId: 'user-1',
closeCode: RELAY_CLOSE_CODE.DRAINING,
generation: 1
})
// The second connect landed after the drain, so the cell refused it: no join.
expect(body.seq).toBe(seq + 2)
})
it('reports an auth expiry as drain-only and the refresh that clears it as active', async () => {
vi.spyOn(console, 'warn').mockImplementation(() => {})
const { connect, read } = createCell()
const exp = Math.floor(Date.now() / 1000) + 5
const socket = await connect(1, { exp })
const { seq } = await read()
// The 15 s heartbeat finds the token expired, inside its grace.
await vi.advanceTimersByTimeAsync(15_000)
verifyRelayToken.mockResolvedValueOnce(hostIdentity(1, exp + 3_600))
socket.emit(
'message',
Buffer.from(JSON.stringify({ type: 'auth-refresh', relayJwt: 'refreshed' })),
false
)
await vi.waitFor(() => expect(verifyRelayToken).toHaveBeenCalled())
const body = await read(`${incarnation}:${seq}`)
expect(body.changes?.map((change) => change.kind)).toEqual(['drain-only', 'active'])
expect(body.counts.seats).toBe(1)
expect((await read()).full).toEqual([expect.objectContaining({ state: 'active' })])
})
it('does not report a rebind as a leave, and joins the rebound socket', async () => {
const { connect, read } = createCell()
await connect(1)
const { seq } = await read()
await connect(1, { rebind: true, epoch: 2 })
const body = await read(`${incarnation}:${seq}`)
expect(body.changes).toEqual([
expect.objectContaining({ kind: 'join', userId: 'user-1', epoch: 2, state: 'active' })
])
})
it('reports a superseded generation leaving after its successor joined', async () => {
const { connect, read } = createCell()
await connect(1)
const { seq } = await read()
await connect(1, { generation: 2 })
const body = await read(`${incarnation}:${seq}`)
// Readers drop the leave: its generation is older than the seat's.
expect(body.changes?.map((change) => [change.kind, change.generation])).toEqual([
['leave', 1],
['join', 2]
])
})
it('resyncs a cursor from another incarnation with a full snapshot', async () => {
const { connect, poll, read } = createCell()
await connect(1)
const body = await read('22222222-2222-4222-8222-222222222222:1')
expect(body.full).toHaveLength(1)
expect((await poll('not a cursor')).status).toBe(400)
})
it('accepts only the directors rehome identity, verified once per poll', async () => {
const { poll } = createCell()
adminTokenChecks.count = 0
expect((await poll(undefined, 'deploy-token')).status).toBe(401)
expect((await poll(undefined, 'wrong')).status).toBe(401)
expect((await poll()).status).toBe(200)
// The admin middleware leaves this route to its own rehome check.
expect(adminTokenChecks.count).toBe(0)
})
it('is unavailable on a cell without the rehome pair, and on directors', async () => {
const withoutPair = createCell(
config({ rehomeAudience: undefined, rehomeDirectorServiceAccount: undefined })
)
const response = await withoutPair.poll()
expect(response.status).toBe(404)
expect(await response.json()).toEqual({ error: 'seat_feed_unavailable' })
const director = createCell(config({ role: 'director', cellId: 'director' }))
expect((await director.poll()).status).toBe(404)
})
})
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { CELL_SEAT_FEED_PAGE_MAX, CellSeatLog } from './cell-seat-log.js'
import { CellSeatLog } from './cell-seat-log.js'
function append(log: CellSeatLog, count: number): void {
for (let index = 0; index < count; index += 1) {
@@ -24,16 +24,6 @@ describe('cell seat log', () => {
expect(log.read(5)).toMatchObject({ seq: 10, full: expect.any(Array) })
})
it('pages a long backlog and resumes from the returned cursor', () => {
const log = new CellSeatLog(CELL_SEAT_FEED_PAGE_MAX * 2)
append(log, CELL_SEAT_FEED_PAGE_MAX + 5)
const first = log.read(0)
expect(first).toMatchObject({ seq: CELL_SEAT_FEED_PAGE_MAX, more: true })
expect('changes' in first && first.changes).toHaveLength(CELL_SEAT_FEED_PAGE_MAX)
const second = log.read(first.seq)
expect(second).toMatchObject({ seq: CELL_SEAT_FEED_PAGE_MAX + 5, more: false })
expect('changes' in second && second.changes).toHaveLength(5)
})
})
describe('cell seat log snapshot', () => {
@@ -85,20 +75,5 @@ describe('cell seat cursor', () => {
expect(log.recentlyLeftOf(host.userId, host.relayHostId)).toBeUndefined()
})
it('sends only the newest page of leavers in a full snapshot', () => {
const log = new CellSeatLog(CELL_SEAT_FEED_PAGE_MAX * 4, () => 0)
const leavers = CELL_SEAT_FEED_PAGE_MAX + 5
for (let index = 0; index < leavers; index += 1) {
const host = { userId: `user-${index}`, relayHostId: 'abcdefghijklmnop', epoch: 1, generation: 1 }
log.append({ kind: 'join', ...host, at: 0 })
log.append({ kind: 'leave', ...host, closeCode: 1006, at: 0 })
}
const page = log.read(null)
const left = 'full' in page ? page.recentlyLeft : []
expect(left).toHaveLength(CELL_SEAT_FEED_PAGE_MAX)
expect(left.at(-1)?.userId).toBe(`user-${leavers - 1}`)
// The cell itself still remembers the oldest.
expect(log.recentlyLeftOf('user-0', 'abcdefghijklmnop')).toBeDefined()
})
})
@@ -1,166 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import type { RelayAssignment } from '../../assignment/assignment-store.js'
import type { RelayConfig } from '../../config.js'
import {
ShadowDirectoryCompare
} from './shadow-directory-compare.js'
import { ShadowSeatDirectory, type SeatFeedResponse } from './shadow-seat-directory.js'
const fakes = vi.hoisted(() => ({
verifyRelayToken: vi.fn(async (token: string) => ({ sub: 'user-a', relayHostId: token }))
}))
vi.mock('../../authentication/relay-token-verifier.js', () => ({
createRelayTokenVerifier: () => fakes.verifyRelayToken,
readBearer: (value: string | undefined) => value?.replace(/^Bearer /, '') ?? null
}))
import { createRelayApp } from '../../app.js'
const HOST = 'hhhhhhhhhhhhhhhh'
const IDENTITY = { userId: 'user-a', relayHostId: HOST }
function feed(cellId: string, overrides: Partial<SeatFeedResponse> = {}): SeatFeedResponse {
return { v: 1, cellId, incarnation: `inc-${cellId}`, seq: 1, at: 1, full: [], ...overrides }
}
function seat(epoch: number, state = 'active', relayHostId = HOST, joinedAt = 1) {
return { userId: 'user-a', relayHostId, epoch, generation: 1, state, joinedAt }
}
function directoryWith(
seats: Record<string, ReturnType<typeof seat>[]>,
noFeed: string[] = [],
unlive: string[] = []
) {
const directory = new ShadowSeatDirectory()
const cellIds = [...Object.keys(seats), ...noFeed]
directory.setCells(cellIds, cellIds, {
readAt: 0,
expiresAt: new Map(
cellIds.filter((cellId) => !unlive.includes(cellId)).map((cellId) => [cellId, 1e12])
)
})
for (const [cellId, full] of Object.entries(seats)) {
directory.apply(cellId, feed(cellId, { full }), 10)
}
for (const cellId of noFeed) directory.markNoFeed(cellId, 10)
return directory
}
describe('unexplained samples', () => {
it('log only the cell and epoch of the real database record, never raw ids', () => {
const lines: Record<string, unknown>[] = []
const compare = new ShadowDirectoryCompare(
directoryWith({ 'cell-a': [seat(3)] }),
() => 20,
(line) => lines.push(JSON.parse(line))
)
compare.compare('sticky-verify', IDENTITY, { ...assignment('cell-a'), assignmentEpoch: 2 })
compare.flush(20)
const sample = lines.find((line) => line.event === 'orca_relay_shadow_compare_unexplained')
expect(sample).toMatchObject({
class: 'epoch-mismatch',
db: { cellId: 'cell-a', assignmentEpoch: 2 }
})
expect(JSON.stringify(lines)).not.toContain(HOST)
expect(JSON.stringify(lines)).not.toContain('user-a')
})
})
describe('compare hooks on the director routes', () => {
async function respond(compareShadowSeats?: () => void) {
const resolved = assignment('cell-a')
const app = createRelayApp(config(), {
// SAFETY: /v1/resolve reads only resolveResume.
store: { resolveResume: vi.fn(async () => ({ userId: 'user-a' })) } as never,
// SAFETY: both routes read only resolve and assign.
assignments: {
resolve: vi.fn(async () => resolved),
assign: vi.fn(async () => resolved)
} as never,
drain: vi.fn(),
ready: vi.fn(async () => true),
compareShadowSeats
})
const sticky = await app.request('/v1/assign', {
method: 'POST',
headers: { authorization: `Bearer ${HOST}`, 'content-type': 'application/json' },
body: JSON.stringify({ v: 1, relayHostId: HOST, reconnect: true })
})
const resolve = await app.request('/v1/resolve', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
v: 1,
relayHostId: HOST,
resumeToken: Buffer.alloc(32, 1).toString('base64url')
})
})
return [
{ status: sticky.status, body: await sticky.text() },
{ status: resolve.status, body: await resolve.text() }
]
}
it('compares after each database answer without changing a response byte', async () => {
vi.useFakeTimers({ now: 1_000_000 })
try {
const baseline = await respond()
const routes: unknown[] = []
const compared = await respond((...args: unknown[]) => {
routes.push(args[0])
})
const throwing = await respond(() => {
throw new Error('compare broke')
})
expect(baseline[0]?.status).toBe(200)
expect(baseline[1]?.status).toBe(200)
expect(compared).toEqual(baseline)
expect(throwing).toEqual(baseline)
expect(routes).toEqual(['sticky-verify', 'resolve'])
} finally {
vi.useRealTimers()
}
})
})
function assignment(cellId: string): RelayAssignment {
return {
userId: 'user-a',
relayHostId: HOST,
cellId,
cellUrl: `https://${cellId}.relay.example.test`,
assignmentEpoch: 3,
leaseExpiresAt: 1_300_000
}
}
function config(): RelayConfig {
return {
port: 8080,
publicUrl: 'https://relay.example.test',
cellUrl: 'https://relay.example.test',
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: new TextEncoder().encode('assignment-key-with-at-least-32-bytes'),
role: 'director',
cellId: 'director',
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
runtimeServiceAccount: 'runtime@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 3,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data'
}
}
@@ -3,7 +3,6 @@ import { RelayAssignmentStore } from '../../assignment/assignment-store.js'
import { openInMemoryRelayDatabase } from '../../database/database.js'
import {
SHADOW_SEAT_OUTRANK_SKEW_MS,
SHADOW_SEAT_RECENTLY_LEFT_MAX_HOSTS,
ShadowSeatDirectory,
startShadowSeatPoller,
type SeatFeedCell,
@@ -141,7 +140,6 @@ describe('startShadowSeatPoller', () => {
{ cellId: 'cell-a', epoch: 3, generation: 1, state: 'active' }
])
expect(cell.directory.cellState('cell-a')).toMatchObject({ status: 'live', seq: 5 })
expect(cell.identityToken).toHaveBeenCalledTimes(1)
})
it('resyncs from a full snapshot when the cell restarts with a new incarnation', async () => {
@@ -284,7 +282,6 @@ describe('startShadowSeatPoller', () => {
await cell.tick()
await cell.tick()
cell.stop()
expect(call).toBe(3)
expect(cell.requests[2]?.searchParams.get('since')).toBe(`${INCARNATION}:1`)
expect(cell.directory.seatsOf('user-a', 'host-2')).toHaveLength(1)
})
@@ -336,31 +333,6 @@ describe('startShadowSeatPoller', () => {
expect(cell.directory.isComplete()).toBe(true)
})
it('forgets the switches a cell reported once its image no longer has the feed', async () => {
let rolledBack = false
const cell = poller({
'cell-a': () => json(feed({ seq: 1, full: [] })),
'cell-b': () =>
rolledBack
? new Response('Not Found', { status: 404 })
: json(
feed({
cellId: 'cell-b',
seq: 1,
full: [],
flagsApplied: { generation: 3, flags: { readinessLocal: true } }
})
)
})
await cell.tick()
expect(cell.directory.cellState('cell-b')?.flagsApplied?.generation).toBe(3)
rolledBack = true
await cell.tick()
cell.stop()
expect(cell.directory.cellState('cell-b')).toMatchObject({ status: 'no-feed' })
expect(cell.directory.cellState('cell-b')?.flagsApplied).toBeUndefined()
})
it('keeps a reserve switch through a 404 until a feed says db or the process is replaced', () => {
const directory = new ShadowSeatDirectory()
directory.setCells(['cell-b'])
@@ -379,58 +351,6 @@ describe('startShadowSeatPoller', () => {
expect(directory.cellState('cell-b')?.flagsApplied).toBeUndefined()
})
it('completes without a cell that is dead, empty or isolated', async () => {
const cell = poller({ 'cell-a': () => json(feed({ seq: 1, full: [] })) }, 'all', async () => [
CELLS[0]!,
{ ...CELLS[1]!, requiredForComplete: false }
])
await cell.tick()
cell.stop()
expect(cell.directory.cellState('cell-b')).toMatchObject({ status: 'pending' })
expect(cell.directory.isComplete()).toBe(true)
})
it('backs off cell-list reads while the database fails', async () => {
const listCells = vi.fn(async (): Promise<SeatFeedCell[]> => {
throw new Error('pool timeout')
})
const cell = poller({}, 'all', listCells)
for (let second = 0; second < 40; second += 1) {
await cell.tick()
cell.advance(1_000)
}
cell.stop()
// Retries at +1, +2, +4, +8, +16 s, then at most every 30 s.
expect(listCells.mock.calls.length).toBeLessThanOrEqual(7)
expect(listCells.mock.calls.length).toBeGreaterThanOrEqual(5)
})
it('keeps polling the last cell list while a cell-list read hangs', async () => {
const readEntered = Promise.withResolvers<void>()
const readDone = Promise.withResolvers<SeatFeedCell[]>()
const listCells = vi
.fn<() => Promise<SeatFeedCell[]>>()
.mockResolvedValueOnce(CELLS)
.mockImplementation(() => {
readEntered.resolve()
return readDone.promise
})
const cell = poller({ 'cell-a': () => json(feed({ seq: 1, full: [] })) }, ['cell-a'], listCells)
await cell.tick()
cell.advance(31_000)
const round = cell.tick()
await readEntered.promise
await Promise.resolve()
expect(listCells).toHaveBeenCalledTimes(2)
expect(cell.requests.map((url) => url.hostname)).toEqual([
'cell-a.example.test',
'cell-a.example.test'
])
readDone.resolve(CELLS)
await round
cell.stop()
})
it('treats a malformed or mislabelled body as unverifiable', async () => {
const cell = poller({
'cell-a': () => json({ v: 2 }),
@@ -483,20 +403,6 @@ describe('startShadowSeatPoller', () => {
expect(cell.directory.seatsOf('user-a', 'host-1')).toEqual([])
})
it('bounds the recently-left memory', () => {
const directory = new ShadowSeatDirectory()
directory.setCells(['cell-a'])
const hosts = SHADOW_SEAT_RECENTLY_LEFT_MAX_HOSTS + 10
directory.apply(
'cell-a',
feed({ seq: 1, full: Array.from({ length: hosts }, (_, index) => seat(`host-${index}`)) }),
10
)
directory.apply('cell-a', feed({ incarnation: 'restarted', seq: 1, full: [] }), 20)
expect(directory.summary(20).recentlyLeftHosts).toBe(SHADOW_SEAT_RECENTLY_LEFT_MAX_HOSTS)
expect(directory.recentlyLeftOf('user-a', 'host-0', 20)).toEqual([])
expect(directory.recentlyLeftOf('user-a', `host-${hosts - 1}`, 20)).toHaveLength(1)
})
})
describe('RelayAssignmentStore.seatFeedCells', () => {
@@ -2,11 +2,9 @@ import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { reserveDeadManWindowMs } from '../cell/reservations/cell-reserve-dead-man.js'
import { openRelayDatabase } from '../database/database.js'
import {
nextFrame,
RESERVE_MODE_CELL_ID,
startReserveModeCell
} from '../test-fixtures/reserve-mode-cell.js'
import { StatementRecordingDatabase } from '../test-fixtures/statement-recording-database.js'
@@ -22,19 +20,6 @@ const OTHER_CELL = {
capacityRequests: 900
}
const RESUME_DEVICE_READ = expect.stringMatching(/^SELECT .* FROM relay_devices\b/)
const HOST_ASSIGNMENT_READ = expect.stringMatching(
/^SELECT assignment\.\*.* FROM relay_assignments\b/
)
async function until(check: () => Promise<boolean>, timeoutMs = 10_000): Promise<void> {
const deadline = Date.now() + timeoutMs
while (!(await check())) {
if (Date.now() > deadline) throw new Error('condition not reached')
await new Promise((resolve) => setTimeout(resolve, 50))
}
}
describe('the memory phone path', () => {
const cleanup: Array<() => Promise<void> | void> = []
@@ -58,11 +43,7 @@ describe('the memory phone path', () => {
cleanup,
...(now ? { now } : {})
})
// The admit-effective writer ticks on its own; every other statement is the phone's.
const phoneStatements = () =>
database.statements.filter((sql) => !sql.includes('relay_cell_admit_effective'))
// The test's own reads and writes go around the recorder.
return { ...reserve, database, raw: inner, phoneStatements }
return { ...reserve, database, raw: inner }
}
async function seatedPhoneHost(phonePath: 'db' | 'memory') {
@@ -70,27 +51,23 @@ describe('the memory phone path', () => {
reserve.setAdmitMode('reserve', { phonePath })
const seated = await reserve.bookedHost(3)
await reserve.installDevice(seated.identity.hostId, TOKEN)
reserve.database.statements.length = 0
return { reserve, seated }
}
// A phone resumed and spliced to its desktop, waiting to confirm.
async function splicedPhone(
phonePath: 'db' | 'memory',
device: { expiresAt?: number } = {},
clock: { now?: () => number; beforeDial?: () => void } = {}
device: { expiresAt?: number } = {}
) {
const reserve = await cell(clock.now)
const reserve = await cell()
reserve.setAdmitMode('reserve', { phonePath })
const seated = await reserve.bookedHost(3)
await reserve.installDevice(seated.identity.hostId, TOKEN, device)
clock.beforeDial?.()
const connOpen = nextFrame(seated.socket, 'conn-open')
const phone = await reserve.dialPhone(seated.identity.hostId, TOKEN)
const basisConnId = String((await connOpen).connId)
await reserve.attachData(await connOpen, seated.ack.generation)
expect(await phone.hello).toMatchObject({ ok: true })
reserve.database.statements.length = 0
// The desktop's confirm, sent in reply to the phone over the end-to-end channel.
const confirm = async (reqId: string, basis = basisConnId) => {
const answer = Promise.race([
@@ -108,54 +85,7 @@ describe('the memory phone path', () => {
const deviceRow = async (reserve: Awaited<ReturnType<typeof cell>>) =>
(await reserve.raw.query(`SELECT current_expires_at FROM relay_devices`))[0]
const warnings = (event: string) =>
vi
.mocked(console.warn)
.mock.calls.map(([line]) => String(line))
.filter((line) => line.includes(event))
.map((line) => JSON.parse(line))
it('admits a seated phone resume from one device read and writes no activity lease', async () => {
const { reserve, seated } = await seatedPhoneHost('memory')
const connOpen = nextFrame(seated.socket, 'conn-open')
const phone = await reserve.dialPhone(seated.identity.hostId, TOKEN)
expect(await connOpen).toMatchObject({ kind: 'resume', relayDeviceId: 'device-1' })
expect(reserve.phoneStatements()).toEqual([RESUME_DEVICE_READ])
await reserve.attachData(await connOpen, seated.ack.generation)
expect(await phone.hello).toMatchObject({
type: 'relay-hello',
ok: true,
credentialKind: 'resume'
})
expect(reserve.phoneStatements()).toEqual([RESUME_DEVICE_READ])
expect(await reserve.activityLeases()).toEqual([])
})
it('persists the connection basis and activity leases while phonePath is db', async () => {
const { reserve, seated } = await seatedPhoneHost('db')
const connOpen = nextFrame(seated.socket, 'conn-open')
const phone = await reserve.dialPhone(seated.identity.hostId, TOKEN)
await reserve.attachData(await connOpen, seated.ack.generation)
expect(await phone.hello).toMatchObject({ type: 'relay-hello', ok: true })
expect((await reserve.activityLeases()).map((lease) => lease.activity_kind).sort()).toEqual([
'confirmation',
'splice'
])
expect(
await reserve.raw.query(
`SELECT basis_conn_id, relay_device_id, credential_kind, active FROM relay_connection_bases`
)
).toEqual([
{
basis_conn_id: String((await connOpen).connId),
relay_device_id: 'device-1',
credential_kind: 'resume',
active: 1
}
])
})
it('closes a revoked or expired device 4401 and never tries it as an invite', async () => {
it('closes a revoked or expired device 4401', async () => {
const reserve = await cell()
reserve.setAdmitMode('reserve', { phonePath: 'memory' })
const seated = await reserve.bookedHost(3)
@@ -165,32 +95,12 @@ describe('the memory phone path', () => {
expiresAt: Date.now() - 1_000
})
for (const refused of [token(2), token(3)]) {
reserve.database.statements.length = 0
const phone = await reserve.dialPhone(seated.identity.hostId, refused)
expect(await phone.hello).toEqual({ type: 'relay-hello', ok: false, code: 4401 })
expect((await phone.closed).code).toBe(4401)
expect(reserve.phoneStatements()).toEqual([RESUME_DEVICE_READ])
}
})
it('closes 4503 when the device read fails, without trying the invite path', async () => {
const { reserve, seated } = await seatedPhoneHost('memory')
vi.spyOn(reserve.relay.store, 'lookupResume').mockRejectedValue(
new Error('connection terminated')
)
const invite = vi.spyOn(reserve.relay.store, 'resolveInviteForMove')
for (let dial = 0; dial < 2; dial += 1) {
const phone = await reserve.dialPhone(seated.identity.hostId, TOKEN)
expect((await phone.closed).code).toBe(4503)
}
expect(invite).not.toHaveBeenCalled()
expect(reserve.phoneStatements()).toEqual([])
// One line opens the window; the repeat waits for its closing line.
expect(warnings('orca_relay_client_accept_db_error')).toEqual([
{ event: 'orca_relay_client_accept_db_error', cause: 'error', cellId: reserve.config.cellId }
])
})
it('falls through to the invite path only when no device row matches', async () => {
const { reserve, seated } = await seatedPhoneHost('memory')
const created = nextFrame(seated.socket, 'invite-created')
@@ -208,7 +118,7 @@ describe('the memory phone path', () => {
])
})
it('answers a phone whose host has no seat here from one assignment read', async () => {
it('distinguishes moved and offline hosts when a phone finds no seat here', async () => {
const reserve = await cell()
reserve.setAdmitMode('reserve', { phonePath: 'memory' })
await reserve.relay.assignments.reconcileCells([...reserve.config.cells, OTHER_CELL])
@@ -222,31 +132,19 @@ describe('the memory phone path', () => {
)
}
await reserve.installDevice(host.hostId, TOKEN)
reserve.database.statements.length = 0
const phone = await reserve.dialPhone(host.hostId, TOKEN)
const closed = await phone.closed
expect(reserve.phoneStatements()).toEqual([RESUME_DEVICE_READ, HOST_ASSIGNMENT_READ])
return closed.code
}
expect(await dial('elsewhere')).toBe(4409)
expect(await dial('none')).toBe(4404)
expect(warnings('orca_relay_client_miss_row_names_here')).toEqual([])
expect(await dial('here')).toBe(4404)
expect(warnings('orca_relay_client_miss_row_names_here')).toEqual([
expect.objectContaining({
cellId: reserve.config.cellId,
relayHostIdDigest: expect.any(String)
})
])
vi.spyOn(reserve.relay.assignments, 'resolve').mockRejectedValueOnce(
new Error('connection terminated')
)
const host = await reserve.host()
await reserve.installDevice(host.hostId, TOKEN)
expect((await (await reserve.dialPhone(host.hostId, TOKEN)).closed).code).toBe(4409)
expect(warnings('orca_relay_client_miss_read_error')).toEqual([
{ event: 'orca_relay_client_miss_read_error', cause: 'error', cellId: reserve.config.cellId }
])
})
it('takes the database path for a phone whose switch turned off during its device read', async () => {
@@ -265,7 +163,7 @@ describe('the memory phone path', () => {
])
})
it('confirms from the memory basis with one fresh read, renewing a device due for it', async () => {
it('confirms from the memory basis and renews a device due for it', async () => {
const { reserve, confirm } = await splicedPhone('memory')
const before = Number((await deviceRow(reserve))?.current_expires_at)
const answer = await confirm('confirm-1')
@@ -278,12 +176,6 @@ describe('the memory phone path', () => {
const after = Number((await deviceRow(reserve))?.current_expires_at)
expect(after).toBeGreaterThan(before)
expect(answer.resumeExpiresAt).toBe(after)
expect(reserve.phoneStatements()).toEqual([
RESUME_DEVICE_READ,
expect.stringMatching(
/^UPDATE relay_devices SET current_expires_at = \?.*RETURNING current_expires_at$/
)
])
expect(await reserve.activityLeases()).toEqual([])
// Its audit row leaves with the next batch.
await reserve.relay.store.resumeAudit.flush()
@@ -293,10 +185,8 @@ describe('the memory phone path', () => {
expect(audits.map((row) => JSON.parse(String(row.detail_json)))).toEqual([
{ reqId: 'confirm-1', basisConnId: expect.any(String), renewed: true }
])
// A retry of the same request answers from memory; another basis cannot reuse its reqId.
reserve.database.statements.length = 0
// A retry keeps its answer; another basis cannot reuse its reqId.
expect(await confirm('confirm-1')).toEqual(answer)
expect(reserve.phoneStatements()).toEqual([])
expect(await confirm('confirm-1', 'another-basis')).toMatchObject({
type: 'control-error',
code: 'confirmation_tuple_mismatch'
@@ -310,7 +200,6 @@ describe('the memory phone path', () => {
renewed: true,
resumeExpiresAt: fresh
})
expect(notDue.reserve.phoneStatements()).toEqual([RESUME_DEVICE_READ])
const due = Date.now() + 20 * 24 * 60 * 60 * 1000
const failing = await splicedPhone('memory', { expiresAt: due })
@@ -320,74 +209,20 @@ describe('the memory phone path', () => {
resumeExpiresAt: due
})
expect(Number((await deviceRow(failing.reserve))?.current_expires_at)).toBe(due)
// Never silent: a renewal left failing ages phones toward re-pairing.
expect(warnings('orca_relay_resume_renewal_failed')).toEqual([
{
event: 'orca_relay_resume_renewal_failed',
cause: 'error',
cellId: failing.reserve.config.cellId
}
])
})
it('replays a stored answer after its splice closed, and drops the basis itself', async () => {
const { reserve, phone, confirm } = await splicedPhone('memory')
const { phone, confirm } = await splicedPhone('memory')
const answer = await confirm('confirm-1')
phone.socket.close()
await phone.closed
await new Promise((resolve) => setTimeout(resolve, 50))
reserve.database.statements.length = 0
expect(await confirm('confirm-1')).toEqual(answer)
expect(await confirm('confirm-1', 'another-basis')).toMatchObject({
code: 'confirmation_tuple_mismatch'
})
expect(reserve.phoneStatements()).toEqual([])
// A new request on the closed basis finds none in memory, so it goes to the database.
expect(await confirm('confirm-2')).toMatchObject({ code: 'confirmation_not_active' })
expect(reserve.phoneStatements()[0]).toBe('BEGIN')
})
it('answers a grace-version confirm renewed:false with its expiry unchanged, as today', async () => {
const { reserve, confirm } = await splicedPhone('memory')
const stored = Number((await deviceRow(reserve))?.current_expires_at)
// An install rotated the credential after the accept read, so the accepted version is grace.
await reserve.raw.query(
`UPDATE relay_devices SET grace_hash = current_hash, grace_version = 1, grace_expires_at = ?,
current_hash = 'rotated', current_version = 2`,
[Date.now() + 60_000]
)
expect(await confirm('confirm-1')).toMatchObject({
type: 'device-resume-confirmed',
currentVersion: 2,
acceptedAs: 'current',
renewed: false,
resumeExpiresAt: stored
})
expect(reserve.phoneStatements()).toEqual([RESUME_DEVICE_READ])
})
it('answers every device change since the accept read as the database confirm does', async () => {
const changes: Record<string, [string, unknown[]]> = {
revoked: ['UPDATE relay_devices SET revoked_at = ?', [Date.now() - 1]],
expired: ['UPDATE relay_devices SET current_expires_at = ?', [Date.now() - 1]],
retired: [
"UPDATE relay_devices SET current_hash = 'rotated-twice', current_version = 3, grace_version = 2",
[]
],
unchanged: ['UPDATE relay_devices SET updated_at = updated_at', []]
}
const outcome = async (phonePath: 'db' | 'memory', [sql, params]: [string, unknown[]]) => {
const { reserve, confirm } = await splicedPhone(phonePath)
await reserve.raw.query(sql, params)
const answer = await confirm('confirm-1')
return answer.type === 'control-error'
? answer.code
: `${answer.type}:${answer.acceptedAs}:${answer.renewed}`
}
for (const [name, change] of Object.entries(changes)) {
expect(await outcome('memory', change), name).toBe(await outcome('db', change))
}
expect(await outcome('memory', changes.revoked!)).toBe('reject-revoked')
})
it('durably confirms and renews a database-path resume, releasing only its confirmation lease', async () => {
@@ -411,82 +246,6 @@ describe('the memory phone path', () => {
expect(await confirm('confirm-1')).toEqual(answer)
})
describe('rolling a memory cell back', () => {
// Flip back by the switch, or by the dead-man; either way the confirm finishes from memory.
type Cell = Awaited<ReturnType<typeof cell>>
type Advance = (ms: number) => void
type Rollback = {
beforeDial?: (advance: Advance) => void
roll: (reserve: Cell, advance: Advance) => void
}
// The dead-man trips just after the phone's accept: its window ran from the host's booking.
const window = reserveDeadManWindowMs(RESERVE_MODE_CELL_ID)
const rollbacks: Record<string, Rollback> = {
'admitMode=db': { roll: (reserve) => reserve.setAdmitMode('db', { phonePath: 'memory' }) },
'phonePath=db': { roll: (reserve) => reserve.setAdmitMode('reserve', { phonePath: 'db' }) },
'a dead-man trip': {
beforeDial: (advance) => advance(window - 5_000),
roll: (_reserve, advance) => advance(6_000)
}
}
for (const [name, rollback] of Object.entries(rollbacks)) {
it(`finishes an in-flight confirm on its memory basis after ${name}, writing and leaking no lease`, async () => {
let now = Date.now()
const advance: Advance = (ms) => {
now += ms
}
const spliced = await splicedPhone(
'memory',
{},
{
now: () => now,
beforeDial: () => rollback.beforeDial?.(advance)
}
)
const { reserve, seated } = spliced
// The flag tool records reserve in Postgres before the cell admits from memory.
await reserve.relay.assignments.reconcileCells([...reserve.config.cells, OTHER_CELL])
await reserve.relay.assignments.setCellAdmitMode(reserve.config.cellId, 'reserve')
rollback.roll(reserve, advance)
expect(reserve.relay.sessions.phonePathEffective()).toBe('db')
reserve.database.statements.length = 0
expect(await spliced.confirm('confirm-1')).toMatchObject({
type: 'device-resume-confirmed',
renewed: true
})
expect(
reserve.phoneStatements().filter((sql) => !sql.includes('relay_assignment'))
).toEqual([
RESUME_DEVICE_READ,
expect.stringMatching(/^UPDATE relay_devices SET current_expires_at/)
])
// Only the control lease a flip back re-registers; nothing for the phone.
const leases = async () =>
(await reserve.activityLeases()).map((lease) => lease.activity_kind)
expect((await leases()).filter((kind) => kind !== 'control')).toEqual([])
// Postgres still records reserve, so evacuation leaves the cell alone.
await expect(
reserve.relay.assignments.startEvacuation(
{ userId: 'user-1', relayHostId: seated.identity.hostId },
OTHER_CELL.id
)
).rejects.toThrow('cell_in_reserve_mode')
// The splice still runs, and closing it releases nothing it never took.
expect(spliced.phone.socket.readyState).toBe(spliced.phone.socket.OPEN)
// Let a flip back finish leasing its control (and fold its relay_cells write) first.
await until(async () => reserve.relay.sessions.reregistrationPending() === 0)
await new Promise((resolve) => setTimeout(resolve, 250))
reserve.database.statements.length = 0
spliced.phone.socket.close()
await spliced.phone.closed
await new Promise((resolve) => setTimeout(resolve, 50))
expect(reserve.phoneStatements()).toEqual([])
expect((await leases()).filter((kind) => kind !== 'control')).toEqual([])
})
}
})
// E-dep-6 in miniature: the staging drill blocks one memory cell's database for 10 minutes.
it('rides out a database outage: splices keep forwarding, new work fails cleanly and recovers', async () => {
let now = Date.now()
@@ -537,13 +296,9 @@ describe('the memory phone path', () => {
// Flipped back during the block: the in-flight confirm still finishes on its memory basis.
reserve.setAdmitMode('db', { phonePath: 'memory' })
reserve.database.failing = null
reserve.database.statements.length = 0
expect(await confirm('confirm-c', inFlight.basisConnId)).toMatchObject({
type: 'device-resume-confirmed'
})
expect(reserve.phoneStatements().find((sql) => !sql.includes('relay_assignment'))).toEqual(
RESUME_DEVICE_READ
)
// And new resumes take the database path again.
const connOpen = nextFrame(seated.socket, 'conn-open')
await reserve.dialPhone(seated.identity.hostId, token(1))
@@ -1,37 +0,0 @@
import { ASSIGNMENT_LIMITS } from '@orca-cloud/relay-contract/assignment-invariants'
import { RELAY_HOST_CLOSE_REASON } from '@orca-cloud/relay-contract/host-close-reason'
import { describe, expect, it } from 'vitest'
import { HostCloseReasonMemory } from './host-close-reason-memory.js'
function memoryAt(clock: { now: number }): HostCloseReasonMemory {
return new HostCloseReasonMemory(() => clock.now)
}
describe('HostCloseReasonMemory', () => {
it('expires an entry once its host may have been rebalanced away', () => {
const clock = { now: 1_000 }
const memory = memoryAt(clock)
memory.record('a', RELAY_HOST_CLOSE_REASON.SIGNED_OUT)
clock.now += ASSIGNMENT_LIMITS.dormantTtlMs - 1
expect(memory.read('a')).toBe(RELAY_HOST_CLOSE_REASON.SIGNED_OUT)
clock.now += 1
expect(memory.read('a')).toBeNull()
expect(memory.size()).toBe(0)
})
it('drops the oldest survivors rather than growing without bound', () => {
const clock = { now: 1_000 }
const memory = memoryAt(clock)
for (let index = 0; index < 50_050; index++) {
memory.record(`host-${index}`, RELAY_HOST_CLOSE_REASON.SIGNED_OUT)
}
expect(memory.size()).toBe(50_000)
expect(memory.read('host-0')).toBeNull()
expect(memory.read('host-50049')).toBe(RELAY_HOST_CLOSE_REASON.SIGNED_OUT)
})
})
@@ -1,4 +1,3 @@
import nacl from 'tweetnacl'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
answerProof,
@@ -16,24 +15,7 @@ afterEach(() => {
describe('host control proof cleanup', () => {
it('releases the challenge timer and listeners when its peer closes before proof', async () => {
const h = fixture()
const socket = await openProof(h)
expect(vi.getTimerCount()).toBe(1)
expect(socket.listenerCount('message')).toBe(1)
socket.peerClose()
expect({
timers: vi.getTimerCount(),
message: socket.listenerCount('message'),
close: socket.listenerCount('close')
}).toEqual({ timers: 0, message: 0, close: 0 })
await vi.advanceTimersByTimeAsync(10_000)
expect(socket.close).not.toHaveBeenCalled()
expect(h.activate).not.toHaveBeenCalled()
expect(h.database.query).not.toHaveBeenCalled()
})
it('does no challenge crypto, send, timer or registration after a closed peer finishes verification', async () => {
it('does not challenge or register a closed peer after verification finishes', async () => {
const h = fixture()
let finish!: (valid: boolean) => void
h.verify.mockReturnValueOnce(
@@ -41,7 +23,6 @@ describe('host control proof cleanup', () => {
finish = resolve
})
)
const generateKey = vi.spyOn(nacl.box, 'keyPair')
const socket = new ProofSocket()
h.registry.acceptControl(socket.registrySocket(), h.identity)
socket.emit('message', Buffer.from(h.hello), false)
@@ -50,11 +31,6 @@ describe('host control proof cleanup', () => {
finish(true)
await vi.advanceTimersByTimeAsync(0)
expect(socket.send).not.toHaveBeenCalled()
expect(generateKey).not.toHaveBeenCalled()
expect(vi.getTimerCount()).toBe(0)
expect(socket.listenerCount('message')).toBe(0)
expect(h.activate).not.toHaveBeenCalled()
expect(h.database.query).not.toHaveBeenCalled()
expect(
h.registry.get({ userId: h.identity.sub, relayHostId: h.identity.relayHostId })
).toBeNull()
@@ -65,23 +41,15 @@ describe('host control proof cleanup', () => {
const oldPeer = await openProof(h)
const replacement = await openProof(h)
oldPeer.peerClose()
expect(vi.getTimerCount()).toBe(1)
answerProof(replacement, h.keyPair)
await vi.advanceTimersByTimeAsync(0)
expect(h.activate).toHaveBeenCalledOnce()
expect(h.recordAuth).toHaveBeenCalledExactlyOnceWith(true)
expect(
h.registry.get({ userId: h.identity.sub, relayHostId: h.identity.relayHostId })?.socket
).toBe(replacement)
expect(replacement.send).toHaveBeenCalledTimes(2)
expect(replacement.listenerCount('message')).toBe(1)
expect(replacement.listenerCount('close')).toBe(2)
expect(vi.getTimerCount()).toBe(1)
await vi.advanceTimersByTimeAsync(10_000)
expect(oldPeer.close).not.toHaveBeenCalled()
expect(replacement.close).not.toHaveBeenCalled()
h.registry.drain(0)
await vi.advanceTimersByTimeAsync(0)
expect(vi.getTimerCount()).toBe(0)
})
})
@@ -1,4 +1,3 @@
import { CONTROL_CONTINUITY_LIMITS } from '@orca-cloud/relay-contract/control-continuity'
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RelayTokenClaims } from '../authentication/relay-token-verifier.js'
@@ -46,7 +45,6 @@ async function lapsedHost() {
)
const session = h.registry.get({ userId: h.identity.sub, relayHostId: h.identity.relayHostId })
expect(session?.state).toBe('orphaned')
expect(CONTROL_CONTINUITY_LIMITS.orphanGraceMs).toBeGreaterThan(5_000)
await vi.advanceTimersByTimeAsync(5_000)
return { h, ack, session: session!, resumeHello: resumeHelloFor(h.hello, ack) }
}
@@ -229,7 +229,6 @@ describe('client accept abandoned mid-DB-phase', () => {
capacity
)
await vi.advanceTimersByTimeAsync(0)
expect(h.acquireActivity).toHaveBeenCalledOnce()
// The phone's 12s bound fires while the cell still waits on Postgres.
client.close(1000, 'client bound')
capacity.release()
@@ -247,54 +246,12 @@ describe('client accept abandoned mid-DB-phase', () => {
{ userId: identity.sub, relayHostId: identity.relayHostId },
expect.stringMatching(/^confirmation:/)
)
expect(h.observer.recordClientAcceptAbandoned).toHaveBeenCalledWith(
'activity',
expect.any(Number)
)
const line = warn.mock.calls
.map((call) => String(call[0]))
.find((entry) => entry.includes('orca_relay_client_accept_abandoned'))
expect(line).toBeDefined()
expect(JSON.parse(line!)).toMatchObject({ stage: 'activity' })
expect(line).not.toContain(identity.relayHostId)
} finally {
warn.mockRestore()
h.registry.drain(0)
vi.advanceTimersByTime(0)
}
})
it('stops after a slow credential reservation without acquiring an activity lease', async () => {
const h = harness()
await activeHost(h)
const slowReserve = deferred<CredentialReservation>()
h.store.reserveCredential.mockReturnValueOnce(slowReserve.promise)
const client = new FakeSocket()
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
try {
const accepting = h.registry.acceptClient(
client as unknown as WebSocket,
identity.relayHostId,
'credential'
)
await vi.advanceTimersByTimeAsync(0)
client.close(1000, 'client bound')
slowReserve.resolve(reservation)
await accepting
expect(h.acquireActivity).not.toHaveBeenCalled()
expect(h.store.failReservation).toHaveBeenCalledWith(reservation)
expect(h.observer.recordClientAcceptAbandoned).toHaveBeenCalledWith(
'credential',
expect.any(Number)
)
} finally {
warn.mockRestore()
h.registry.drain(0)
vi.advanceTimersByTime(0)
}
})
})
// The attach resolves its owning session once and hands it to the unfenced leg;
@@ -360,7 +317,6 @@ describe('host data attach ownership', () => {
1
)
await vi.advanceTimersByTimeAsync(0)
expect(h.store.recordConnectionBasis).toHaveBeenCalledOnce()
expect(client.send).not.toHaveBeenCalledWith(expect.stringContaining('relay-hello'))
basis.resolve()
expect(await attaching).toBe(true)
@@ -420,17 +376,4 @@ describe('paced drain and the phones of a host not yet told', () => {
expect(served.close).not.toHaveBeenCalled()
expect(untold.send).toHaveBeenCalledWith(expect.stringContaining('conn-open'))
})
it("refuses that host's phones as soon as its own drain is sent", async () => {
const { h, untold } = await twoHostCell()
h.registry.drain(0, { paceWindowMs: 40_000 })
await vi.advanceTimersByTimeAsync(40_000)
// Told by its zero-grace close, a timer nested 1 ms after the send.
await vi.advanceTimersByTimeAsync(1)
expect(untold.send).toHaveBeenCalledWith(expect.stringContaining('"type":"drain"'))
const refused = await dial(h, laterHostId)
expect(refused.close).toHaveBeenCalledWith(RELAY_CLOSE_CODE.DRAINING, expect.any(String))
})
})
@@ -1,6 +1,5 @@
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { DRAIN_RELEASE_BEFORE_CLOSE_MS } from './host-session-registry.js'
import {
answerProof,
hostControlProofFixture,
@@ -70,48 +69,4 @@ describe('a zero-grace drain releases the host before telling it', () => {
socket.close.mock.invocationCallOrder[0]!
)
})
it('closes on the bound when the release has not landed, as before this ordering', async () => {
const { h, socket } = await connectedHost()
vi.spyOn(h.assignments, 'releaseActivity').mockReturnValue(pendingRelease().promise)
h.registry.drain(0)
await vi.advanceTimersByTimeAsync(DRAIN_RELEASE_BEFORE_CLOSE_MS - 1)
expect(socket.close).not.toHaveBeenCalled()
await vi.advanceTimersByTimeAsync(2)
expect(frames(socket)).toEqual([{ type: 'drain', graceMs: 0, recovery: 'resolve-director' }])
expect(socket.close).toHaveBeenCalledWith(
RELAY_CLOSE_CODE.DRAINING,
'resolve configured director'
)
const slow = vi
.mocked(console.warn)
.mock.calls.map(([line]) => String(line))
.filter((line) => line.includes('"orca_relay_host_drain_release_slow"'))
expect(slow).toHaveLength(1)
expect(slow[0]).not.toContain(h.identity.relayHostId)
})
it('still tells a host with grace at once, and releases it before the closing deadline', async () => {
const { h, socket } = await connectedHost()
const release = pendingRelease()
vi.spyOn(h.assignments, 'releaseActivity').mockReturnValue(release.promise)
h.registry.drain(10_000)
expect(frames(socket)).toEqual([
{ type: 'drain', graceMs: 10_000, recovery: 'resolve-director' }
])
await vi.advanceTimersByTimeAsync(10_000)
expect(socket.close).not.toHaveBeenCalled()
release.settle('released')
await vi.advanceTimersByTimeAsync(0)
expect(socket.close).toHaveBeenCalledWith(
RELAY_CLOSE_CODE.DRAINING,
'resolve configured director'
)
// Told once, by the drain itself; the close adds no second frame.
expect(frames(socket)).toHaveLength(1)
})
})
@@ -1,534 +0,0 @@
import { EventEmitter } from 'node:events'
import { CONTROL_CONTINUITY_LIMITS } from '@orca-cloud/relay-contract/control-continuity'
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { RELAY_HOST_CAPABILITY_IDLE_REGIONAL_REHOME } from '@orca-cloud/relay-contract/control-messages'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type WebSocket from 'ws'
import type { RelayAssignmentStore } from '../assignment/assignment-store.js'
import type { RelayConfig } from '../config.js'
import type { RelayCredentialStore } from '../credentials/credential-store.js'
import { CONTROL_RENEWAL_BATCH_INTERVAL_MS } from '../control-leases/control-renewal-batch.js'
import type {
ControlRenewalOutcome
} from '../control-leases/control-renewal-statement.js'
import {
HostSessionRegistry,
IDLE_REHOME_MIN_CONTROL_AGE_MS,
type HostSession
} from './host-session-registry.js'
import type { RelayRuntimeObserver } from '../observability/relay-observability.js'
import type { RelayTokenClaims } from '../authentication/relay-token-verifier.js'
import { ProcessQueuedByteBudget } from './splice-forwarder.js'
// A due renewal leaves the heartbeat as a batch enqueue, so the store only sees
// the tick once the batch window closes.
async function closeRenewalWindow(): Promise<void> {
await vi.advanceTimersByTimeAsync(CONTROL_RENEWAL_BATCH_INTERVAL_MS)
}
class FakeSocket extends EventEmitter {
readonly OPEN = 1
readonly CLOSING = 2
readonly CLOSED = 3
readyState = this.OPEN
readonly send = vi.fn()
readonly close = vi.fn((code?: number, reason?: string) => {
this.readyState = this.CLOSED
this.emit('close', code, Buffer.from(reason ?? ''))
})
readonly terminate = vi.fn(() => {
this.readyState = this.CLOSED
this.emit('close')
})
}
type ActivateSession = (
socket: WebSocket,
identity: RelayTokenClaims,
existing: HostSession | null,
generation: number,
rebind: boolean,
assignmentEpoch: number,
appVersion?: string
) => Promise<void>
const config = {
port: 8080,
publicUrl: 'https://relay-c3.example.com',
cellUrl: 'https://relay-c3.example.com',
authIssuer: 'https://auth.example.com',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.com/jwks',
assignmentSigningKey: new Uint8Array(32),
role: 'cell',
cellId: 'production-gce-c3',
cells: [
{
id: 'production-gce-c3',
url: 'https://relay-c3.example.com',
capacityRequests: 4_000
}
],
adminAudience: 'https://relay-c3.example.com/v1/admin/drain',
deployServiceAccount: 'deploy@example.com',
runtimeServiceAccount: 'runtime@example.com',
adminJwksUrl: 'https://auth.example.com/admin-jwks',
databasePoolMax: 10,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './test-data'
} satisfies RelayConfig
const identity = {
sub: 'user-1',
prof: 'profile-1',
relayHostId: 'abcdefghijklmnop',
purpose: 'host-control',
exp: 4_102_444_800
} satisfies RelayTokenClaims
function deferred<T>(): {
promise: Promise<T>
resolve(value: T): void
} {
let resolve!: (value: T) => void
const promise = new Promise<T>((complete) => {
resolve = complete
})
return { promise, resolve }
}
function createRegistry(
activateControl: RelayAssignmentStore['activateControl'],
store: Partial<RelayCredentialStore> = {},
verifyRelayToken: (token: string) => Promise<RelayTokenClaims | null> = vi.fn()
): {
registry: HostSessionRegistry
activate: ActivateSession
acquireActivity: ReturnType<typeof vi.fn>
renewControlActivities: ReturnType<typeof vi.fn<RelayAssignmentStore['renewControlActivities']>>
releaseActivity: ReturnType<typeof vi.fn>
observer: {
recordAuth: ReturnType<typeof vi.fn>
recordControlClose: ReturnType<typeof vi.fn>
recordPostProofClose: ReturnType<typeof vi.fn>
recordSpliceClose: ReturnType<typeof vi.fn>
}
} {
const acquireActivity = vi.fn().mockResolvedValue(undefined)
const releaseActivity = vi.fn().mockResolvedValue(true)
const renewControlActivities = vi
.fn<RelayAssignmentStore['renewControlActivities']>()
.mockImplementation(async (rows) => rows.map(() => 'renewed'))
const assignments = {
activateControl,
markMigrationTargetRegistered: vi.fn().mockResolvedValue(undefined),
resolve: vi.fn().mockResolvedValue({ cellId: config.cellId }),
acquireActivity,
renewControlActivities,
releaseActivity
} as unknown as RelayAssignmentStore
const observer = {
recordAuth: vi.fn(),
recordForwardedBytes: vi.fn(),
recordHttp: vi.fn(),
recordReconnect: vi.fn(),
recordSql: vi.fn(),
recordControlClose: vi.fn(),
recordPostProofClose: vi.fn(),
recordSpliceClose: vi.fn()
} satisfies RelayRuntimeObserver
const registry = new HostSessionRegistry(
config,
verifyRelayToken,
store as RelayCredentialStore,
assignments,
new ProcessQueuedByteBudget(),
observer,
Date.now,
Math.random,
'incarnation-1'
)
// Mirrors the production signature exactly so a future positional shift fails to compile.
const bound = (
registry as unknown as {
activate: (
socket: WebSocket,
identity: RelayTokenClaims,
existing: HostSession | null,
generation: number,
rebind: boolean,
assignmentEpoch: number,
appVersion: string,
connectionInclusionWatermark?: number
) => Promise<void>
}
).activate.bind(registry)
const activate: ActivateSession = (
socket,
identity,
existing,
generation,
rebind,
assignmentEpoch,
appVersion = '1.4.173'
) => bound(socket, identity, existing, generation, rebind, assignmentEpoch, appVersion)
return {
registry,
activate,
acquireActivity,
renewControlActivities,
releaseActivity,
observer
}
}
describe('host session cleanup races', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
vi.clearAllTimers()
vi.useRealTimers()
})
// c34 ghost seat came from activation outliving orphan grace and reviving a removed session outside the map.
it('refuses a rebind whose session left the map while its activation was in flight', async () => {
const blocked = deferred<string>()
const activateControl = vi
.fn<RelayAssignmentStore['activateControl']>()
.mockResolvedValueOnce('control:production-gce-c3:1')
.mockReturnValueOnce(blocked.promise)
const { registry, activate, releaseActivity } = createRegistry(activateControl)
const key = { userId: identity.sub, relayHostId: identity.relayHostId }
const originalSocket = new FakeSocket()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: FakeSocket implements the registry's WebSocket event and lifecycle surface.
await activate(originalSocket as unknown as WebSocket, identity, null, 1, false, 1)
const original = registry.get(key)
originalSocket.close(1006, 'network reset')
expect(original?.state).toBe('orphaned')
const rebindSocket = new FakeSocket()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: FakeSocket implements the registry's WebSocket event and lifecycle surface.
const rebinding = activate(rebindSocket as unknown as WebSocket, identity, original, 1, true, 1)
await vi.advanceTimersByTimeAsync(0)
expect(activateControl).toHaveBeenCalledTimes(2)
// The grace runs out while the database answers slowly.
await vi.advanceTimersByTimeAsync(CONTROL_CONTINUITY_LIMITS.orphanGraceMs)
expect(registry.get(key)).toBeNull()
blocked.resolve('control:production-gce-c3:1')
await rebinding
expect(rebindSocket.close).toHaveBeenCalledWith(
RELAY_CLOSE_CODE.PEER_DROPPED,
'control activation superseded'
)
expect(registry.get(key)).toBeNull()
expect(original?.state).toBe('closed')
const page = registry.seatFeed(null)
expect('full' in page && page.full).toEqual([])
expect(releaseActivity).toHaveBeenCalledWith(key, 'control:production-gce-c3:1')
})
it('skips a closed queued control so its live retry avoids abandoned database work', async () => {
const stalled = deferred<string>()
const activateControl = vi
.fn<RelayAssignmentStore['activateControl']>()
.mockReturnValueOnce(stalled.promise)
.mockImplementation(async () => {
await new Promise<void>((resolve) => setTimeout(resolve, 4_000))
return 'control:production-gce-c3:1'
})
const { registry, activate, releaseActivity } = createRegistry(activateControl)
const firstSocket = new FakeSocket()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: FakeSocket implements the registry's WebSocket event and lifecycle surface.
const first = activate(firstSocket as unknown as WebSocket, identity, null, 1, false, 1)
await vi.advanceTimersByTimeAsync(0)
expect(activateControl).toHaveBeenCalledOnce()
const abandonedSocket = new FakeSocket()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: FakeSocket implements the registry's WebSocket event and lifecycle surface.
const abandoned = activate(abandonedSocket as unknown as WebSocket, identity, null, 1, false, 1)
const liveSocket = new FakeSocket()
const startedAt = Date.now()
let liveCompletedAt: number | undefined
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: FakeSocket implements the registry's WebSocket event and lifecycle surface.
const live = activate(liveSocket as unknown as WebSocket, identity, null, 1, false, 1).then(
() => {
liveCompletedAt = Date.now()
}
)
firstSocket.close()
abandonedSocket.close()
stalled.resolve('control:production-gce-c3:1')
await vi.advanceTimersByTimeAsync(8_000)
await Promise.all([first, abandoned, live])
console.log(
JSON.stringify({
scenario: 'closed queued control before a live retry',
activationCalls: activateControl.mock.calls.length,
activityReleases: releaseActivity.mock.calls.length,
liveReadyMs: liveCompletedAt === undefined ? null : liveCompletedAt - startedAt
})
)
expect(activateControl).toHaveBeenCalledTimes(2)
expect(releaseActivity).toHaveBeenCalledOnce()
expect(liveCompletedAt! - startedAt).toBe(4_000)
expect(registry.get({ userId: identity.sub, relayHostId: identity.relayHostId })?.socket).toBe(
liveSocket
)
})
it('ignores a denial belonging to the socket before a same-generation rebind', async () => {
const h = createRegistry(vi.fn().mockResolvedValue('control:production-gce-c3:1'))
const oldSocket = new FakeSocket()
await h.activate(oldSocket as unknown as WebSocket, identity, null, 1, false, 1)
const session = h.registry.get({ userId: identity.sub, relayHostId: identity.relayHostId })!
let finish!: (outcomes: ControlRenewalOutcome[]) => void
h.renewControlActivities.mockReturnValueOnce(
new Promise<ControlRenewalOutcome[]>((resolve) => {
finish = resolve
})
)
await vi.advanceTimersByTimeAsync(15_000)
await closeRenewalWindow()
const replacement = new FakeSocket()
await h.activate(replacement as unknown as WebSocket, identity, session, 1, true, 1)
finish(['activity_cell_not_authoritative'])
await vi.advanceTimersByTimeAsync(0)
expect(replacement.close).not.toHaveBeenCalled()
expect(session.socket).toBe(replacement)
expect(session.generation).toBe(1)
})
})
describe('source-owned idle cutover', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
vi.clearAllTimers()
vi.useRealTimers()
})
const request = {
attemptId: 'idle-1',
userId: identity.sub,
relayHostId: identity.relayHostId,
sourceAssignmentEpoch: 1,
sourceGeneration: 1,
sourceCellIncarnation: 'incarnation-1',
targetCellId: 'target'
}
async function source(
store: Partial<RelayCredentialStore> = {},
controlAgeMs = IDLE_REHOME_MIN_CONTROL_AGE_MS
) {
const h = createRegistry(vi.fn().mockResolvedValue('control:1'), store)
const socket = new FakeSocket()
h.registry.acceptControl(
socket as unknown as WebSocket,
identity,
undefined,
new Set([RELAY_HOST_CAPABILITY_IDLE_REGIONAL_REHOME])
)
socket.removeAllListeners('message')
await h.activate(socket as unknown as WebSocket, identity, null, 1, false, 1)
// Moves the clock without firing the heartbeat timers that would close the session.
vi.setSystemTime(Date.now() + controlAgeMs)
return { ...h, socket, session: h.registry.get(request)! }
}
it('rejects new accepts and replacements synchronously while a commit awaits', async () => {
const h = await source()
const result = deferred<{ outcome: 'deferred' }>()
const commit = vi.fn().mockReturnValue(result.promise)
const moving = h.registry.idleRehome(
request,
commit,
vi.fn().mockResolvedValue('not-committed')
)
const duplicate = h.registry.idleRehome(
request,
commit,
vi.fn().mockResolvedValue('not-committed')
)
const client = new FakeSocket()
const release = vi.fn()
await h.registry.acceptClient(
client as unknown as WebSocket,
identity.relayHostId,
'credential',
{ release } as never
)
expect(client.close).toHaveBeenCalledWith(RELAY_CLOSE_CODE.WRONG_CELL, expect.any(String))
expect(release).toHaveBeenCalledOnce()
const replacement = new FakeSocket()
await h.activate(replacement as unknown as WebSocket, identity, h.session, 2, false, 1)
expect(replacement.close).toHaveBeenCalledWith(RELAY_CLOSE_CODE.WRONG_CELL, expect.any(String))
result.resolve({ outcome: 'deferred' })
await moving
await duplicate
expect(commit).toHaveBeenCalledOnce()
expect(h.socket.close).not.toHaveBeenCalled()
expect(
await h.registry.idleRehome(
{ ...request, attemptId: 'next' },
vi.fn().mockResolvedValue({ outcome: 'committed' }),
vi.fn().mockResolvedValue('not-committed')
)
).toEqual({ outcome: 'committed' })
})
it.each(['ambiguous', 'deferred'])(
'keeps %s outcomes fenced until locked reconciliation succeeds',
async (claim) => {
const h = await source()
const reconcile = vi
.fn()
.mockRejectedValueOnce(new Error('database unavailable'))
.mockRejectedValueOnce(new Error('database unavailable'))
.mockResolvedValue('not-committed')
const moving = h.registry.idleRehome(
request,
claim === 'ambiguous'
? vi.fn().mockRejectedValue(new Error('lost commit reply'))
: vi.fn().mockResolvedValue({ outcome: 'deferred' }),
reconcile
)
await vi.advanceTimersByTimeAsync(50)
expect(
await h.registry.idleRehome(
{ ...request, attemptId: 'other' },
vi.fn(),
vi.fn().mockResolvedValue('not-committed')
)
).toEqual({ outcome: 'busy' })
expect(h.socket.close).not.toHaveBeenCalled()
await vi.advanceTimersByTimeAsync(300)
expect(await moving).toEqual({ outcome: 'deferred' })
expect(reconcile).toHaveBeenCalledTimes(3)
expect(h.socket.close).not.toHaveBeenCalled()
}
)
it('holds attach ownership through basis failure reservation cleanup', async () => {
const basis = deferred<void>()
const cleanup = deferred<void>()
const h = await source({
recordConnectionBasis: vi.fn().mockImplementation(async () => {
await basis.promise
throw new Error('basis failed')
}),
failReservation: vi.fn().mockReturnValue(cleanup.promise)
})
const client = new FakeSocket()
h.session.pendingConns.set('conn', {
connId: 'conn',
connTicket: 'ticket',
client: client as unknown as WebSocket,
reservation: {
userId: identity.sub,
relayHostId: identity.relayHostId,
credentialKind: 'invite',
relayDeviceId: 'device-1',
tokenHash: 'invite-hash',
reservationId: 'reservation-1',
leaseExpiresAt: Date.now() + 1000
},
attachTimer: setTimeout(() => {}, 1000),
credentialActivityId: null,
phonePath: 'db',
timing: {
startedAt: Date.now(),
connOpenAt: Date.now(),
stageMs: { assignment: 0, credential: 0, activity: 0 }
}
})
const attached = h.registry.acceptHostData(
new FakeSocket() as unknown as WebSocket,
'conn',
'ticket',
1
)
const commit = vi.fn().mockResolvedValue({ outcome: 'deferred' })
expect(await h.registry.idleRehome(request, commit, vi.fn())).toEqual({ outcome: 'busy' })
basis.resolve()
await vi.advanceTimersByTimeAsync(0)
expect(h.session.activeConnIds.size).toBe(0)
expect(await h.registry.idleRehome(request, commit, vi.fn())).toEqual({ outcome: 'busy' })
expect(commit).not.toHaveBeenCalled()
cleanup.resolve()
await attached
})
})
describe('paced drain', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
vi.clearAllTimers()
vi.useRealTimers()
})
async function connectHosts(count: number): Promise<{
registry: HostSessionRegistry
sockets: FakeSocket[]
}> {
const activateControl = vi
.fn<RelayAssignmentStore['activateControl']>()
.mockResolvedValue('control:production-gce-c3:1')
const { registry, activate } = createRegistry(activateControl)
const sockets: FakeSocket[] = []
for (let index = 0; index < count; index += 1) {
const socket = new FakeSocket()
sockets.push(socket)
await activate(
socket as unknown as WebSocket,
{ ...identity, sub: `user-${index}` },
null,
1,
false,
1
)
socket.send.mockClear()
}
return { registry, sockets }
}
function drainsSent(sockets: FakeSocket[]): number {
return sockets.filter((socket) =>
socket.send.mock.calls.some(([payload]) => String(payload).includes('"type":"drain"'))
).length
}
// Windows here stay under the 75s control-silence watchdog, which would otherwise close
// a test socket that never heartbeats before its paced send is due.
it('gives each host its own grace after its own send, not after the call', async () => {
const { registry, sockets } = await connectHosts(2)
registry.drain(10_000, { paceWindowMs: 40_000 })
await vi.advanceTimersByTimeAsync(10_000)
expect(sockets[0]!.readyState).toBe(sockets[0]!.CLOSED)
expect(sockets[1]!.readyState).toBe(sockets[1]!.OPEN)
// Its own send at 40s plus its own 10s grace, not 10s from the drain call.
await vi.advanceTimersByTimeAsync(39_999)
expect(sockets[1]!.readyState).toBe(sockets[1]!.OPEN)
await vi.advanceTimersByTimeAsync(1)
expect(sockets[1]!.readyState).toBe(sockets[1]!.CLOSED)
})
it('lets an emergency drain supersede the sends still queued by a paced one', async () => {
const { registry, sockets } = await connectHosts(4)
registry.drain(0, { paceWindowMs: 40_000 })
await vi.advanceTimersByTimeAsync(0)
expect(drainsSent(sockets)).toBe(1)
registry.drain(0)
await vi.advanceTimersByTimeAsync(0)
expect(drainsSent(sockets)).toBe(4)
const sendsAfterEmergency = sockets.map((socket) => socket.send.mock.calls.length)
await vi.advanceTimersByTimeAsync(40_000)
expect(sockets.map((socket) => socket.send.mock.calls.length)).toEqual(sendsAfterEmergency)
})
})
@@ -1,178 +0,0 @@
import { EventEmitter } from 'node:events'
import { CONTROL_CONTINUITY_LIMITS } from '@orca-cloud/relay-contract/control-continuity'
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { RELAY_HOST_CLOSE_REASON } from '@orca-cloud/relay-contract/host-close-reason'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type WebSocket from 'ws'
import type { RelayAssignmentStore } from '../assignment/assignment-store.js'
import type { RelayConfig } from '../config.js'
import type { RelayCredentialStore } from '../credentials/credential-store.js'
import { HostSessionRegistry } from './host-session-registry.js'
import type { RelayRuntimeObserver } from '../observability/relay-observability.js'
import type { RelayTokenClaims } from '../authentication/relay-token-verifier.js'
import { ProcessQueuedByteBudget } from './splice-forwarder.js'
class FakeSocket extends EventEmitter {
readonly OPEN = 1
readonly CLOSED = 3
readyState = this.OPEN
readonly send = vi.fn()
readonly close = vi.fn((code?: number, reason?: string) => {
this.readyState = this.CLOSED
this.emit('close', code, Buffer.from(reason ?? ''))
})
readonly terminate = vi.fn(() => {
this.readyState = this.CLOSED
this.emit('close', 1006, Buffer.alloc(0))
})
}
const config = {
port: 8080,
publicUrl: 'https://relay-c3.example.com',
cellUrl: 'https://relay-c3.example.com',
authIssuer: 'https://auth.example.com',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.com/jwks',
assignmentSigningKey: new Uint8Array(32),
role: 'cell',
cellId: 'production-gce-c3',
cells: []
} as unknown as RelayConfig
const identity = {
sub: 'user-1',
prof: 'profile-1',
org: 'org-1',
relayHostId: 'AbCdEf0123_-xyZ9'
} as unknown as RelayTokenClaims
const reservation = {
userId: identity.sub,
relayHostId: identity.relayHostId,
credentialKind: 'resume',
relayDeviceId: 'device-1',
leaseExpiresAt: Date.now() + 60_000
}
function createRegistry() {
const store = {
resolveResume: vi.fn().mockResolvedValue({ userId: identity.sub }),
reserveCredential: vi.fn().mockResolvedValue(reservation),
failReservation: vi.fn().mockResolvedValue(undefined)
}
const assignments = {
activateControl: vi.fn().mockResolvedValue('control:production-gce-c3:1'),
markMigrationTargetRegistered: vi.fn().mockResolvedValue(undefined),
resolve: vi.fn().mockResolvedValue({ cellId: config.cellId }),
acquireActivity: vi.fn().mockResolvedValue(undefined),
renewControlActivity: vi.fn().mockResolvedValue(undefined),
releaseActivity: vi.fn().mockResolvedValue(true)
} as unknown as RelayAssignmentStore
const observer = {
recordAuth: vi.fn(),
recordForwardedBytes: vi.fn(),
recordHttp: vi.fn(),
recordReconnect: vi.fn(),
recordSql: vi.fn(),
recordControlClose: vi.fn(),
recordSpliceClose: vi.fn()
} satisfies RelayRuntimeObserver
const registry = new HostSessionRegistry(
config,
vi.fn(),
store as unknown as RelayCredentialStore,
assignments,
new ProcessQueuedByteBudget(),
observer
)
const activate = (socket: WebSocket, generation: number): Promise<void> =>
(
registry as unknown as {
activate: (
socket: WebSocket,
identity: RelayTokenClaims,
existing: null,
generation: number,
rebind: boolean,
assignmentEpoch: number,
appVersion: string
) => Promise<void>
}
).activate(socket, identity, null, generation, false, 1, '1.4.173')
return { registry, activate }
}
async function dialPhone(registry: HostSessionRegistry): Promise<FakeSocket> {
const phone = new FakeSocket()
await registry.acceptClient(phone as unknown as WebSocket, identity.relayHostId, 'credential')
return phone
}
// The 4404 hello body is unchanged: every shipped phone parses it with a strict
// schema, so the cause has to ride the close frame instead.
const HOST_OFFLINE_HELLO = JSON.stringify({
type: 'relay-hello',
ok: false,
code: RELAY_CLOSE_CODE.HOST_OFFLINE
})
describe('host sign-out reason on phone rejection', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
vi.clearAllTimers()
vi.useRealTimers()
})
it('names the sign-out to a phone that arrives after the host is gone', async () => {
const { registry, activate } = createRegistry()
const control = new FakeSocket()
await activate(control as unknown as WebSocket, 1)
control.close(1000, RELAY_HOST_CLOSE_REASON.SIGNED_OUT)
vi.advanceTimersByTime(CONTROL_CONTINUITY_LIMITS.orphanGraceMs + 1)
const phone = await dialPhone(registry)
expect(phone.send).toHaveBeenCalledWith(HOST_OFFLINE_HELLO)
expect(phone.close).toHaveBeenCalledWith(
RELAY_CLOSE_CODE.HOST_OFFLINE,
RELAY_HOST_CLOSE_REASON.SIGNED_OUT
)
})
it('ignores a close reason the host invented', async () => {
const { registry, activate } = createRegistry()
const control = new FakeSocket()
await activate(control as unknown as WebSocket, 1)
control.close(1000, 'signed-out-ish')
vi.advanceTimersByTime(CONTROL_CONTINUITY_LIMITS.orphanGraceMs + 1)
const phone = await dialPhone(registry)
expect(phone.close).toHaveBeenCalledWith(
RELAY_CLOSE_CODE.HOST_OFFLINE,
'relay connection rejected'
)
})
it('forgets the sign-out once the host proves itself again', async () => {
const { registry, activate } = createRegistry()
const control = new FakeSocket()
await activate(control as unknown as WebSocket, 1)
control.close(1000, RELAY_HOST_CLOSE_REASON.SIGNED_OUT)
vi.advanceTimersByTime(CONTROL_CONTINUITY_LIMITS.orphanGraceMs + 1)
const reconnected = new FakeSocket()
await activate(reconnected as unknown as WebSocket, 2)
// Drop it abruptly, as a network death would, so only the stale memory
// could still name a cause.
reconnected.terminate()
vi.advanceTimersByTime(CONTROL_CONTINUITY_LIMITS.orphanGraceMs + 1)
const phone = await dialPhone(registry)
expect(phone.close).toHaveBeenCalledWith(
RELAY_CLOSE_CODE.HOST_OFFLINE,
'relay connection rejected'
)
})
})
@@ -122,15 +122,12 @@ describe('relay connection hard cap', () => {
it('preserves control headroom and never disturbs established sockets', async () => {
const keys = await generateKeyPair('ES256')
const publicJwk = await exportJWK(keys.publicKey)
const adminKeys = await generateKeyPair('RS256')
const adminPublicJwk = await exportJWK(adminKeys.publicKey)
const jwks = createServer((_request, response) => {
response.setHeader('content-type', 'application/json')
response.end(
JSON.stringify({
keys: [
{ ...publicJwk, kid: 'test-key', alg: 'ES256', use: 'sig' },
{ ...adminPublicJwk, kid: 'admin-key', alg: 'RS256', use: 'sig' }
{ ...publicJwk, kid: 'test-key', alg: 'ES256', use: 'sig' }
]
})
)
@@ -188,36 +185,6 @@ describe('relay connection hard cap', () => {
await new Promise<void>((resolve) => relay.server.once('listening', resolve))
cleanup.push(() => new Promise<void>((resolve) => relay.server.close(() => resolve())))
const wsUrl = relayUrl.replace('http:', 'ws:')
const adminToken = await new SignJWT({
email: 'deploy@example.com',
email_verified: true
})
.setProtectedHeader({ alg: 'RS256', kid: 'admin-key' })
.setIssuer('https://accounts.google.com')
.setAudience(`${relayUrl}/admin`)
.setSubject('deploy-subject')
.setIssuedAt()
.setExpirationTime('5m')
.sign(adminKeys.privateKey)
const statusResponse = await fetch(`${relayUrl}/v1/admin/runtime-status`, {
method: 'POST',
headers: {
authorization: `Bearer ${adminToken}`,
'content-type': 'application/json'
},
body: JSON.stringify({ v: 1 })
})
expect(statusResponse.status).toBe(200)
expect(await statusResponse.json()).toMatchObject({
connectionCapacity: {
hardCap: 600,
controlRebindReserve: 100,
ordinaryConnectionLimit: 500,
unobservedBound: 60,
normalAdmissionPause: 440
}
})
const unmatchedData = await Promise.all(
['unmatched-1', 'unmatched-2', 'unmatched-3'].map((connectionId) =>
openSocket(`${wsUrl}/v1/host/data/${connectionId}`)
@@ -13,38 +13,6 @@ function socket(): { webSocket: WebSocket; close: () => void } {
describe('RelayConnectionLedger', () => {
it.each([600, 1_000, 3_000])(
'enforces the %i-unit boundary with 100 control units reserved',
(hardCap) => {
const ledger = new RelayConnectionLedger(hardCap, 100)
const sockets: Array<{ webSocket: WebSocket; close: () => void }> = []
for (let index = 0; index < (hardCap - 100) / 2; index++) {
const admission = ledger.tryReservePhone()
expect(admission).not.toBeNull()
const phoneSocket = socket()
admission!.upgrade.promote(phoneSocket.webSocket)
admission!.hostData.bind(`connection-${index}`)
sockets.push(phoneSocket)
}
expect(ledger.tryReservePhone()).toBeNull()
expect(ledger.tryReserveControl(false)).toBeNull()
for (let index = 0; index < 100; index++) {
const upgrade = ledger.tryReserveControl(true)
expect(upgrade).not.toBeNull()
const controlSocket = socket()
upgrade!.promote(controlSocket.webSocket)
sockets.push(controlSocket)
}
expect(ledger.counts().enforcedConnectionUnits).toBe(hardCap)
expect(ledger.tryReserveControl(true)).toBeNull()
sockets.at(-1)!.close()
const replacement = ledger.tryReserveControl(true)
expect(replacement).not.toBeNull()
replacement!.release()
}
)
it('transfers a pending host-data unit without increasing enforced capacity', () => {
const ledger = new RelayConnectionLedger(6, 2)
const phone = ledger.tryReservePhone()!
@@ -128,4 +96,3 @@ describe('RelayConnectionLedger', () => {
expect(ledger.canReserveBooking(100)).toBe(false)
})
})
@@ -1,7 +1,6 @@
import { connect, type Socket } from 'node:net'
import { afterEach, expect, it, vi } from 'vitest'
import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract/close-codes'
import { RELAY_PROTOCOL_LIMITS } from '@orca-cloud/relay-contract/protocol-limits'
import { loadRelayConfig } from '../config.js'
import type { RelayDatabase } from '../database/database.js'
import { createRelayServer } from '../relay-server.js'
@@ -19,10 +18,7 @@ const IDLE_LEDGER = {
enforcedConnectionUnits: 0
}
async function fixture(
options: { role?: 'cell' | 'director'; hardCap?: number } = {}
) {
const role = options.role ?? 'cell'
async function fixture() {
const database: RelayDatabase = {
query: vi.fn(async () => []),
queryLocked: vi.fn(async () => []),
@@ -35,27 +31,20 @@ async function fixture(
ORCA_RELAY_AUTH_ISSUER: 'https://auth.example.test',
ORCA_RELAY_JWKS_URL: 'https://auth.example.test/jwks',
ORCA_RELAY_ASSIGNMENT_SIGNING_KEY: 'synthetic-assignment-key-for-test-only',
ORCA_RELAY_ROLE: role,
ORCA_RELAY_ROLE: 'cell',
ORCA_RELAY_ADMIN_AUDIENCE: 'https://auth.example.test/admin',
ORCA_RELAY_DEPLOY_SERVICE_ACCOUNT: 'deploy@example.test',
ORCA_RELAY_CELL_CONNECTION_HARD_CAP: '600',
ORCA_RELAY_CELL_CONNECTION_UNOBSERVED_BOUND: '60',
...(role === 'director'
? {
ORCA_RELAY_CELLS_JSON: JSON.stringify([
{ id: 'cell-1', url: 'https://cell-1.example.test', capacityRequests: 900 }
])
}
: {})
ORCA_RELAY_CELL_CONNECTION_UNOBSERVED_BOUND: '60'
})
const relay = createRelayServer(config, database, {
connectionLedgerLimits: { hardCap: options.hardCap ?? 3, controlReserve: 1 }
connectionLedgerLimits: { hardCap: 3, controlReserve: 1 }
})
await new Promise<void>((resolve) => relay.server.listen(0, '127.0.0.1', resolve))
cleanups.push(() => new Promise<void>((resolve) => relay.server.close(() => resolve())))
const address = relay.server.address()
if (!address || typeof address === 'string') throw new Error('missing test port')
return { relay, port: address.port, database }
return { relay, port: address.port }
}
type RawPeer = {
@@ -108,9 +97,6 @@ async function silentUpgrade(port: number, target: string): Promise<RawPeer> {
}
}
// A 43-character base64url credential is the shortest value RelayAuthSchema accepts.
const WELL_FORMED_CREDENTIAL = 'abcdefghijklmnopqrstuvwxyzABCDEFGH012345678'
function maskedTextFrame(payload: string): Buffer {
const body = Buffer.from(payload)
const mask = Buffer.from([1, 2, 3, 4])
@@ -118,17 +104,6 @@ function maskedTextFrame(payload: string): Buffer {
return Buffer.concat([Buffer.from([0x81, 0x80 | body.length]), mask, masked])
}
function relayAuthFrame(): Buffer {
return maskedTextFrame(
JSON.stringify({
type: 'relay-auth',
v: 1,
mode: 'connect',
credential: WELL_FORMED_CREDENTIAL
})
)
}
// Rejected peer’s last unmasked frame: 0x88, length, big-endian status, then UTF-8 reason.
function readCloseFrame(received: Buffer): { code: number; reason: string } | null {
const start = received.lastIndexOf(0x88)
@@ -149,67 +124,16 @@ async function expectIdleLedger(
const PHONE_TARGET = '/v1/connect/abcdefghijklmnop'
it.each([
{ label: 'first-frame timeout', target: PHONE_TARGET, opcode: undefined },
{ label: 'binary first frame', target: PHONE_TARGET, opcode: 0x82 },
{ label: 'invalid phone auth', target: PHONE_TARGET, opcode: 0x81 },
{ label: 'invalid host-data auth', target: '/v1/host/data/connection-1', opcode: 0x81 }
])(
'releases admission after $label even when the peer ignores close',
async ({ target, opcode }) => {
const { relay, port, database } = await fixture()
const peer = await silentUpgrade(port, target)
const firstFrameDeadline = opcode === undefined ? RELAY_PROTOCOL_LIMITS.firstFrameDeadlineMs : 0
if (opcode !== undefined) peer.socket.write(Buffer.from([opcode, 0x80, 0, 0, 0, 0]))
await expectIdleLedger(relay, firstFrameDeadline + 2_000)
expect(relay.runtimeCounts().preAuthConnections).toBe(0)
expect(database.query).not.toHaveBeenCalled()
expect(database.queryLocked).not.toHaveBeenCalled()
await silentUpgrade(port, PHONE_TARGET)
expect(relay.connectionSnapshot()?.enforcedConnectionUnits).toBe(2)
}
)
it('releases admission after a rejected director invite when the peer ignores close', async () => {
const { relay, port } = await fixture({ role: 'director' })
it('releases admission after a binary first frame even when the peer ignores close', async () => {
const { relay, port } = await fixture()
const peer = await silentUpgrade(port, PHONE_TARGET)
peer.socket.write(relayAuthFrame())
peer.socket.write(Buffer.from([0x82, 0x80, 0, 0, 0, 0]))
await expectIdleLedger(relay, 2_000)
expect(readCloseFrame(peer.received())).toEqual({
code: RELAY_CLOSE_CODE.BAD_OUTER_CREDENTIAL,
reason: 'invalid invite'
})
expect(relay.runtimeCounts().preAuthConnections).toBe(0)
await silentUpgrade(port, PHONE_TARGET)
expect(relay.connectionSnapshot()?.enforcedConnectionUnits).toBe(2)
})
it('releases admission after a director move redirect when the peer ignores close', async () => {
const { relay, port } = await fixture({ role: 'director' })
const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
vi.spyOn(relay.store, 'resolveInviteForMove').mockResolvedValue({
userId: identity.userId,
relayDeviceId: 'device-1'
})
vi.spyOn(relay.assignments, 'resolve').mockResolvedValue({
...identity,
cellId: 'cell-1',
cellUrl: 'https://cell-1.example.test',
assignmentEpoch: 1,
leaseExpiresAt: Date.now() + 60_000
})
const peer = await silentUpgrade(port, PHONE_TARGET)
peer.socket.write(relayAuthFrame())
await expectIdleLedger(relay, 2_000)
expect(peer.received().toString('utf8')).toContain('"type":"relay-moved"')
expect(readCloseFrame(peer.received())).toEqual({
code: RELAY_CLOSE_CODE.DRAINING,
reason: 'connect to assigned cell'
})
await silentUpgrade(port, PHONE_TARGET)
expect(relay.connectionSnapshot()?.enforcedConnectionUnits).toBe(2)
})
// This responsive peer proves close bytes precede FIN, not delivery under backpressure: terminate can drop queued frames after 1s.
it('delivers the rejection code and a graceful FIN to a peer that keeps reading', async () => {
const { relay, port } = await fixture()
@@ -224,23 +148,3 @@ it('delivers the rejection code and a graceful FIN to a peer that keeps reading'
expect(peer.transport().error).toBeNull()
await expectIdleLedger(relay, 2_000)
})
const BINARY_FIRST_FRAME = Buffer.from([0x82, 0x80, 0, 0, 0, 0])
const CONCURRENT_PEERS_PER_SOURCE = 4
// Ledger does not clamp at zero, so a disconnect racing rejection exposes double release as a negative count.
it('releases exactly once when a rejected peer disconnects at the same moment', async () => {
const { relay, port } = await fixture({ hardCap: 2 * CONCURRENT_PEERS_PER_SOURCE + 1 })
const peers = await Promise.all(
Array.from({ length: CONCURRENT_PEERS_PER_SOURCE }, () => silentUpgrade(port, PHONE_TARGET))
)
for (const peer of peers) {
peer.socket.write(BINARY_FIRST_FRAME)
peer.socket.destroy()
}
await expectIdleLedger(relay, 3_000)
expect(relay.connectionSnapshot()).toMatchObject(IDLE_LEDGER)
expect(relay.runtimeCounts().preAuthConnections).toBe(0)
await silentUpgrade(port, PHONE_TARGET)
expect(relay.connectionSnapshot()?.enforcedConnectionUnits).toBe(2)
})
@@ -147,7 +147,7 @@ describe('host hello shedding under database pool pressure', () => {
})
}
it('admits hellos behind a deep queue that is still moving', async () => {
it('admits hellos behind a deep queue below the age threshold', async () => {
const cell = await startCell(120)
cell.advance(HOST_HELLO_SHED_OLDEST_WAIT_MS - 300)
@@ -167,12 +167,6 @@ describe('host hello shedding under database pool pressure', () => {
cell.advance(HOST_HELLO_SHED_OLDEST_WAIT_MS)
expect(await dial(cell)).toEqual({ status: 503, retryAfter: '2' })
// Shipped desktops lack unexpected-response listeners; refusal must reach their ordinary connect-error backoff.
const desktop = new WebSocket(cell.url, {
headers: { authorization: `Bearer ${cell.token}` }
})
const error = await new Promise<Error>((resolve) => desktop.once('error', resolve))
expect(error.message).toBe('Unexpected server response: 503')
})
})
@@ -28,7 +28,7 @@ describe('relay host proof failures', () => {
})
// A verifyCellAssignment connect-timeout rejection once escaped beginProof and crashed whole cells.
it.each(['cell', 'combined'] as const)('contains %s host hello database timeouts', async (role) => {
it('contains cell host hello database timeouts', async () => {
const keys = await generateKeyPair('ES256')
const publicJwk = await exportJWK(keys.publicKey)
const jwksServer: Server = createServer((_request, response) => {
@@ -64,7 +64,7 @@ describe('relay host proof failures', () => {
authAudience: 'orca-relay',
jwksUrl: issuer,
assignmentSigningKey: new Uint8Array(32),
role,
role: 'cell',
cellId: 'production-gce-c3',
cells: [{ id: 'production-gce-c3', url: relayUrl, capacityRequests: 4_000 }],
adminAudience: `${relayUrl}/admin`,
@@ -88,7 +88,7 @@ describe('relay host proof failures', () => {
await new Promise<void>((resolve) => relay.server.once('listening', resolve))
cleanup.push(() => new Promise<void>((resolve) => relay.server.close(() => resolve())))
vi.spyOn(console, 'log').mockImplementation(() => undefined)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const unhandled: unknown[] = []
const onUnhandled = (reason: unknown): void => {
unhandled.push(reason)
@@ -140,11 +140,6 @@ describe('relay host proof failures', () => {
)
expect(await closed).toBe(RELAY_CLOSE_CODE.LIMIT_EXCEEDED)
expect(
warn.mock.calls.map((call) => String(call[0]))
).toContain(
'[orca-relay] host hello proof failed: Connection terminated due to connection timeout'
)
await new Promise<void>((resolve) => setImmediate(resolve))
expect(unhandled).toEqual([])
})
@@ -12,8 +12,6 @@ const { Receiver } = createRequire(new URL('../splice-forwarder.test.ts', import
Receiver: new (options: { isServer: true }) => Writable
}
const FRAME_OVERHEAD = RELAY_ADMISSION_BUDGETS.queuedFrameOverheadBytes
class FakeSocket extends EventEmitter {
readonly OPEN = 1
readyState = 1
@@ -48,7 +46,6 @@ describe('splice forwarder', () => {
onClose: vi.fn()
})
client.emit('message', Buffer.alloc(8 * 1024 * 1024), true)
expect(budget.current()).toBe(8 * 1024 * 1024 + FRAME_OVERHEAD)
client.emit('message', Buffer.alloc(512 * 1024), true)
expect(client.closes[0]?.code).toBe(4429)
expect(host.closes[0]?.code).toBe(4429)
@@ -73,7 +70,6 @@ describe('splice forwarder', () => {
client.emit('message', frame, true)
expect(client.closes).toEqual([])
expect(host.closes).toEqual([])
expect(budget.current()).toBe(8 * 1024 * 1024 + FRAME_OVERHEAD)
host.bufferedAmount = 0
vi.advanceTimersByTime(25)
@@ -109,9 +105,6 @@ describe('splice forwarder', () => {
receiver.write(batch)
expect(client._socket.pause).toHaveBeenCalledTimes(
RELAY_ADMISSION_BUDGETS.spliceHardQueuedFrames
)
expect(closed).toHaveBeenCalledWith({
code: 4429,
reason: 'relay queue limit exceeded',
@@ -122,34 +115,4 @@ describe('splice forwarder', () => {
expect(host.closes[0]?.code).toBe(4429)
expect(budget.current()).toBe(0)
})
it('charges empty queued frames against the shared byte budget until drained or closed', () => {
vi.useFakeTimers()
const client = new FakeSocket()
const host = new FakeSocket()
host.bufferedAmount = 1024 * 1024
const budget = new ProcessQueuedByteBudget()
wireSplice({
client: client as unknown as WebSocket,
host: host as unknown as WebSocket,
budget,
onClose: vi.fn()
})
for (let index = 0; index < 3; index += 1) {
client.emit('message', Buffer.alloc(0), true)
}
expect(budget.current()).toBe(3 * FRAME_OVERHEAD)
host.bufferedAmount = 0
vi.advanceTimersByTime(25)
expect(host.sent).toHaveLength(3)
expect(budget.current()).toBe(0)
host.bufferedAmount = 1024 * 1024
client.emit('message', Buffer.alloc(0), true)
client.emit('message', Buffer.from('x'), true)
expect(budget.current()).toBe(2 * FRAME_OVERHEAD + 1)
client.close(1000, 'done')
expect(budget.current()).toBe(0)
})
})
@@ -1,9 +1,6 @@
import pg from 'pg'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { RelayAssignmentStore } from '../assignment/assignment-store.js'
import type { RelayConfig } from '../config.js'
import { openRelayDatabase, type RelayDatabase } from '../database/database.js'
import { createRelayServer } from '../relay-server.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
@@ -174,41 +171,6 @@ describePostgres('PostgreSQL control accept without a held cell row', () => {
expect(Number(cells[0]!.reserved_requests)).toBe(Number(units[0]!.units))
}, 20_000)
// Drive the production store: wrappers can omit optional fused commit and silently restore a separate COMMIT round trip.
it('commits every counter write in one message through the production store', async () => {
await removeTestRows(databases[0]!)
const store = new RelayAssignmentStore(databases[0]!, () => 100)
await prepareCell(store)
const { assignments } = createRelayServer(cellConfig(), databases[0]!, { now: () => 100 })
const assignment = await store.assign(first)
const firstControl = await assignments.activateControl(first, {
cellId: cell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
const sent = vi.spyOn(pg.Client.prototype, 'query')
let texts: string[] = []
try {
await assignments.releaseActivity(first, firstControl)
await assignments.activateControl(first, {
cellId: cell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 2
})
await assignments.acquireActivity(first, {
activityId: 'invite:fused',
kind: 'invite',
cellId: cell.id
})
} finally {
texts = sent.mock.calls.map(([text]) => String(text))
sent.mockRestore()
}
expect(texts.filter((text) => text.endsWith('; COMMIT'))).toHaveLength(3)
expect(texts.filter((text) => text === 'COMMIT')).toEqual([])
})
async function prepareCell(store: RelayAssignmentStore): Promise<void> {
await store.reconcileCells([cell])
await store.recordCellHeartbeat({
@@ -227,33 +189,4 @@ describePostgres('PostgreSQL control accept without a held cell row', () => {
connectionUnobservedBound: 50
})
}
})
function cellConfig(): RelayConfig {
return {
port: 0,
publicUrl: cell.url,
cellUrl: cell.url,
authIssuer: 'https://auth.example.test',
authAudience: 'orca-relay',
jwksUrl: 'https://auth.example.test/jwks',
assignmentSigningKey: new Uint8Array(32),
role: 'cell',
cellId: cell.id,
cells: [],
adminAudience: 'https://relay.example.test/v1/admin/drain',
deployServiceAccount: 'deploy@example.test',
runtimeServiceAccount: 'relay-cell@example.test',
adminJwksUrl: 'https://auth.example.test/jwks',
databasePoolMax: 10,
publicAssignmentsEnabled: true,
publicAssignmentConcurrency: 2,
publicAssignmentQueueMax: 128,
publicAssignmentWaitMs: 4_000,
publicResolveConcurrency: 1,
publicResolveWaitMs: 5_000,
publicAssignmentRetryAfterSeconds: 5,
dataDir: './data'
}
}
@@ -1,131 +0,0 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import type { RelayCellConfig } from '../config.js'
import { RelayAssignmentStore } from '../assignment/assignment-store.js'
import {
openInMemoryRelayDatabase,
type RelayDatabase,
type RelayLockOptions,
type RelayTransactionOptions,
type SqlRow
} from '../database/database.js'
const CELL: RelayCellConfig = {
id: 'accept-cell-a',
url: 'https://accept-a.example.com',
capacityRequests: 2
}
const host = { userId: 'accept-user', relayHostId: 'acceptho00000001' }
const second = { userId: 'accept-user', relayHostId: 'acceptho00000002' }
const third = { userId: 'accept-user', relayHostId: 'acceptho00000003' }
type Statement = { sql: string; locked: boolean }
// Records the statements a transaction issues, in order, so what the accept
// path does with the shared cell row can be asserted rather than described.
class RecordingDatabase implements RelayDatabase {
constructor(
private readonly inner: RelayDatabase,
readonly statements: Statement[] = []
) {}
async query(sql: string, params: unknown[] = []): Promise<SqlRow[]> {
this.statements.push({ sql, locked: false })
return await this.inner.query(sql, params)
}
async queryLocked(
sql: string,
params: unknown[] = [],
options: RelayLockOptions = {}
): Promise<SqlRow[]> {
this.statements.push({ sql, locked: true })
return await this.inner.queryLocked(sql, params, options)
}
async transaction<T>(
operation: (transaction: RelayDatabase) => Promise<T>,
options: RelayTransactionOptions = {}
): Promise<T> {
return await this.inner.transaction(
async (transaction) =>
await operation(new RecordingDatabase(transaction, this.statements)),
options
)
}
async close(): Promise<void> {
await this.inner.close()
}
}
describe('control accept cell-row lock span', () => {
let recorder: RecordingDatabase
let store: RelayAssignmentStore
let assignmentEpoch: number
beforeEach(async () => {
recorder = new RecordingDatabase(await openInMemoryRelayDatabase())
store = new RelayAssignmentStore(recorder, () => 100)
await store.reconcileCells([CELL])
assignmentEpoch = (await store.assign(host)).assignmentEpoch
})
afterEach(async () => {
await recorder?.close()
})
async function accept(generation: number): Promise<string> {
recorder.statements.length = 0
return await store.activateControl(host, {
cellId: CELL.id,
assignmentEpoch,
generation
})
}
function cellStatements(): Statement[] {
return recorder.statements.filter((statement) => /relay_cells/.test(statement.sql))
}
async function reservedRequests(): Promise<number> {
const row = (
await recorder.query(`SELECT reserved_requests FROM relay_cells WHERE cell_id = ?`, [
CELL.id
])
)[0]!
return Number(row.reserved_requests)
}
async function cellLeaseUnits(): Promise<number> {
const row = (
await recorder.query(
`SELECT COALESCE(SUM(request_units), 0) AS units
FROM relay_assignment_activity_leases WHERE cell_id = ?`,
[CELL.id]
)
)[0]!
return Number(row.units)
}
it('leaves the cell row untouched when a rebind retires and installs one control', async () => {
await accept(1)
await accept(2)
expect(cellStatements()).toEqual([])
expect(await reservedRequests()).toBe(1)
expect(await cellLeaseUnits()).toBe(1)
})
it('still refuses an accept that would exceed the cell capacity', async () => {
const control = await accept(1)
await store.assign(second)
await store.releaseActivity(host, control)
await store.assign(third)
expect(await reservedRequests()).toBe(CELL.capacityRequests)
await expect(accept(2)).rejects.toThrow('relay_capacity_exhausted')
expect(await reservedRequests()).toBe(CELL.capacityRequests)
expect(await cellLeaseUnits()).toBe(CELL.capacityRequests)
})
})

Some files were not shown because too many files have changed in this diff Show More