@@ -35,6 +35,7 @@ export class ConfigurationClientManager {
3535 #replicaCount: number = 0 ;
3636 #lastFallbackClientRefreshTime: number = 0 ;
3737 #lastFallbackClientRefreshAttempt: number = 0 ;
38+ #srvQueryPending: boolean = false ;
3839
3940 constructor (
4041 connectionStringOrEndpoint ?: string | URL ,
@@ -116,7 +117,8 @@ export class ConfigurationClientManager {
116117 ( ! this . #dynamicClients ||
117118 // All dynamic clients are in backoff means no client is available
118119 this . #dynamicClients. every ( client => currentTime < client . backoffEndTime ) ||
119- currentTime >= this . #lastFallbackClientRefreshTime + FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL ) ) {
120+ currentTime >= this . #lastFallbackClientRefreshTime + FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL )
121+ ) {
120122 this . #lastFallbackClientRefreshAttempt = currentTime ;
121123 await this . #discoverFallbackClients( this . endpoint . hostname ) ;
122124 return availableClients . concat ( this . #dynamicClients) ;
@@ -142,17 +144,33 @@ export class ConfigurationClientManager {
142144 }
143145
144146 async #discoverFallbackClients( host : string ) {
145- let result ;
146- let timeout ;
147+ if ( this . #srvQueryPending) {
148+ return ;
149+ }
150+ this . #srvQueryPending = true ;
151+ let result , timeoutId ;
147152 try {
153+ // Promise.race will not terminate the pending promises.
154+ // This is a known issue in JavaScript and there is no good solution for it.
155+ // We need to make sure the scale of the potential memory leak is controllable.
156+ const srvQueryPromise = this . #querySrvTargetHost( host ) ;
157+ // There is no way to check the promise status synchronously, so we need to set the flag through the callback.
158+ srvQueryPromise
159+ . then ( ( ) => this . #srvQueryPending = false ) // resolved
160+ . catch ( ( ) => this . #srvQueryPending = false ) ; // rejected
161+ // If the srvQueryPromise is rejected before timeout, the error will be caught in the catch block.
162+ // Otherwise, the timeout error will be caught in the catch block and the srvQueryPromise rejection will be ignored.
148163 result = await Promise . race ( [
149- new Promise ( ( _ , reject ) => timeout = setTimeout ( ( ) => reject ( new Error ( "SRV record query timed out." ) ) , SRV_QUERY_TIMEOUT ) ) ,
150- this . #querySrvTargetHost( host )
164+ new Promise ( ( _ , reject ) =>
165+ timeoutId = setTimeout ( ( ) => reject ( new Error ( "SRV record query timed out." ) ) , SRV_QUERY_TIMEOUT ) ) ,
166+ srvQueryPromise
151167 ] ) ;
152168 } catch ( error ) {
153- throw new Error ( `Failed to build fallback clients, ${ error . message } ` ) ;
169+ console . warn ( `Failed to build fallback clients, ${ error . message } ` ) ;
170+ this . #lastFallbackClientRefreshTime = Date . now ( ) ;
171+ return ; // silently fail when SRV record query times out
154172 } finally {
155- clearTimeout ( timeout ) ;
173+ clearTimeout ( timeoutId ) ;
156174 }
157175
158176 const srvTargetHosts = shuffleList ( result ) as string [ ] ;
0 commit comments