fix(relay): let the same-cap trust probe outlast load-balancer warm-up (#31067)

A freshly restarted cell can be ready while the global load balancer still
answers 503 for it. The probe retried once after 2 s, so c10's 2026-10-20
roll failed after its drain and was left isolated. Retry wrapped source
5xx every 10 s for up to 2 minutes.

Co-authored-by: E2E Test <e2e@test.local>
This commit is contained in:
Jinwoo Hong
2026-10-20 04:40:49 -04:00
committed by GitHub
co-authored by E2E Test
parent 14131cd068
commit 30d32d3fcf
2 changed files with 29 additions and 5 deletions
+13 -3
View File
@@ -6,6 +6,8 @@ import { fetchAdminOnceMore } from './relay-admin-transient-retry.mjs'
const PRODUCTION_CELL =
/^production-gce-c(?:7|8|9|10|13|14|15|16|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35)$/
const DIRECTOR_ORIGIN = 'https://relay.onorca.dev'
const SOURCE_TRANSIENT_RETRIES = 12
const SOURCE_TRANSIENT_RETRY_DELAY_MS = 10_000
export function parseRehomeTrustProbeArguments(argv, environment = process.env) {
const values = {}
@@ -56,11 +58,19 @@ export async function probeRehomeTrust(config, dependencies = {}) {
},
{ wait: dependencies.wait }
)
const wait = dependencies.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))
let response = await request()
let body = await response.json().catch(() => ({}))
// The director wraps source HTTP failures in 409; retry only explicit transient statuses.
if (response.status === 409 && /^regional_rehome_trust_probe_source_(500|502|503|504)$/.test(body?.error ?? '')) {
await (dependencies.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(2_000)
// The director wraps source HTTP failures in 409; retry only explicit transient statuses. The
// load balancer can answer 503 for a minute after a fresh instance is ready (c10, 2026-10-20).
for (
let retry = 0;
retry < SOURCE_TRANSIENT_RETRIES &&
response.status === 409 &&
/^regional_rehome_trust_probe_source_(500|502|503|504)$/.test(body?.error ?? '');
retry++
) {
await wait(SOURCE_TRANSIENT_RETRY_DELAY_MS)
response = await request()
body = await response.json().catch(() => ({}))
}
@@ -165,13 +165,27 @@ test('reports safe trust reasons, keeps rejection final, and redacts arbitrary e
}
})
test('stops after the second wrapped transient failure', async () => {
test('outlasts a load-balancer warm-up of consecutive wrapped source 503s', async () => {
let calls = 0
const waits = []
const result = await probeRehomeTrust(parseRehomeTrustProbeArguments(argv, environment), {
wait: async (ms) => { waits.push(ms) },
fetch: async () => ++calls <= 6
? Response.json({ error: 'regional_rehome_trust_probe_source_503' }, { status: 409 })
: Response.json(provenProbe)
})
assert.equal(calls, 7)
assert.deepEqual(waits, Array(6).fill(10_000))
assert.equal(result.proven, true)
})
test('stops after two minutes of wrapped transient failures', async () => {
let calls = 0
await assert.rejects(probeRehomeTrust(parseRehomeTrustProbeArguments(argv, environment), {
wait: async () => {},
fetch: async () => { calls++; return Response.json({ error: 'regional_rehome_trust_probe_source_503' }, { status: 409 }) }
}), /returned 409.*source_503/)
assert.equal(calls, 2)
assert.equal(calls, 13)
})
// The probe allowlist is the committed rehome source list, so a declared source can always probe.