@@ -64,27 +64,32 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
6464 #isFailoverRequest: boolean = false ;
6565
6666 // Refresh
67- #watchAll : boolean = false ;
68- #refreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
67+ #refreshInProgress : boolean = false ;
68+
6969 #onRefreshListeners: Array < ( ) => any > = [ ] ;
7070 /**
7171 * Aka watched settings.
7272 */
7373 #sentinels: ConfigurationSettingId [ ] = [ ] ;
74- #refreshTimer: RefreshTimer ;
74+ #watchAll: boolean = false ;
75+ #kvRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
76+ #kvRefreshTimer: RefreshTimer ;
7577
7678 // Feature flags
77- #featureFlagRefreshInterval : number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
78- #featureFlagRefreshTimer : RefreshTimer ;
79+ #ffRefreshInterval : number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
80+ #ffRefreshTimer : RefreshTimer ;
7981
8082 /**
81- * selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
83+ * Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
8284 */
83- #keyValueSelectors : PagedSettingSelector [ ] = [ ] ;
85+ #kvSelectors : PagedSettingSelector [ ] = [ ] ;
8486 /**
85- * selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
87+ * Selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
8688 */
87- #featureFlagSelectors: PagedSettingSelector [ ] = [ ] ;
89+ #ffSelectors: PagedSettingSelector [ ] = [ ] ;
90+
91+ // Load balancing
92+ #lastSuccessfulEndpoint: string = "" ;
8893
8994 constructor (
9095 clientManager : ConfigurationClientManager ,
@@ -123,18 +128,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
123128 if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
124129 throw new Error ( `The refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
125130 } else {
126- this . #refreshInterval = refreshIntervalInMs ;
131+ this . #kvRefreshInterval = refreshIntervalInMs ;
127132 }
128133 }
129- this . #refreshTimer = new RefreshTimer ( this . #refreshInterval ) ;
134+ this . #kvRefreshTimer = new RefreshTimer ( this . #kvRefreshInterval ) ;
130135 }
131136
132- this . #keyValueSelectors = getValidKeyValueSelectors ( options ?. selectors ) ;
137+ this . #kvSelectors = getValidKeyValueSelectors ( options ?. selectors ) ;
133138
134139 // feature flag options
135140 if ( options ?. featureFlagOptions ?. enabled ) {
136141 // validate feature flag selectors
137- this . #featureFlagSelectors = getValidFeatureFlagSelectors ( options . featureFlagOptions . selectors ) ;
142+ this . #ffSelectors = getValidFeatureFlagSelectors ( options . featureFlagOptions . selectors ) ;
138143
139144 if ( options . featureFlagOptions . refresh ?. enabled ) {
140145 const { refreshIntervalInMs } = options . featureFlagOptions . refresh ;
@@ -143,11 +148,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
143148 if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
144149 throw new Error ( `The feature flag refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
145150 } else {
146- this . #featureFlagRefreshInterval = refreshIntervalInMs ;
151+ this . #ffRefreshInterval = refreshIntervalInMs ;
147152 }
148153 }
149154
150- this . #featureFlagRefreshTimer = new RefreshTimer ( this . #featureFlagRefreshInterval ) ;
155+ this . #ffRefreshTimer = new RefreshTimer ( this . #ffRefreshInterval ) ;
151156 }
152157 }
153158
@@ -274,6 +279,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
274279 throw new Error ( "Refresh is not enabled for key-values or feature flags." ) ;
275280 }
276281
282+ if ( this . #refreshInProgress) {
283+ return ;
284+ }
285+ this . #refreshInProgress = true ;
286+ try {
287+ await this . #refreshTasks( ) ;
288+ } finally {
289+ this . #refreshInProgress = false ;
290+ }
291+ }
292+
293+ async #refreshTasks( ) : Promise < void > {
277294 const refreshTasks : Promise < boolean > [ ] = [ ] ;
278295 if ( this . #refreshEnabled) {
279296 refreshTasks . push ( this . #refreshKeyValues( ) ) ;
@@ -331,7 +348,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
331348 * If false, loads key-value using the key-value selectors. Defaults to false.
332349 */
333350 async #loadConfigurationSettings( loadFeatureFlag : boolean = false ) : Promise < ConfigurationSetting [ ] > {
334- const selectors = loadFeatureFlag ? this . #featureFlagSelectors : this . #keyValueSelectors ;
351+ const selectors = loadFeatureFlag ? this . #ffSelectors : this . #kvSelectors ;
335352 const funcToExecute = async ( client ) => {
336353 const loadedSettings : ConfigurationSetting [ ] = [ ] ;
337354 // deep copy selectors to avoid modification if current client fails
@@ -363,9 +380,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
363380 }
364381
365382 if ( loadFeatureFlag ) {
366- this . #featureFlagSelectors = selectorsToUpdate ;
383+ this . #ffSelectors = selectorsToUpdate ;
367384 } else {
368- this . #keyValueSelectors = selectorsToUpdate ;
385+ this . #kvSelectors = selectorsToUpdate ;
369386 }
370387 return loadedSettings ;
371388 } ;
@@ -449,14 +466,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
449466 */
450467 async #refreshKeyValues( ) : Promise < boolean > {
451468 // if still within refresh interval/backoff, return
452- if ( ! this . #refreshTimer . canRefresh ( ) ) {
469+ if ( ! this . #kvRefreshTimer . canRefresh ( ) ) {
453470 return Promise . resolve ( false ) ;
454471 }
455472
456473 // try refresh if any of watched settings is changed.
457474 let needRefresh = false ;
458475 if ( this . #watchAll) {
459- needRefresh = await this . #checkConfigurationSettingsChange( this . #keyValueSelectors ) ;
476+ needRefresh = await this . #checkConfigurationSettingsChange( this . #kvSelectors ) ;
460477 }
461478 for ( const sentinel of this . #sentinels. values ( ) ) {
462479 const response = await this . #getConfigurationSetting( sentinel , {
@@ -476,7 +493,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
476493 await this . #loadSelectedAndWatchedKeyValues( ) ;
477494 }
478495
479- this . #refreshTimer . reset ( ) ;
496+ this . #kvRefreshTimer . reset ( ) ;
480497 return Promise . resolve ( needRefresh ) ;
481498 }
482499
@@ -486,16 +503,16 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
486503 */
487504 async #refreshFeatureFlags( ) : Promise < boolean > {
488505 // if still within refresh interval/backoff, return
489- if ( ! this . #featureFlagRefreshTimer . canRefresh ( ) ) {
506+ if ( ! this . #ffRefreshTimer . canRefresh ( ) ) {
490507 return Promise . resolve ( false ) ;
491508 }
492509
493- const needRefresh = await this . #checkConfigurationSettingsChange( this . #featureFlagSelectors ) ;
510+ const needRefresh = await this . #checkConfigurationSettingsChange( this . #ffSelectors ) ;
494511 if ( needRefresh ) {
495512 await this . #loadFeatureFlags( ) ;
496513 }
497514
498- this . #featureFlagRefreshTimer . reset ( ) ;
515+ this . #ffRefreshTimer . reset ( ) ;
499516 return Promise . resolve ( needRefresh ) ;
500517 }
501518
@@ -569,14 +586,29 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
569586 }
570587
571588 async #executeWithFailoverPolicy( funcToExecute : ( client : AppConfigurationClient ) => Promise < any > ) : Promise < any > {
572- const clientWrappers = await this . #clientManager. getClients ( ) ;
589+ let clientWrappers = await this . #clientManager. getClients ( ) ;
590+ if ( this . #options?. loadBalancingEnabled && this . #lastSuccessfulEndpoint !== "" && clientWrappers . length > 1 ) {
591+ let nextClientIndex = 0 ;
592+ // Iterate through clients to find the index of the client with the last successful endpoint
593+ for ( const clientWrapper of clientWrappers ) {
594+ nextClientIndex ++ ;
595+ if ( clientWrapper . endpoint === this . #lastSuccessfulEndpoint) {
596+ break ;
597+ }
598+ }
599+ // If we found the last successful client, rotate the list so that the next client is at the beginning
600+ if ( nextClientIndex < clientWrappers . length ) {
601+ clientWrappers = [ ...clientWrappers . slice ( nextClientIndex ) , ...clientWrappers . slice ( 0 , nextClientIndex ) ] ;
602+ }
603+ }
573604
574605 let successful : boolean ;
575606 for ( const clientWrapper of clientWrappers ) {
576607 successful = false ;
577608 try {
578609 const result = await funcToExecute ( clientWrapper . client ) ;
579610 this . #isFailoverRequest = false ;
611+ this . #lastSuccessfulEndpoint = clientWrapper . endpoint ;
580612 successful = true ;
581613 clientWrapper . updateBackoffStatus ( successful ) ;
582614 return result ;
0 commit comments