@@ -284,6 +284,44 @@ describe('McpConnectionPool', () => {
284284 await next . release ( )
285285 } )
286286
287+ it ( 'reuses a concurrently-pooled replacement rather than orphaning it (recreate race)' , async ( ) => {
288+ // stale's ping is deferred per-call so we can fully resolve one acquire before
289+ // the other's ping settles — the exact interleaving that used to leak.
290+ const releasePing : Array < ( alive : boolean ) => void > = [ ]
291+ const stale = makeFakeClient ( )
292+ ; ( stale . ping as ReturnType < typeof vi . fn > ) . mockImplementation (
293+ ( ) =>
294+ new Promise ( ( resolve , reject ) => {
295+ releasePing . push ( ( alive ) => ( alive ? resolve ( { } ) : reject ( new Error ( 'dead' ) ) ) )
296+ } )
297+ )
298+ const fresh = makeFakeClient ( )
299+ const create = vi
300+ . fn < ( ) => Promise < McpClient > > ( )
301+ . mockResolvedValueOnce ( stale )
302+ . mockResolvedValueOnce ( fresh )
303+
304+ await borrow ( pool , params ( 's1:w1:u1' , create ) )
305+ vi . setSystemTime ( Date . now ( ) + 61 * 1000 )
306+
307+ const pA = pool . acquire ( params ( 's1:w1:u1' , create ) )
308+ const pB = pool . acquire ( params ( 's1:w1:u1' , create ) )
309+ while ( releasePing . length < 2 ) await Promise . resolve ( )
310+
311+ // A's ping fails → A retires stale, rebuilds `fresh`, pools it, clears pending.
312+ releasePing [ 0 ] ( false )
313+ for ( let i = 0 ; i < 20 ; i ++ ) await Promise . resolve ( )
314+ // B's ping fails → B must reuse the pooled `fresh`, not create a third client.
315+ releasePing [ 1 ] ( false )
316+
317+ const [ la , lb ] = await Promise . all ( [ pA , pB ] )
318+ expect ( create ) . toHaveBeenCalledTimes ( 2 )
319+ expect ( la . client ) . toBe ( fresh )
320+ expect ( lb . client ) . toBe ( fresh )
321+ await la . release ( )
322+ await lb . release ( )
323+ } )
324+
287325 it ( 'bypasses the pool once disposed (connects without caching)' , async ( ) => {
288326 const client = makeFakeClient ( )
289327 const create = vi . fn ( async ( ) => client )
0 commit comments