diff --git a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts index 8e0cbcc6cd..8b3e2a3a06 100644 --- a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts +++ b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts @@ -117,6 +117,30 @@ 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) + + // 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 + if (polls <= 2) return null + return 'late-leader' + }) + + const result = await withLeaderLock({ + key: 'k', + pollIntervalMs: 5, + maxWaitMs: 9, + onLeader: async () => 'should-not-run', + onFollower, + }) + + expect(result).toBe('late-leader') + expect(onFollower).toHaveBeenCalledTimes(3) + }) + 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