From 43d31d4b25dfd4d2255e2c12ed237ce3a3af856b Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 7 Sep 2026 15:45:14 -0400 Subject: [PATCH] fix(relay): hold a still-measured incumbent through an incomplete catalog An expiry that lands during the other region's roll wave saw a one-region catalog and withheld the hint, sending a correctly homed desktop to the director default for an hour. The incumbent was earned against a full catalog, so it is kept; a lone survivor that is not the incumbent is still never promoted. --- .../relay/relay-region-preference.test.ts | 41 +++++++++++++++++++ .../runtime/relay/relay-region-preference.ts | 14 +++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/main/runtime/relay/relay-region-preference.test.ts b/src/main/runtime/relay/relay-region-preference.test.ts index 33b2b2643cc..c96ace66b8e 100644 --- a/src/main/runtime/relay/relay-region-preference.test.ts +++ b/src/main/runtime/relay/relay-region-preference.test.ts @@ -384,6 +384,47 @@ describe('Relay region preference', () => { ]) }) + it('holds a still-measured incumbent through an incomplete catalog', async () => { + // Why: an Asia desktop whose 24 h hint expires during a US roll wave sees a + // one-region catalog. Withholding the hint would place it at the director + // default for an hour; the incumbent was earned against a full catalog. + const path = userDataPath() + writeCache(path, 'asia-east2') + const events: unknown[] = [] + const healthy = sampledProbe({ [ASIA]: [90, 30, 32, 34] }) + await expect( + new RelayRegionPreferenceResolver({ + directorUrl: DIRECTOR, + userDataPath: path, + fetch: catalogFetch([{ region: 'asia-east2', probeOrigins: [ASIA] }]), + probe: healthy.probe, + now: () => 1_000, + logEvent: (event) => events.push(event) + }).resolve() + ).resolves.toBe('asia-east2') + expect(JSON.parse(readFileSync(cachePath(path), 'utf8'))).toMatchObject({ + region: 'asia-east2', + expiresAt: 1_000 + 24 * 60 * 60_000 + }) + expect(events).toEqual([expect.objectContaining({ chosenRegion: 'asia-east2' })]) + }) + + it('does not promote a lone survivor that is not the incumbent', async () => { + const path = userDataPath() + writeCache(path, 'us-central1') + const healthy = sampledProbe({ [ASIA]: [90, 30, 32, 34] }) + await expect( + new RelayRegionPreferenceResolver({ + directorUrl: DIRECTOR, + userDataPath: path, + fetch: catalogFetch([{ region: 'asia-east2', probeOrigins: [ASIA] }]), + probe: healthy.probe, + now: () => 1_000 + }).resolve() + ).resolves.toBeUndefined() + expect(JSON.parse(readFileSync(cachePath(path), 'utf8'))).toMatchObject({ region: null }) + }) + it('uses a valid diagnostic override without network or cache mutation', async () => { const path = userDataPath() const fetch = vi.fn() diff --git a/src/main/runtime/relay/relay-region-preference.ts b/src/main/runtime/relay/relay-region-preference.ts index e84c1f04a23..86d1b82561f 100644 --- a/src/main/runtime/relay/relay-region-preference.ts +++ b/src/main/runtime/relay/relay-region-preference.ts @@ -183,13 +183,19 @@ export class RelayRegionPreferenceResolver { this.log(relayRegionCatalogFailureEvent(this.options.directorUrl)) ) const measurements = measuredRegions(reports) + const previousRegion = previous?.region ?? null // Why: a region may only win against a measured competitor. An unmeasured // peer, or one the catalog dropped for having no general cell (a roll wave // or heartbeat stall), means director default placement beats a lone survivor. - const selected = - measurements.length < reports.length || reports.length < RELAY_REGIONS.length - ? null - : selectRegionMeasurement(measurements, previous?.region ?? null) + // The exception is the incumbent: a hint that was earned against a full + // catalog and still measures is held, not discarded, or an expiry that lands + // during the other region's roll wave would send the desktop to the default. + const complete = + measurements.length === reports.length && reports.length === RELAY_REGIONS.length + const incumbent = measurements.find((measurement) => measurement.region === previousRegion) + const selected = complete + ? selectRegionMeasurement(measurements, previousRegion) + : (incumbent ?? null) const ttlMs = selected ? CACHE_TTL_MS : NO_HINT_TTL_MS this.log( relayRegionRefreshEvent({