Skip to content

Commit 83c3e11

Browse files
no longer reload watched setting when refresh all is triggered (#228)
1 parent 14f96fc commit 83c3e11

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
@@ -53,18 +53,14 @@ import {
5353
} from "./requestTracing/utils.js";
5454
import { FeatureFlagTracingOptions } from "./requestTracing/featureFlagTracingOptions.js";
5555
import { AIConfigurationTracingOptions } from "./requestTracing/aiConfigurationTracingOptions.js";
56-
import { KeyFilter, LabelFilter, SettingSelector } from "./types.js";
56+
import { KeyFilter, LabelFilter, SettingWatcher, SettingSelector, PagedSettingsWatcher, WatchedSetting } from "./types.js";
5757
import { ConfigurationClientManager } from "./configurationClientManager.js";
5858
import { getFixedBackoffDuration, getExponentialBackoffDuration } from "./common/backoffUtils.js";
5959
import { InvalidOperationError, ArgumentError, isFailoverableError, isInputError } from "./common/errors.js";
6060
import { ErrorMessages } from "./common/errorMessages.js";
6161

6262
const MIN_DELAY_FOR_UNHANDLED_FAILURE = 5_000; // 5 seconds
6363

64-
type PagedSettingSelector = SettingSelector & {
65-
pageEtags?: string[];
66-
};
67-
6864
export class AzureAppConfigurationImpl implements AzureAppConfiguration {
6965
/**
7066
* Hosting key-value pairs in the configuration store.
@@ -94,7 +90,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
9490
* Aka watched settings.
9591
*/
9692
#refreshEnabled: boolean = false;
97-
#sentinels: ConfigurationSettingId[] = [];
93+
#sentinels: Map<WatchedSetting, SettingWatcher> = new Map();
9894
#watchAll: boolean = false;
9995
#kvRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
10096
#kvRefreshTimer: RefreshTimer;
@@ -114,11 +110,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
114110
/**
115111
* Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
116112
*/
117-
#kvSelectors: PagedSettingSelector[] = [];
113+
#kvSelectors: PagedSettingsWatcher[] = [];
118114
/**
119115
* Selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
120116
*/
121-
#ffSelectors: PagedSettingSelector[] = [];
117+
#ffSelectors: PagedSettingsWatcher[] = [];
122118

123119
// Load balancing
124120
#lastSuccessfulEndpoint: string = "";
@@ -157,7 +153,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
157153
if (setting.label?.includes("*") || setting.label?.includes(",")) {
158154
throw new ArgumentError(ErrorMessages.INVALID_WATCHED_SETTINGS_LABEL);
159155
}
160-
this.#sentinels.push(setting);
156+
this.#sentinels.set(setting, { etag: undefined });
161157
}
162158
}
163159

@@ -386,7 +382,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
386382
let postAttempts = 0;
387383
do { // at least try to load once
388384
try {
389-
await this.#loadSelectedAndWatchedKeyValues();
385+
if (this.#refreshEnabled && !this.#watchAll) {
386+
await this.#loadWatchedSettings();
387+
}
388+
389+
await this.#loadSelectedKeyValues();
390+
390391
if (this.#featureFlagEnabled) {
391392
await this.#loadFeatureFlags();
392393
}
@@ -486,7 +487,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
486487
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
487488
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
488489
// deep copy selectors to avoid modification if current client fails
489-
const selectorsToUpdate = JSON.parse(
490+
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
490491
JSON.stringify(selectors)
491492
);
492493

@@ -497,22 +498,22 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
497498
labelFilter: selector.labelFilter,
498499
tagsFilter: selector.tagFilters
499500
};
500-
const pageEtags: string[] = [];
501+
const pageWatchers: SettingWatcher[] = [];
501502
const pageIterator = listConfigurationSettingsWithTrace(
502503
this.#requestTraceOptions,
503504
client,
504505
listOptions
505506
).byPage();
506507

507508
for await (const page of pageIterator) {
508-
pageEtags.push(page.etag ?? "");
509+
pageWatchers.push({ etag: page.etag });
509510
for (const setting of page.items) {
510511
if (loadFeatureFlag === isFeatureFlag(setting)) {
511512
loadedSettings.set(setting.key, setting);
512513
}
513514
}
514515
}
515-
selector.pageEtags = pageEtags;
516+
selector.pageWatchers = pageWatchers;
516517
} else { // snapshot selector
517518
const snapshot = await this.#getSnapshot(selector.snapshotName);
518519
if (snapshot === undefined) {
@@ -549,15 +550,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
549550
}
550551

551552
/**
552-
* Loads selected key-values and watched settings (sentinels) for refresh from App Configuration to the local configuration.
553+
* Loads selected key-values from App Configuration to the local configuration.
553554
*/
554-
async #loadSelectedAndWatchedKeyValues() {
555+
async #loadSelectedKeyValues() {
555556
this.#secretReferences = []; // clear all cached key vault reference configuration settings
556557
const keyValues: [key: string, value: unknown][] = [];
557558
const loadedSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings();
558-
if (this.#refreshEnabled && !this.#watchAll) {
559-
await this.#updateWatchedKeyValuesEtag(loadedSettings);
560-
}
561559

562560
if (this.#requestTracingEnabled && this.#aiConfigurationTracing !== undefined) {
563561
// reset old AI configuration tracing in order to track the information present in the current response from server
@@ -587,22 +585,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
587585
}
588586

589587
/**
590-
* 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.
588+
* Loads watched settings (sentinels) for refresh from App Configuration to the local configuration.
591589
*/
592-
async #updateWatchedKeyValuesEtag(existingSettings: ConfigurationSetting[]): Promise<void> {
593-
const updatedSentinels: ConfigurationSettingId[] = [];
594-
for (const sentinel of this.#sentinels) {
595-
const matchedSetting = existingSettings.find(s => s.key === sentinel.key && s.label === sentinel.label);
596-
if (matchedSetting) {
597-
updatedSentinels.push( {...sentinel, etag: matchedSetting.etag} );
598-
} else {
599-
// Send a request to retrieve key-value since it may be either not loaded or loaded with a different label or different casing
600-
const { key, label } = sentinel;
601-
const response = await this.#getConfigurationSetting({ key, label });
602-
updatedSentinels.push( {...sentinel, etag: response?.etag} );
603-
}
590+
async #loadWatchedSettings(): Promise<void> {
591+
for (const watchedSetting of this.#sentinels.keys()) {
592+
const configurationSettingId: ConfigurationSettingId = { key: watchedSetting.key, label: watchedSetting.label };
593+
const response = await this.#getConfigurationSetting(configurationSettingId, { onlyIfChanged: false });
594+
this.#sentinels.set(watchedSetting, { etag: response?.etag });
604595
}
605-
this.#sentinels = updatedSentinels;
606596
}
607597

608598
/**
@@ -649,27 +639,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
649639

650640
// try refresh if any of watched settings is changed.
651641
let needRefresh = false;
642+
let changedSentinel;
643+
let changedSentinelWatcher;
652644
if (this.#watchAll) {
653645
needRefresh = await this.#checkConfigurationSettingsChange(this.#kvSelectors);
654-
}
655-
for (const sentinel of this.#sentinels.values()) {
656-
const response = await this.#getConfigurationSetting(sentinel, {
657-
onlyIfChanged: true
658-
});
659-
660-
if (response?.statusCode === 200 // created or changed
661-
|| (response === undefined && sentinel.etag !== undefined) // deleted
662-
) {
663-
needRefresh = true;
664-
break;
646+
} else {
647+
for (const watchedSetting of this.#sentinels.keys()) {
648+
const configurationSettingId: ConfigurationSettingId = { key: watchedSetting.key, label: watchedSetting.label, etag: this.#sentinels.get(watchedSetting)?.etag };
649+
const response = await this.#getConfigurationSetting(configurationSettingId, {
650+
onlyIfChanged: true
651+
});
652+
653+
const watcher = this.#sentinels.get(watchedSetting);
654+
if (response?.statusCode === 200 // created or changed
655+
|| (response === undefined && watcher?.etag !== undefined) // deleted
656+
) {
657+
changedSentinel = watchedSetting;
658+
changedSentinelWatcher = watcher;
659+
needRefresh = true;
660+
break;
661+
}
665662
}
666663
}
667664

668665
if (needRefresh) {
669666
for (const adapter of this.#adapters) {
670667
await adapter.onChangeDetected();
671668
}
672-
await this.#loadSelectedAndWatchedKeyValues();
669+
await this.#loadSelectedKeyValues();
670+
this.#sentinels.set(changedSentinel, changedSentinelWatcher); // update the changed sentinel's watcher
673671
}
674672

675673
this.#kvRefreshTimer.reset();
@@ -719,17 +717,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
719717
* @param selectors - The @see PagedSettingSelector of the kev-value collection.
720718
* @returns true if key-value collection has changed, false otherwise.
721719
*/
722-
async #checkConfigurationSettingsChange(selectors: PagedSettingSelector[]): Promise<boolean> {
720+
async #checkConfigurationSettingsChange(selectors: PagedSettingsWatcher[]): Promise<boolean> {
723721
const funcToExecute = async (client) => {
724722
for (const selector of selectors) {
725723
if (selector.snapshotName) { // skip snapshot selector
726724
continue;
727725
}
726+
const pageWatchers: SettingWatcher[] = selector.pageWatchers ?? [];
728727
const listOptions: ListConfigurationSettingsOptions = {
729728
keyFilter: selector.keyFilter,
730729
labelFilter: selector.labelFilter,
731730
tagsFilter: selector.tagFilters,
732-
pageEtags: selector.pageEtags
731+
pageEtags: pageWatchers.map(w => w.etag ?? "")
733732
};
734733

735734
const pageIterator = listConfigurationSettingsWithTrace(
@@ -739,6 +738,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
739738
).byPage();
740739

741740
for await (const page of pageIterator) {
741+
// when conditional request is sent, the response will be 304 if not changed
742742
if (page._response.status === 200) { // created or changed
743743
return true;
744744
}

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)