Skip to content

Commit 7ead0bc

Browse files
Merge pull request #229 from Azure/merge-main-to-preview
Merge main to preview
2 parents 8f75608 + 4121c87 commit 7ead0bc

File tree

6 files changed

+99
-86
lines changed

6 files changed

+99
-86
lines changed

src/appConfigurationImpl.ts

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,14 @@ import {
6161
} from "./requestTracing/utils.js";
6262
import { FeatureFlagTracingOptions } from "./requestTracing/featureFlagTracingOptions.js";
6363
import { AIConfigurationTracingOptions } from "./requestTracing/aiConfigurationTracingOptions.js";
64-
import { KeyFilter, LabelFilter, SettingSelector } from "./types.js";
64+
import { KeyFilter, LabelFilter, SettingWatcher, SettingSelector, PagedSettingsWatcher, WatchedSetting } from "./types.js";
6565
import { ConfigurationClientManager } from "./configurationClientManager.js";
6666
import { getFixedBackoffDuration, getExponentialBackoffDuration } from "./common/backoffUtils.js";
6767
import { InvalidOperationError, ArgumentError, isFailoverableError, isInputError } from "./common/errors.js";
6868
import { ErrorMessages } from "./common/errorMessages.js";
6969

7070
const MIN_DELAY_FOR_UNHANDLED_FAILURE = 5_000; // 5 seconds
7171

72-
type PagedSettingSelector = SettingSelector & {
73-
pageEtags?: string[];
74-
};
75-
7672
export class AzureAppConfigurationImpl implements AzureAppConfiguration {
7773
/**
7874
* Hosting key-value pairs in the configuration store.
@@ -102,7 +98,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
10298
* Aka watched settings.
10399
*/
104100
#refreshEnabled: boolean = false;
105-
#sentinels: ConfigurationSettingId[] = [];
101+
#sentinels: Map<WatchedSetting, SettingWatcher> = new Map();
106102
#watchAll: boolean = false;
107103
#kvRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
108104
#kvRefreshTimer: RefreshTimer;
@@ -122,11 +118,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
122118
/**
123119
* Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
124120
*/
125-
#kvSelectors: PagedSettingSelector[] = [];
121+
#kvSelectors: PagedSettingsWatcher[] = [];
126122
/**
127123
* Selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
128124
*/
129-
#ffSelectors: PagedSettingSelector[] = [];
125+
#ffSelectors: PagedSettingsWatcher[] = [];
130126

131127
// Load balancing
132128
#lastSuccessfulEndpoint: string = "";
@@ -165,7 +161,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
165161
if (setting.label?.includes("*") || setting.label?.includes(",")) {
166162
throw new ArgumentError(ErrorMessages.INVALID_WATCHED_SETTINGS_LABEL);
167163
}
168-
this.#sentinels.push(setting);
164+
this.#sentinels.set(setting, { etag: undefined });
169165
}
170166
}
171167

@@ -394,7 +390,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
394390
let postAttempts = 0;
395391
do { // at least try to load once
396392
try {
397-
await this.#loadSelectedAndWatchedKeyValues();
393+
if (this.#refreshEnabled && !this.#watchAll) {
394+
await this.#loadWatchedSettings();
395+
}
396+
397+
await this.#loadSelectedKeyValues();
398+
398399
if (this.#featureFlagEnabled) {
399400
await this.#loadFeatureFlags();
400401
}
@@ -494,7 +495,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
494495
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
495496
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
496497
// deep copy selectors to avoid modification if current client fails
497-
const selectorsToUpdate = JSON.parse(
498+
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
498499
JSON.stringify(selectors)
499500
);
500501

@@ -505,22 +506,22 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
505506
labelFilter: selector.labelFilter,
506507
tagsFilter: selector.tagFilters
507508
};
508-
const pageEtags: string[] = [];
509+
const pageWatchers: SettingWatcher[] = [];
509510
const pageIterator = listConfigurationSettingsWithTrace(
510511
this.#requestTraceOptions,
511512
client,
512513
listOptions
513514
).byPage();
514515

515516
for await (const page of pageIterator) {
516-
pageEtags.push(page.etag ?? "");
517+
pageWatchers.push({ etag: page.etag });
517518
for (const setting of page.items) {
518519
if (loadFeatureFlag === isFeatureFlag(setting)) {
519520
loadedSettings.set(setting.key, setting);
520521
}
521522
}
522523
}
523-
selector.pageEtags = pageEtags;
524+
selector.pageWatchers = pageWatchers;
524525
} else { // snapshot selector
525526
const snapshot = await this.#getSnapshot(selector.snapshotName);
526527
if (snapshot === undefined) {
@@ -557,15 +558,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
557558
}
558559

559560
/**
560-
* Loads selected key-values and watched settings (sentinels) for refresh from App Configuration to the local configuration.
561+
* Loads selected key-values from App Configuration to the local configuration.
561562
*/
562-
async #loadSelectedAndWatchedKeyValues() {
563+
async #loadSelectedKeyValues() {
563564
this.#secretReferences = []; // clear all cached key vault reference configuration settings
564565
const keyValues: [key: string, value: unknown][] = [];
565566
const loadedSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings();
566-
if (this.#refreshEnabled && !this.#watchAll) {
567-
await this.#updateWatchedKeyValuesEtag(loadedSettings);
568-
}
569567

570568
if (this.#requestTracingEnabled && this.#aiConfigurationTracing !== undefined) {
571569
// reset old AI configuration tracing in order to track the information present in the current response from server
@@ -595,22 +593,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
595593
}
596594

597595
/**
598-
* Updates etag of watched settings from loaded data. If a watched setting is not covered by any selector, a request will be sent to retrieve it.
596+
* Loads watched settings (sentinels) for refresh from App Configuration to the local configuration.
599597
*/
600-
async #updateWatchedKeyValuesEtag(existingSettings: ConfigurationSetting[]): Promise<void> {
601-
const updatedSentinels: ConfigurationSettingId[] = [];
602-
for (const sentinel of this.#sentinels) {
603-
const matchedSetting = existingSettings.find(s => s.key === sentinel.key && s.label === sentinel.label);
604-
if (matchedSetting) {
605-
updatedSentinels.push( {...sentinel, etag: matchedSetting.etag} );
606-
} else {
607-
// Send a request to retrieve key-value since it may be either not loaded or loaded with a different label or different casing
608-
const { key, label } = sentinel;
609-
const response = await this.#getConfigurationSetting({ key, label });
610-
updatedSentinels.push( {...sentinel, etag: response?.etag} );
611-
}
598+
async #loadWatchedSettings(): Promise<void> {
599+
for (const watchedSetting of this.#sentinels.keys()) {
600+
const configurationSettingId: ConfigurationSettingId = { key: watchedSetting.key, label: watchedSetting.label };
601+
const response = await this.#getConfigurationSetting(configurationSettingId, { onlyIfChanged: false });
602+
this.#sentinels.set(watchedSetting, { etag: response?.etag });
612603
}
613-
this.#sentinels = updatedSentinels;
614604
}
615605

616606
/**
@@ -657,27 +647,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
657647

658648
// try refresh if any of watched settings is changed.
659649
let needRefresh = false;
650+
let changedSentinel;
651+
let changedSentinelWatcher;
660652
if (this.#watchAll) {
661653
needRefresh = await this.#checkConfigurationSettingsChange(this.#kvSelectors);
662-
}
663-
for (const sentinel of this.#sentinels.values()) {
664-
const response = await this.#getConfigurationSetting(sentinel, {
665-
onlyIfChanged: true
666-
});
667-
668-
if (response?.statusCode === 200 // created or changed
669-
|| (response === undefined && sentinel.etag !== undefined) // deleted
670-
) {
671-
needRefresh = true;
672-
break;
654+
} else {
655+
for (const watchedSetting of this.#sentinels.keys()) {
656+
const configurationSettingId: ConfigurationSettingId = { key: watchedSetting.key, label: watchedSetting.label, etag: this.#sentinels.get(watchedSetting)?.etag };
657+
const response = await this.#getConfigurationSetting(configurationSettingId, {
658+
onlyIfChanged: true
659+
});
660+
661+
const watcher = this.#sentinels.get(watchedSetting);
662+
if (response?.statusCode === 200 // created or changed
663+
|| (response === undefined && watcher?.etag !== undefined) // deleted
664+
) {
665+
changedSentinel = watchedSetting;
666+
changedSentinelWatcher = watcher;
667+
needRefresh = true;
668+
break;
669+
}
673670
}
674671
}
675672

676673
if (needRefresh) {
677674
for (const adapter of this.#adapters) {
678675
await adapter.onChangeDetected();
679676
}
680-
await this.#loadSelectedAndWatchedKeyValues();
677+
await this.#loadSelectedKeyValues();
678+
this.#sentinels.set(changedSentinel, changedSentinelWatcher); // update the changed sentinel's watcher
681679
}
682680

683681
this.#kvRefreshTimer.reset();
@@ -727,17 +725,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
727725
* @param selectors - The @see PagedSettingSelector of the kev-value collection.
728726
* @returns true if key-value collection has changed, false otherwise.
729727
*/
730-
async #checkConfigurationSettingsChange(selectors: PagedSettingSelector[]): Promise<boolean> {
728+
async #checkConfigurationSettingsChange(selectors: PagedSettingsWatcher[]): Promise<boolean> {
731729
const funcToExecute = async (client) => {
732730
for (const selector of selectors) {
733731
if (selector.snapshotName) { // skip snapshot selector
734732
continue;
735733
}
734+
const pageWatchers: SettingWatcher[] = selector.pageWatchers ?? [];
736735
const listOptions: ListConfigurationSettingsOptions = {
737736
keyFilter: selector.keyFilter,
738737
labelFilter: selector.labelFilter,
739738
tagsFilter: selector.tagFilters,
740-
pageEtags: selector.pageEtags
739+
pageEtags: pageWatchers.map(w => w.etag ?? "")
741740
};
742741

743742
const pageIterator = listConfigurationSettingsWithTrace(
@@ -747,6 +746,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
747746
).byPage();
748747

749748
for await (const page of pageIterator) {
749+
// when conditional request is sent, the response will be 304 if not changed
750750
if (page._response.status === 200) { // created or changed
751751
return true;
752752
}

src/refresh/refreshOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { WatchedSetting } from "../watchedSetting.js";
4+
import { WatchedSetting } from "../types.js";
55

66
export const DEFAULT_REFRESH_INTERVAL_IN_MS = 30_000;
77
export const MIN_REFRESH_INTERVAL_IN_MS = 1_000;

src/types.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export enum KeyFilter {
5858
* Matches all key-values.
5959
*/
6060
Any = "*"
61-
}
61+
};
6262

6363
/**
6464
* LabelFilter is used to filter key-values based on labels.
@@ -68,7 +68,7 @@ export enum LabelFilter {
6868
* Matches key-values without a label.
6969
*/
7070
Null = "\0"
71-
}
71+
};
7272

7373
/**
7474
* TagFilter is used to filter key-values based on tags.
@@ -78,4 +78,25 @@ export enum TagFilter {
7878
* Represents empty tag value.
7979
*/
8080
Null = ""
81+
};
82+
83+
export type WatchedSetting = {
84+
/**
85+
* The key for this setting.
86+
*/
87+
key: string;
88+
89+
/**
90+
* The label for this setting.
91+
* Leaving this undefined means this setting does not have a label.
92+
*/
93+
label?: string;
8194
}
95+
96+
export type SettingWatcher = {
97+
etag?: string;
98+
}
99+
100+
export type PagedSettingsWatcher = SettingSelector & {
101+
pageWatchers?: SettingWatcher[]
102+
};

src/watchedSetting.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)