Skip to content

Commit 6a8ceb7

Browse files
update
1 parent 326bf46 commit 6a8ceb7

File tree

10 files changed

+188
-52
lines changed

10 files changed

+188
-52
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"dev": "rollup --config --watch",
2626
"lint": "eslint src/ test/",
2727
"fix-lint": "eslint src/ test/ --fix",
28-
"test": "mocha out/test/failover.test.{js,cjs,mjs} --parallel"
28+
"test": "mocha out/test/*.test.{js,cjs,mjs}"
2929
},
3030
"repository": {
3131
"type": "git",

src/AzureAppConfigurationImpl.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AzureAppConfiguration, ConfigurationObjectConstructionOptions } from ".
77
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js";
88
import { IKeyValueAdapter } from "./IKeyValueAdapter.js";
99
import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter.js";
10+
import { DEFAULT_STARTUP_TIMEOUT_IN_MS } from "./StartupOptions.js";
1011
import { DEFAULT_REFRESH_INTERVAL_IN_MS, MIN_REFRESH_INTERVAL_IN_MS } from "./RefreshOptions.js";
1112
import { Disposable } from "./common/disposable.js";
1213
import { base64Helper, jsonSorter } from "./common/utils.js";
@@ -41,7 +42,7 @@ import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOp
4142
import { KeyFilter, LabelFilter, SettingSelector } from "./types.js";
4243
import { ConfigurationClientManager } from "./ConfigurationClientManager.js";
4344
import { getFixedBackoffDuration, calculateDynamicBackoffDuration } from "./failover.js";
44-
import { FailoverError, OperationError, isFailoverableError } from "./error.js";
45+
import { FailoverError, OperationError, isFailoverableError, isRetriableError } from "./error.js";
4546

4647
type PagedSettingSelector = SettingSelector & {
4748
/**
@@ -50,8 +51,6 @@ type PagedSettingSelector = SettingSelector & {
5051
pageEtags?: string[];
5152
};
5253

53-
const DEFAULT_STARTUP_TIMEOUT = 100 * 1000; // 100 seconds in milliseconds
54-
5554
export class AzureAppConfigurationImpl implements AzureAppConfiguration {
5655
/**
5756
* Hosting key-value pairs in the configuration store.
@@ -233,7 +232,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
233232
* Loads the configuration store for the first time.
234233
*/
235234
async load() {
236-
const startupTimeout: number = this.#options?.startupOptions?.timeoutInMs ?? DEFAULT_STARTUP_TIMEOUT;
235+
const startupTimeout: number = this.#options?.startupOptions?.timeoutInMs ?? DEFAULT_STARTUP_TIMEOUT_IN_MS;
237236
const abortController = new AbortController();
238237
const abortSignal = abortController.signal;
239238
let timeoutId;
@@ -344,11 +343,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
344343
/**
345344
* Initializes the configuration provider.
346345
*/
347-
async #initializeWithRetryPolicy(abortSignal: AbortSignal) {
346+
async #initializeWithRetryPolicy(abortSignal: AbortSignal): Promise<void> {
348347
if (!this.#isInitialLoadCompleted) {
349348
await this.#inspectFmPackage();
350349
const startTimestamp = Date.now();
351-
while (!abortSignal.aborted) {
350+
do { // at least try to load once
352351
try {
353352
await this.#loadSelectedAndWatchedKeyValues();
354353
if (this.#featureFlagEnabled) {
@@ -358,6 +357,13 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
358357
this.#isInitialLoadCompleted = true;
359358
break;
360359
} catch (error) {
360+
361+
if (!isRetriableError(error)) {
362+
throw error;
363+
}
364+
if (abortSignal.aborted) {
365+
return;
366+
}
361367
const timeElapsed = Date.now() - startTimestamp;
362368
let postAttempts = 0;
363369
let backoffDuration = getFixedBackoffDuration(timeElapsed);
@@ -368,7 +374,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
368374
await new Promise(resolve => setTimeout(resolve, backoffDuration));
369375
console.warn("Failed to load configuration settings at startup. Retrying...");
370376
}
371-
}
377+
} while (!abortSignal.aborted);
372378
}
373379
}
374380

src/StartupOptions.ts

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

4+
export const DEFAULT_STARTUP_TIMEOUT_IN_MS = 100 * 1000; // 100 seconds in milliseconds
5+
46
export interface StartupOptions {
57
/**
68
* The amount of time allowed to load data from Azure App Configuration on startup.
79
*
810
* @remarks
9-
* If not specified, the default value is 100 seconds. The maximum allowed value is 1 hour.
11+
* If not specified, the default value is 100 seconds. Must be greater than 1 second.
1012
*/
1113
timeoutInMs?: number;
1214
}

src/error.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33

44
import { isRestError } from "@azure/core-rest-pipeline";
55

6+
/**
7+
* Error thrown when an operation is not allowed to be performed.
8+
*/
69
export class OperationError extends Error {
710
constructor(message: string) {
811
super(message);
912
this.name = "OperationError";
1013
}
1114
}
1215

16+
/**
17+
* Error thrown when fail to perform failover.
18+
*/
1319
export class FailoverError extends Error {
1420
constructor(message: string) {
1521
super(message);
@@ -24,9 +30,9 @@ export function isFailoverableError(error: any): boolean {
2430
}
2531

2632
export function isRetriableError(error: any): boolean {
27-
if (error instanceof OperationError ||
33+
if (error instanceof OperationError ||
2834
error instanceof RangeError) {
2935
return false;
3036
}
3137
return true;
32-
}
38+
}

src/refresh/RefreshTimer.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ export class RefreshTimer {
55
#backoffEnd: number; // Timestamp
66
#interval: number;
77

8-
constructor(
9-
interval: number
10-
) {
8+
constructor(interval: number) {
119
if (interval <= 0) {
12-
throw new Error(`Refresh interval must be greater than 0. Given: ${this.#interval}`);
10+
throw new RangeError(`Refresh interval must be greater than 0. Given: ${this.#interval}`);
1311
}
1412

1513
this.#interval = interval;

test/clientOptions.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ describe("custom client options", function () {
4848
policy: countPolicy,
4949
position: "perRetry"
5050
}]
51+
},
52+
startupOptions: {
53+
timeoutInMs: 5_000
5154
}
5255
});
5356
};
@@ -73,6 +76,9 @@ describe("custom client options", function () {
7376
retryOptions: {
7477
maxRetries
7578
}
79+
},
80+
startupOptions: {
81+
timeoutInMs: 5_000
7682
}
7783
});
7884
};
@@ -108,6 +114,9 @@ describe("custom client options", function () {
108114
policy: countPolicy,
109115
position: "perRetry"
110116
}]
117+
},
118+
startupOptions: {
119+
timeoutInMs: 5_000
111120
}
112121
});
113122
};

test/failover.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as chaiAsPromised from "chai-as-promised";
66
chai.use(chaiAsPromised);
77
const expect = chai.expect;
88
import { load } from "./exportedApi";
9-
import { MAX_TIME_OUT, createMockedConnectionString, createMockedFeatureFlag, createMockedKeyValue, mockConfigurationManagerGetClients, restoreMocks, sleepInMs } from "./utils/testHelper";
9+
import { MAX_TIME_OUT, createMockedConnectionString, createMockedFeatureFlag, createMockedKeyValue, mockConfigurationManagerGetClients, restoreMocks } from "./utils/testHelper";
1010
import { getValidDomain, isValidEndpoint } from "../src/ConfigurationClientManager";
1111

1212
const mockedKVs = [{

test/keyvault.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe("key vault reference", function () {
4040
});
4141

4242
it("require key vault options to resolve reference", async () => {
43-
return expect(load(createMockedConnectionString())).eventually.rejectedWith("Configure keyVaultOptions to resolve Key Vault Reference(s).");
43+
return expect(load(createMockedConnectionString())).eventually.rejectedWith("Failed to process the key vault reference. The keyVaultOptions is not configured.");
4444
});
4545

4646
it("should resolve key vault reference with credential", async () => {
@@ -96,7 +96,7 @@ describe("key vault reference", function () {
9696
]
9797
}
9898
});
99-
return expect(loadKeyVaultPromise).eventually.rejectedWith("No key vault credential or secret resolver callback configured, and no matching secret client could be found.");
99+
return expect(loadKeyVaultPromise).eventually.rejectedWith("Failed to process the key vault reference. No key vault credential or secret resolver callback configured, and no matching secret client could be found.");
100100
});
101101

102102
it("should fallback to use default credential when corresponding secret client not provided", async () => {

0 commit comments

Comments
 (0)