Skip to content

Commit fe9ad2f

Browse files
add boot loop protection
1 parent 39d9a3d commit fe9ad2f

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOp
4242
import { KeyFilter, LabelFilter, SettingSelector } from "./types.js";
4343
import { ConfigurationClientManager } from "./ConfigurationClientManager.js";
4444
import { getFixedBackoffDuration, calculateBackoffDuration } from "./failover.js";
45-
import { OperationError, ArgumentError, isFailoverableError, isRetriableError } from "./error.js";
45+
import { OperationError, ArgumentError, isFailoverableError, isRetriableError, isInstantlyThrowError } from "./error.js";
46+
47+
const MIN_DELAY_FOR_UNHANDLED_FAILURE = 5_000; // 5 seconds
4648

4749
type PagedSettingSelector = SettingSelector & {
4850
/**
@@ -232,6 +234,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
232234
* Loads the configuration store for the first time.
233235
*/
234236
async load() {
237+
const startTimestamp = Date.now();
235238
const startupTimeout: number = this.#options?.startupOptions?.timeoutInMs ?? DEFAULT_STARTUP_TIMEOUT_IN_MS;
236239
const abortController = new AbortController();
237240
const abortSignal = abortController.signal;
@@ -252,6 +255,15 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
252255
})
253256
]);
254257
} catch (error) {
258+
if (!isInstantlyThrowError(error)) {
259+
const timeElapsed = Date.now() - startTimestamp;
260+
if (timeElapsed < MIN_DELAY_FOR_UNHANDLED_FAILURE) {
261+
// load() method is called in the application's startup code path.
262+
// Unhandled exceptions cause application crash which can result in crash loops as orchestrators attempt to restart the application.
263+
// Knowing the intended usage of the provider in startup code path, we mitigate back-to-back crash loops from overloading the server with requests by waiting a minimum time to propogate fatal errors.
264+
await new Promise(resolve => setTimeout(resolve, MIN_DELAY_FOR_UNHANDLED_FAILURE - timeElapsed));
265+
}
266+
}
255267
throw new Error(`Failed to load: ${error.message}`);
256268
} finally {
257269
clearTimeout(timeoutId); // cancel the timeout promise

src/error.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,12 @@ export function isRetriableError(error: any): boolean {
5959
}
6060
return true;
6161
}
62+
63+
export function isInstantlyThrowError(error: any): boolean {
64+
if (error instanceof ArgumentError ||
65+
error instanceof TypeError ||
66+
error instanceof RangeError) {
67+
return true;
68+
}
69+
return false;
70+
}

0 commit comments

Comments
 (0)