From ab928e3fffe107eeb35bcdfd32f0a4f3775c6cf3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 21 May 2026 22:57:28 -0700 Subject: [PATCH 1/2] fix(oauth): follower last-chance read after poll deadline --- .../concurrency/__tests__/leader-lock.test.ts | 22 +++++++++++++++++++ apps/sim/lib/concurrency/leader-lock.ts | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts index 8e0cbcc6cd..5153862c2e 100644 --- a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts +++ b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts @@ -117,6 +117,28 @@ describe('withLeaderLock', () => { expect(onFollower.mock.calls.length).toBeGreaterThanOrEqual(2) }) + it('follower does a final read after timeout to catch a just-finished leader', async () => { + redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false) + + let polls = 0 + const onFollower = vi.fn(async () => { + polls += 1 + // Return null during the poll loop, value on the post-deadline read. + if (polls <= 2) return null + return 'late-leader' + }) + + const result = await withLeaderLock({ + key: 'k', + pollIntervalMs: 5, + maxWaitMs: 12, + onLeader: async () => 'should-not-run', + onFollower, + }) + + expect(result).toBe('late-leader') + }) + it('follower returns null after timeout', async () => { redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false) diff --git a/apps/sim/lib/concurrency/leader-lock.ts b/apps/sim/lib/concurrency/leader-lock.ts index dd0ed0402a..567abd1c8a 100644 --- a/apps/sim/lib/concurrency/leader-lock.ts +++ b/apps/sim/lib/concurrency/leader-lock.ts @@ -64,6 +64,10 @@ export async function withLeaderLock(opts: LeaderLockOptions): Promise Date: Thu, 21 May 2026 23:02:38 -0700 Subject: [PATCH 2/2] test(oauth): exercise last-chance read in follower timeout test --- apps/sim/lib/concurrency/__tests__/leader-lock.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts index 5153862c2e..8b3e2a3a06 100644 --- a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts +++ b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts @@ -120,10 +120,11 @@ describe('withLeaderLock', () => { it('follower does a final read after timeout to catch a just-finished leader', async () => { redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false) + // pollInterval=5, maxWait=9 → loop exits after 2 in-loop polls (T+5, T+10); + // the third call (polls=3) is the post-deadline last-chance read. let polls = 0 const onFollower = vi.fn(async () => { polls += 1 - // Return null during the poll loop, value on the post-deadline read. if (polls <= 2) return null return 'late-leader' }) @@ -131,12 +132,13 @@ describe('withLeaderLock', () => { const result = await withLeaderLock({ key: 'k', pollIntervalMs: 5, - maxWaitMs: 12, + maxWaitMs: 9, onLeader: async () => 'should-not-run', onFollower, }) expect(result).toBe('late-leader') + expect(onFollower).toHaveBeenCalledTimes(3) }) it('follower returns null after timeout', async () => {