@@ -7,7 +7,7 @@ import { db } from '@sim/db'
77import { mcpServerOauth } from '@sim/db/schema'
88import { createLogger } from '@sim/logger'
99import { toError } from '@sim/utils/errors'
10- import { sleep } from '@sim/utils/helpers'
10+ import { interruptibleSleep } from '@sim/utils/helpers'
1111import { generateId , generateShortId } from '@sim/utils/id'
1212import { and , eq , gt } from 'drizzle-orm'
1313import { acquireLock , extendLock , releaseLock } from '@/lib/core/config/redis'
@@ -275,7 +275,12 @@ const REFRESH_QUEUE_WAIT_TIMEOUT_MS = 90_000
275275
276276const inflightChains = new Map < string , Promise < unknown > > ( )
277277
278- export async function withMcpOauthRefreshLock < T > ( rowId : string , fn : ( ) => Promise < T > ) : Promise < T > {
278+ export async function withMcpOauthRefreshLock < T > (
279+ rowId : string ,
280+ fn : ( ) => Promise < T > ,
281+ signal ?: AbortSignal
282+ ) : Promise < T > {
283+ signal ?. throwIfAborted ( )
279284 const lockKey = `mcp:oauth:refresh:${ rowId } `
280285 const prev = inflightChains . get ( lockKey ) ?? Promise . resolve ( )
281286 const prevSettled = prev . catch ( ( ) => undefined )
@@ -285,7 +290,8 @@ export async function withMcpOauthRefreshLock<T>(rowId: string, fn: () => Promis
285290 if ( queueTimedOut ) {
286291 throw new Error ( `MCP OAuth refresh queue for ${ rowId } abandoned after timeout` )
287292 }
288- return runWithRedisMutex ( lockKey , rowId , fn )
293+ signal ?. throwIfAborted ( )
294+ return runWithRedisMutex ( lockKey , rowId , fn , signal )
289295 } )
290296 inflightChains . set ( lockKey , next )
291297 const cleanup = ( ) => {
@@ -305,11 +311,25 @@ export async function withMcpOauthRefreshLock<T>(rowId: string, fn: () => Promis
305311 } , REFRESH_QUEUE_WAIT_TIMEOUT_MS )
306312 queueTimer . unref ?.( )
307313 } )
314+ let abortListener : ( ( ) => void ) | undefined
315+ const queueAbort = new Promise < never > ( ( _resolve , reject ) => {
316+ if ( ! signal ) return
317+ abortListener = ( ) => {
318+ try {
319+ signal . throwIfAborted ( )
320+ } catch ( error ) {
321+ reject ( error )
322+ }
323+ }
324+ signal . addEventListener ( 'abort' , abortListener , { once : true } )
325+ if ( signal . aborted ) abortListener ( )
326+ } )
308327
309328 try {
310- await Promise . race ( [ prevSettled , queueDeadline ] )
329+ await Promise . race ( [ prevSettled , queueDeadline , queueAbort ] )
311330 } finally {
312331 clearTimeout ( queueTimer )
332+ if ( signal && abortListener ) signal . removeEventListener ( 'abort' , abortListener )
313333 }
314334
315335 return next
@@ -318,16 +338,19 @@ export async function withMcpOauthRefreshLock<T>(rowId: string, fn: () => Promis
318338async function runWithRedisMutex < T > (
319339 lockKey : string ,
320340 rowId : string ,
321- fn : ( ) => Promise < T >
341+ fn : ( ) => Promise < T > ,
342+ signal ?: AbortSignal
322343) : Promise < T > {
323344 const ownerToken = generateShortId ( )
324345 const deadline = Date . now ( ) + REFRESH_MAX_WAIT_MS
325346
326347 while ( true ) {
348+ signal ?. throwIfAborted ( )
327349 let acquired = false
328350 try {
329351 acquired = await acquireLock ( lockKey , ownerToken , REFRESH_LOCK_TTL_SEC )
330352 } catch ( error ) {
353+ signal ?. throwIfAborted ( )
331354 logger . warn ( 'Redis unavailable, running OAuth flow uncoordinated' , {
332355 rowId,
333356 error : toError ( error ) . message ,
@@ -345,6 +368,7 @@ async function runWithRedisMutex<T>(
345368 } )
346369 } , REFRESH_LOCK_EXTEND_INTERVAL_MS )
347370 try {
371+ signal ?. throwIfAborted ( )
348372 return await fn ( )
349373 } finally {
350374 clearInterval ( watchdog )
@@ -357,6 +381,7 @@ async function runWithRedisMutex<T>(
357381 }
358382 }
359383
384+ signal ?. throwIfAborted ( )
360385 if ( Date . now ( ) >= deadline ) {
361386 // Lock still held by another process AND its watchdog is keeping it
362387 // alive — falling open would let us refresh concurrently and race the
@@ -367,6 +392,6 @@ async function runWithRedisMutex<T>(
367392 `MCP OAuth refresh lock for ${ rowId } held longer than ${ REFRESH_MAX_WAIT_MS } ms`
368393 )
369394 }
370- await sleep ( REFRESH_POLL_INTERVAL_MS )
395+ await interruptibleSleep ( REFRESH_POLL_INTERVAL_MS , signal )
371396 }
372397}
0 commit comments