From 09fae65216b83fef12898369a6c8d2c384baef37 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Thu, 14 Dec 2023 15:19:31 +0800 Subject: [PATCH 1/4] Add error handling delay in load function --- src/load.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/load.ts b/src/load.ts index a44d4042..c72abc1a 100644 --- a/src/load.ts +++ b/src/load.ts @@ -8,6 +8,8 @@ import { AzureAppConfigurationImpl } from "./AzureAppConfigurationImpl"; import { AzureAppConfigurationOptions, MaxRetries, MaxRetryDelayInMs } from "./AzureAppConfigurationOptions"; import * as RequestTracing from "./requestTracing/constants"; +const MinDelayForUnhandedError: number = 5000; // 5 seconds + /** * Loads the data from Azure App Configuration service and returns an instance of AzureAppConfiguration. * @param connectionString The connection string for the App Configuration store. @@ -25,6 +27,26 @@ export async function load( connectionStringOrEndpoint: string | URL, credentialOrOptions?: TokenCredential | AzureAppConfigurationOptions, appConfigOptions?: AzureAppConfigurationOptions +): Promise { + const startTimestamp = Date.now(); + try { + return await loadPromise(connectionStringOrEndpoint, credentialOrOptions, appConfigOptions); + } catch (error) { + // load() method is called in the application's startup code path. + // Unhandled exceptions cause application crash which can result in crash loops as orchestrators attempt to restart the application. + // 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. + const delay = MinDelayForUnhandedError - (Date.now() - startTimestamp); + if (delay > 0) { + await new Promise((resolve) => setTimeout(resolve, delay)); + } + throw error; + } +} + +async function loadPromise( + connectionStringOrEndpoint: string | URL, + credentialOrOptions?: TokenCredential | AzureAppConfigurationOptions, + appConfigOptions?: AzureAppConfigurationOptions ): Promise { let client: AppConfigurationClient; let options: AzureAppConfigurationOptions | undefined; From 365e1187be4560f8ab4a4d3e182bcceef01d7a74 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Thu, 14 Dec 2023 15:23:05 +0800 Subject: [PATCH 2/4] Fix typo in comment --- src/load.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/load.ts b/src/load.ts index c72abc1a..0493d99c 100644 --- a/src/load.ts +++ b/src/load.ts @@ -34,7 +34,7 @@ export async function load( } catch (error) { // load() method is called in the application's startup code path. // Unhandled exceptions cause application crash which can result in crash loops as orchestrators attempt to restart the application. - // 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. + // 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 propagate fatal errors. const delay = MinDelayForUnhandedError - (Date.now() - startTimestamp); if (delay > 0) { await new Promise((resolve) => setTimeout(resolve, delay)); From 1020a06a31811b1f924a9fb2407fe85913d2f6de Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Thu, 14 Dec 2023 15:27:44 +0800 Subject: [PATCH 3/4] Increase test timeout to 15000 milliseconds --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74eca211..0bf89104 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dev": "rollup --config --watch", "lint": "eslint src/ test/", "fix-lint": "eslint src/ test/ --fix", - "test": "mocha out/test/*.test.{js,cjs,mjs} --timeout 10000" + "test": "mocha out/test/*.test.{js,cjs,mjs} --timeout 15000" }, "repository": { "type": "git", From ce47f52bb92f79dfce49b3ca673afcb627fc0867 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Fri, 15 Dec 2023 09:48:16 +0800 Subject: [PATCH 4/4] throw exception directly during input validation --- src/load.ts | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/load.ts b/src/load.ts index 0493d99c..a7007136 100644 --- a/src/load.ts +++ b/src/load.ts @@ -29,27 +29,10 @@ export async function load( appConfigOptions?: AzureAppConfigurationOptions ): Promise { const startTimestamp = Date.now(); - try { - return await loadPromise(connectionStringOrEndpoint, credentialOrOptions, appConfigOptions); - } catch (error) { - // load() method is called in the application's startup code path. - // Unhandled exceptions cause application crash which can result in crash loops as orchestrators attempt to restart the application. - // 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 propagate fatal errors. - const delay = MinDelayForUnhandedError - (Date.now() - startTimestamp); - if (delay > 0) { - await new Promise((resolve) => setTimeout(resolve, delay)); - } - throw error; - } -} - -async function loadPromise( - connectionStringOrEndpoint: string | URL, - credentialOrOptions?: TokenCredential | AzureAppConfigurationOptions, - appConfigOptions?: AzureAppConfigurationOptions -): Promise { let client: AppConfigurationClient; let options: AzureAppConfigurationOptions | undefined; + + // input validation if (typeof connectionStringOrEndpoint === "string" && !instanceOfTokenCredential(credentialOrOptions)) { const connectionString = connectionStringOrEndpoint; options = credentialOrOptions as AzureAppConfigurationOptions; @@ -77,9 +60,20 @@ async function loadPromise( throw new Error("A connection string or an endpoint with credential must be specified to create a client."); } - const appConfiguration = new AzureAppConfigurationImpl(client, options); - await appConfiguration.load(); - return appConfiguration; + try { + const appConfiguration = new AzureAppConfigurationImpl(client, options); + await appConfiguration.load(); + return appConfiguration; + } catch (error) { + // load() method is called in the application's startup code path. + // Unhandled exceptions cause application crash which can result in crash loops as orchestrators attempt to restart the application. + // 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 propagate fatal errors. + const delay = MinDelayForUnhandedError - (Date.now() - startTimestamp); + if (delay > 0) { + await new Promise((resolve) => setTimeout(resolve, delay)); + } + throw error; + } } function instanceOfTokenCredential(obj: unknown) {