11// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
22
33using static Valkey . Glide . ConnectionConfiguration ;
4+ using static Valkey . Glide . Errors ;
45
56namespace Valkey . Glide . TestUtils ;
67
@@ -16,6 +17,16 @@ public abstract class Server : IDisposable
1617 /// </summary>
1718 protected static readonly GlideString [ ] KillClientArgs = [ "CLIENT" , "KILL" , "TYPE" , "NORMAL" ] ;
1819
20+ /// <summary>
21+ /// Number of attempts to establish the initial client connection.
22+ /// </summary>
23+ private const int ConnectionAttempts = 5 ;
24+
25+ /// <summary>
26+ /// Delay between initial connection attempts.
27+ /// </summary>
28+ private static readonly TimeSpan ConnectionRetryDelay = TimeSpan . FromMilliseconds ( 500 ) ;
29+
1930 #endregion
2031 #region Fields
2132
@@ -122,6 +133,44 @@ public void Dispose()
122133 /// </summary>
123134 public abstract Task KillClientsAsync ( ) ;
124135
136+ #endregion
137+ #region Protected Methods
138+
139+ /// <summary>
140+ /// Creates a client using the given factory.
141+ /// </summary>
142+ /// <typeparam name="T">The client type produced by the factory.</typeparam>
143+ /// <param name="factory">Factory that builds and connects a client.</param>
144+ /// <returns>The connected client.</returns>
145+ /// <exception cref="ConnectionException">
146+ /// Thrown when a connection could not be established after all attempts.
147+ /// </exception>
148+ protected static async Task < T > CreateClientAsync < T > ( Func < Task < T > > factory )
149+ where T : BaseClient
150+ {
151+ ConnectionException ? lastException = null ;
152+
153+ /// Retry the initial connection attempt if needed to handle flakiness in CI environment.
154+ for ( int attempt = 1 ; attempt <= ConnectionAttempts ; attempt ++ )
155+ {
156+ try
157+ {
158+ return await factory ( ) ;
159+ }
160+ catch ( ConnectionException ex )
161+ {
162+ lastException = ex ;
163+
164+ if ( attempt < ConnectionAttempts )
165+ {
166+ await Task . Delay ( ConnectionRetryDelay ) ;
167+ }
168+ }
169+ }
170+
171+ throw lastException ! ;
172+ }
173+
125174 #endregion
126175}
127176
@@ -150,7 +199,11 @@ public override async Task<BaseClient> CreateClientAsync()
150199 /// Builds and returns a cluster client for this server.
151200 /// </summary>
152201 public async Task < GlideClusterClient > CreateClusterClientAsync ( )
153- => await GlideClusterClient . CreateClient ( CreateConfigBuilder ( ) . Build ( ) ) ;
202+ {
203+ var config = CreateConfigBuilder ( ) . Build ( ) ;
204+ Task < GlideClusterClient > factory ( ) => GlideClusterClient . CreateClient ( config ) ;
205+ return await CreateClientAsync ( factory ) ;
206+ }
154207
155208 public override async Task SetPasswordAsync ( string password )
156209 {
@@ -200,7 +253,11 @@ public override async Task<BaseClient> CreateClientAsync()
200253 /// Builds and returns a standalone client for this server.
201254 /// </summary>
202255 public async Task < GlideClient > CreateStandaloneClientAsync ( )
203- => await GlideClient . CreateClient ( CreateConfigBuilder ( ) . Build ( ) ) ;
256+ {
257+ var config = CreateConfigBuilder ( ) . Build ( ) ;
258+ Task < GlideClient > factory ( ) => GlideClient . CreateClient ( config ) ;
259+ return await CreateClientAsync ( factory ) ;
260+ }
204261
205262 public override async Task SetPasswordAsync ( string password )
206263 {
0 commit comments