@@ -268,3 +268,103 @@ async fn test_connection_maintenance() -> anyhow::Result<()> {
268268
269269 Ok ( ( ) )
270270}
271+
272+ #[ sqlx_macros:: test]
273+ async fn pool_ping_timeout_on_return ( ) -> anyhow:: Result < ( ) > {
274+ sqlx:: any:: install_default_drivers ( ) ;
275+
276+ // With a reasonable timeout, connections should be returned to the pool
277+ let pool = AnyPoolOptions :: new ( )
278+ . ping_timeout ( Duration :: from_secs ( 10 ) )
279+ . max_connections ( 1 )
280+ . connect ( & dotenvy:: var ( "DATABASE_URL" ) ?)
281+ . await ?;
282+
283+ let mut conn = pool. acquire ( ) . await ?;
284+ sqlx:: query ( "SELECT 1" ) . fetch_one ( & mut * conn) . await ?;
285+ conn. return_to_pool ( ) . await ;
286+
287+ assert_eq ! ( pool. num_idle( ) , 1 ) ;
288+ drop ( pool) ;
289+
290+ // With a zero timeout, connections should be discarded on return
291+ let pool = AnyPoolOptions :: new ( )
292+ . ping_timeout ( Duration :: ZERO )
293+ . max_connections ( 1 )
294+ . connect ( & dotenvy:: var ( "DATABASE_URL" ) ?)
295+ . await ?;
296+
297+ let mut conn = pool. acquire ( ) . await ?;
298+ sqlx:: query ( "SELECT 1" ) . fetch_one ( & mut * conn) . await ?;
299+ conn. return_to_pool ( ) . await ;
300+
301+ assert_eq ! ( pool. num_idle( ) , 0 ) ;
302+
303+ Ok ( ( ) )
304+ }
305+
306+ #[ sqlx_macros:: test]
307+ async fn pool_ping_timeout_on_acquire ( ) -> anyhow:: Result < ( ) > {
308+ sqlx:: any:: install_default_drivers ( ) ;
309+
310+ // Helper to wait for idle connections
311+ async fn wait_for_idle ( pool : & sqlx:: AnyPool , expected : usize ) {
312+ for _ in 0 ..100 {
313+ if pool. num_idle ( ) == expected {
314+ return ;
315+ }
316+ sqlx_core:: rt:: sleep ( Duration :: from_millis ( 50 ) ) . await ;
317+ }
318+ panic ! ( "timed out waiting for {} idle connections, got {}" , expected, pool. num_idle( ) ) ;
319+ }
320+
321+ // With a reasonable timeout, idle connections should be used
322+ let connect_count = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
323+ let connect_count_ = connect_count. clone ( ) ;
324+ let pool = AnyPoolOptions :: new ( )
325+ . ping_timeout ( Duration :: from_secs ( 10 ) )
326+ . test_before_acquire ( true )
327+ . min_connections ( 1 )
328+ . max_connections ( 1 )
329+ . after_connect ( move |_conn, _meta| {
330+ connect_count_. fetch_add ( 1 , Ordering :: SeqCst ) ;
331+ Box :: pin ( async { Ok ( ( ) ) } )
332+ } )
333+ . connect ( & dotenvy:: var ( "DATABASE_URL" ) ?)
334+ . await ?;
335+
336+ wait_for_idle ( & pool, 1 ) . await ;
337+ assert_eq ! ( connect_count. load( Ordering :: SeqCst ) , 1 ) ;
338+
339+ // Acquire should reuse the same connection
340+ let _conn = pool. acquire ( ) . await ?;
341+ assert_eq ! ( connect_count. load( Ordering :: SeqCst ) , 1 ) ;
342+ drop ( pool) ;
343+
344+ // With a zero timeout, idle connections should fail ping and be replaced
345+ let connect_count = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
346+ let connect_count_ = connect_count. clone ( ) ;
347+ let pool = AnyPoolOptions :: new ( )
348+ . ping_timeout ( Duration :: ZERO )
349+ . test_before_acquire ( true )
350+ . min_connections ( 1 )
351+ . max_connections ( 1 )
352+ // Disable timeouts to prevent the reaper from interfering
353+ . idle_timeout ( None )
354+ . max_lifetime ( None )
355+ . after_connect ( move |_conn, _meta| {
356+ connect_count_. fetch_add ( 1 , Ordering :: SeqCst ) ;
357+ Box :: pin ( async { Ok ( ( ) ) } )
358+ } )
359+ . connect_lazy ( & dotenvy:: var ( "DATABASE_URL" ) ?) ?;
360+
361+ wait_for_idle ( & pool, 1 ) . await ;
362+ assert_eq ! ( connect_count. load( Ordering :: SeqCst ) , 1 ) ;
363+
364+ // Acquire - ping will fail and the caller will go ahead and open a new
365+ // connection. Importantly, the caller won't observe any error.
366+ let _conn = pool. acquire ( ) . await ?;
367+ assert_eq ! ( connect_count. load( Ordering :: SeqCst ) , 2 ) ;
368+
369+ Ok ( ( ) )
370+ }
0 commit comments