Skip to content

Commit 0d295af

Browse files
committed
resolve conflicts
1 parent 22f80fd commit 0d295af

14 files changed

+338
-165
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 127 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { RefreshTimer } from "./refresh/RefreshTimer.js";
1515
import { getConfigurationSettingWithTrace, listConfigurationSettingsWithTrace, requestTracingEnabled } from "./requestTracing/utils.js";
1616
import { KeyFilter, LabelFilter, SettingSelector } from "./types.js";
1717
import { ConfigurationClientManager } from "./ConfigurationClientManager.js";
18+
import { updateClientBackoffStatus } from "./ConfigurationClientWrapper.js";
1819

1920
type PagedSettingSelector = SettingSelector & {
2021
/**
@@ -41,6 +42,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
4142
#clientEndpoint: string | undefined;
4243
#options: AzureAppConfigurationOptions | undefined;
4344
#isInitialLoadCompleted: boolean = false;
45+
#isFailoverRequest: boolean = false;
4446

4547
// Refresh
4648
#refreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
@@ -59,13 +61,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
5961
#featureFlagSelectors: PagedSettingSelector[] = [];
6062

6163
constructor(
62-
client: AppConfigurationClient,
63-
clientEndpoint: string | undefined,
64-
options: AzureAppConfigurationOptions | undefined
64+
clientManager: ConfigurationClientManager,
65+
options: AzureAppConfigurationOptions | undefined,
6566
) {
66-
this.#client = client;
67-
this.#clientEndpoint = clientEndpoint;
6867
this.#options = options;
68+
this.#clientManager = clientManager;
6969

7070
// Enable request tracing if not opt-out
7171
this.#requestTracingEnabled = options?.requestTracingOptions?.enabled ?? requestTracingEnabled();
@@ -178,34 +178,70 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
178178
return {
179179
requestTracingEnabled: this.#requestTracingEnabled,
180180
initialLoadCompleted: this.#isInitialLoadCompleted,
181-
appConfigOptions: this.#options
181+
appConfigOptions: this.#options,
182+
isFailoverRequest: this.#isFailoverRequest
182183
};
183184
}
184185

185-
async #loadSelectedKeyValues(): Promise<ConfigurationSetting[]> {
186-
const loadedSettings: ConfigurationSetting[] = [];
186+
async #executeWithFailoverPolicy(funcToExecute) {
187+
const clients = await this.#clientManager.getClients();
188+
if (clients.length === 0) {
189+
this.#clientManager.refreshClients();
190+
throw new Error("No client is available to connect to the target App Configuration store.");
191+
}
187192

188-
// validate selectors
189-
const selectors = getValidKeyValueSelectors(this.#options?.selectors);
193+
for (const client of clients) {
194+
let successful = false;
195+
try {
196+
const result = await funcToExecute(client.client);
197+
this.#isFailoverRequest = false;
198+
successful = true;
199+
updateClientBackoffStatus(client, successful);
200+
return result;
201+
} catch (error) {
202+
if (isFailoverableError(error)) {
203+
updateClientBackoffStatus(client, successful);
204+
this.#isFailoverRequest = true;
205+
continue;
206+
}
190207

191-
for (const selector of selectors) {
192-
const listOptions: ListConfigurationSettingsOptions = {
193-
keyFilter: selector.keyFilter,
194-
labelFilter: selector.labelFilter
195-
};
208+
throw error;
209+
}
210+
}
196211

197-
const settings = listConfigurationSettingsWithTrace(
198-
this.#requestTraceOptions,
199-
this.#client,
200-
listOptions
201-
);
212+
this.#clientManager.refreshClients();
213+
throw new Error("All app configuration clients failed to get settings.");
214+
}
202215

203-
for await (const setting of settings) {
204-
if (!isFeatureFlag(setting)) { // exclude feature flags
205-
loadedSettings.push(setting);
216+
async #loadSelectedKeyValues(): Promise<ConfigurationSetting[]> {
217+
// validate selectors
218+
const selectors = getValidKeyValueSelectors(this.#options?.selectors);
219+
let loadedSettings: ConfigurationSetting[] = [];
220+
221+
const funcToExecute = async (client) => {
222+
const loadedSettings: ConfigurationSetting[] = [];
223+
for (const selector of selectors) {
224+
const listOptions: ListConfigurationSettingsOptions = {
225+
keyFilter: selector.keyFilter,
226+
labelFilter: selector.labelFilter
227+
};
228+
229+
const settings = listConfigurationSettingsWithTrace(
230+
this.#requestTraceOptions,
231+
client,
232+
listOptions
233+
);
234+
235+
for await (const setting of settings) {
236+
if (!isFeatureFlag(setting)) { // exclude feature flags
237+
loadedSettings.push(setting);
238+
}
206239
}
207240
}
208-
}
241+
return loadedSettings;
242+
};
243+
244+
loadedSettings = await this.#executeWithFailoverPolicy(funcToExecute);
209245
return loadedSettings;
210246
}
211247

@@ -260,30 +296,43 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
260296
}
261297

262298
async #loadFeatureFlags() {
263-
const featureFlagSettings: ConfigurationSetting[] = [];
264-
for (const selector of this.#featureFlagSelectors) {
265-
const listOptions: ListConfigurationSettingsOptions = {
266-
keyFilter: `${featureFlagPrefix}${selector.keyFilter}`,
267-
labelFilter: selector.labelFilter
268-
};
299+
// Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
300+
const funcToExecute = async (client) => {
301+
const featureFlagSettings: ConfigurationSetting[] = [];
302+
const selectors = JSON.parse(
303+
JSON.stringify(this.#featureFlagSelectors)
304+
);
269305

270-
const pageEtags: string[] = [];
271-
const pageIterator = listConfigurationSettingsWithTrace(
272-
this.#requestTraceOptions,
273-
this.#client,
274-
listOptions
275-
).byPage();
276-
for await (const page of pageIterator) {
277-
pageEtags.push(page.etag ?? "");
278-
for (const setting of page.items) {
279-
if (isFeatureFlag(setting)) {
280-
featureFlagSettings.push(setting);
306+
for (const selector of selectors) {
307+
const listOptions: ListConfigurationSettingsOptions = {
308+
keyFilter: `${featureFlagPrefix}${selector.keyFilter}`,
309+
labelFilter: selector.labelFilter
310+
};
311+
312+
const pageEtags: string[] = [];
313+
const pageIterator = listConfigurationSettingsWithTrace(
314+
this.#requestTraceOptions,
315+
client,
316+
listOptions
317+
).byPage();
318+
for await (const page of pageIterator) {
319+
pageEtags.push(page.etag ?? "");
320+
for (const setting of page.items) {
321+
if (isFeatureFlag(setting)) {
322+
featureFlagSettings.push(setting);
323+
}
281324
}
282325
}
326+
selector.pageEtags = pageEtags;
283327
}
284-
selector.pageEtags = pageEtags;
328+
329+
this.#featureFlagSelectors = selectors;
330+
return featureFlagSettings;
285331
}
286332

333+
let featureFlagSettings: ConfigurationSetting[] = [];
334+
featureFlagSettings = await this.#executeWithFailoverPolicy(funcToExecute);
335+
287336
// parse feature flags
288337
const featureFlags = await Promise.all(
289338
featureFlagSettings.map(setting => this.#parseFeatureFlag(setting))
@@ -435,30 +484,32 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
435484
}
436485

437486
// check if any feature flag is changed
438-
let needRefresh = false;
439-
for (const selector of this.#featureFlagSelectors) {
440-
const listOptions: ListConfigurationSettingsOptions = {
441-
keyFilter: `${featureFlagPrefix}${selector.keyFilter}`,
442-
labelFilter: selector.labelFilter,
443-
pageEtags: selector.pageEtags
444-
};
445-
const pageIterator = listConfigurationSettingsWithTrace(
446-
this.#requestTraceOptions,
447-
this.#client,
448-
listOptions
449-
).byPage();
450-
451-
for await (const page of pageIterator) {
452-
if (page._response.status === 200) { // created or changed
453-
needRefresh = true;
454-
break;
487+
const funcToExecute = async (client) => {
488+
const needRefresh = false;
489+
for (const selector of this.#featureFlagSelectors) {
490+
const listOptions: ListConfigurationSettingsOptions = {
491+
keyFilter: `${featureFlagPrefix}${selector.keyFilter}`,
492+
labelFilter: selector.labelFilter,
493+
pageEtags: selector.pageEtags
494+
};
495+
496+
const pageIterator = listConfigurationSettingsWithTrace(
497+
this.#requestTraceOptions,
498+
client,
499+
listOptions
500+
).byPage();
501+
502+
for await (const page of pageIterator) {
503+
if (page._response.status === 200) { // created or changed
504+
return true;
505+
}
455506
}
456507
}
457-
458-
if (needRefresh) {
459-
break; // short-circuit if result from any of the selectors is changed
460-
}
461-
}
508+
return needRefresh;
509+
};
510+
511+
let needRefresh: boolean;
512+
needRefresh = await this.#executeWithFailoverPolicy(funcToExecute);
462513

463514
if (needRefresh) {
464515
try {
@@ -521,14 +572,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
521572
* Get a configuration setting by key and label. If the setting is not found, return undefine instead of throwing an error.
522573
*/
523574
async #getConfigurationSetting(configurationSettingId: ConfigurationSettingId, customOptions?: GetConfigurationSettingOptions): Promise<GetConfigurationSettingResponse | undefined> {
524-
let response: GetConfigurationSettingResponse | undefined;
525-
try {
526-
response = await getConfigurationSettingWithTrace(
575+
const funcToExecute = async (client) => {
576+
return getConfigurationSettingWithTrace(
527577
this.#requestTraceOptions,
528-
this.#client,
578+
client,
529579
configurationSettingId,
530580
customOptions
531581
);
582+
};
583+
584+
let response: GetConfigurationSettingResponse | undefined;
585+
try {
586+
response = await this.#executeWithFailoverPolicy(funcToExecute);
532587
} catch (error) {
533588
if (isRestError(error) && error.statusCode === 404) {
534589
response = undefined;
@@ -659,3 +714,7 @@ function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSel
659714
return getValidSelectors(selectors);
660715
}
661716
}
717+
718+
function isFailoverableError(error: any): boolean {
719+
return isRestError(error) && (error.statusCode === 408 || error.statusCode === 429 || (error.statusCode !== undefined && error.statusCode >= 500));
720+
}

0 commit comments

Comments
 (0)