From ae251630c36503c0d2f1de1b617b221429ffd9e2 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 18 Nov 2025 15:33:28 -0500 Subject: [PATCH 1/2] add env var to configure ffe init time --- index.d.ts | 2 + packages/dd-trace/src/config.js | 10 + .../src/supported-configurations.json | 2 + .../flagging_provider_timeout.spec.js | 193 ++++++++++++++++++ 4 files changed, 207 insertions(+) diff --git a/index.d.ts b/index.d.ts index 4a6e09c9e04..59667b25807 100644 --- a/index.d.ts +++ b/index.d.ts @@ -657,6 +657,8 @@ declare namespace tracer { /** * Timeout in milliseconds for OpenFeature provider initialization. * If configuration is not received within this time, initialization fails. + * Can be configured via DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS or + * DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS environment variables. * * @default 30000 */ diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index b20636224d9..8446c482004 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -567,6 +567,8 @@ class Config { OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, + DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS, + DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL, @@ -698,6 +700,9 @@ class Config { unprocessedTarget['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS this.#setString(target, 'env', DD_ENV || tags.env) this.#setBoolean(target, 'experimental.flaggingProvider.enabled', DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED) + target['experimental.flaggingProvider.initializationTimeoutMs'] = maybeInt( + DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS || DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + ) this.#setBoolean(target, 'traceEnabled', DD_TRACE_ENABLED) this.#setBoolean(target, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED) this.#setString(target, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT) @@ -1012,6 +1017,11 @@ class Config { this.#setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData) this.#setString(opts, 'experimental.exporter', options.experimental?.exporter) this.#setBoolean(opts, 'experimental.flaggingProvider.enabled', options.experimental?.flaggingProvider?.enabled) + opts['experimental.flaggingProvider.initializationTimeoutMs'] = maybeInt( + options.experimental?.flaggingProvider?.initializationTimeoutMs + ) + this.#optsUnprocessed['experimental.flaggingProvider.initializationTimeoutMs'] = + options.experimental?.flaggingProvider?.initializationTimeoutMs opts.flushInterval = maybeInt(options.flushInterval) this.#optsUnprocessed.flushInterval = options.flushInterval opts.flushMinSpans = maybeInt(options.flushMinSpans) diff --git a/packages/dd-trace/src/supported-configurations.json b/packages/dd-trace/src/supported-configurations.json index f91ebd73507..3c42fd91b53 100644 --- a/packages/dd-trace/src/supported-configurations.json +++ b/packages/dd-trace/src/supported-configurations.json @@ -70,10 +70,12 @@ "DD_ENV": ["A"], "DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED": ["A"], "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED": ["A"], + "DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS": ["A"], "DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_ENABLED": ["A"], "DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_DIR": ["A"], "DD_EXTERNAL_ENV": ["A"], "DD_FLAGGING_PROVIDER_ENABLED": ["A"], + "DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS": ["A"], "DD_GIT_BRANCH": ["A"], "DD_GIT_COMMIT_AUTHOR_DATE": ["A"], "DD_GIT_COMMIT_AUTHOR_EMAIL": ["A"], diff --git a/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js b/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js index a5a7167fb84..e40078f0824 100644 --- a/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js +++ b/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js @@ -256,4 +256,197 @@ describe('FlaggingProvider Initialization Timeout', () => { expect(errorArg.message).to.include('10000ms') }) }) + + describe('environment variable timeout configuration', () => { + let originalEnv + + beforeEach(() => { + // Save original environment variables + originalEnv = { + DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS: process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS, + DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS: + process.env.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + } + }) + + afterEach(() => { + // Restore original environment variables + if (originalEnv.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS !== undefined) { + process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = + originalEnv.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + } else { + delete process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + } + + if (originalEnv.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS !== undefined) { + process.env.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = + originalEnv.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + } else { + delete process.env.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + } + }) + + it('should use DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS environment variable', async () => { + // Set environment variable for 8-second timeout + process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '8000' + + // Config without timeout (should use env var) + const configWithoutTimeout = { + ...mockConfig, + experimental: { + flaggingProvider: { + enabled: true + // initializationTimeoutMs not specified - should use env var + } + } + } + + // Need to reload the config module to pick up env var + delete require.cache[require.resolve('../../src/config')] + const Config = require('../../src/config') + const config = new Config({}) + + const provider = new FlaggingProvider(mockTracer, config) + + const initPromise = provider.initialize() + + // Attach catch handler + initPromise.catch(() => { + // Expected to reject on timeout + }) + + // Verify initialization is in progress + expect(provider.initController.isInitializing()).to.be.true + + // Advance time by 7.9 seconds (before env var timeout) + await clock.tickAsync(7900) + + // Should still be initializing + expect(provider.initController.isInitializing()).to.be.true + + // Advance by another 200ms to trigger the 8-second timeout + await clock.tickAsync(200) + + // Wait for promise to settle + await initPromise.catch(() => {}) + + // Should now be timed out + expect(provider.initController.isInitializing()).to.be.false + }) + + it('should use DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS environment variable', async () => { + // Set experimental environment variable for 6-second timeout + process.env.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '6000' + + // Need to reload the config module to pick up env var + delete require.cache[require.resolve('../../src/config')] + const Config = require('../../src/config') + const config = new Config({}) + + const provider = new FlaggingProvider(mockTracer, config) + + // Spy on setError method to verify timeout message + const setErrorSpy = sinon.spy(provider, 'setError') + + const initPromise = provider.initialize() + + // Attach catch handler + initPromise.catch(() => { + // Expected to reject + }) + + // Advance time to trigger experimental env var timeout + await clock.tickAsync(6000) + + await initPromise.catch(() => {}) + + // Verify setError was called with experimental env var timeout error + expect(setErrorSpy).to.have.been.calledOnce + const errorArg = setErrorSpy.firstCall.args[0] + expect(errorArg).to.be.instanceOf(Error) + expect(errorArg.message).to.include('Initialization timeout') + expect(errorArg.message).to.include('6000ms') + }) + + it('should prioritize DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS over DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS', async () => { + // Set both environment variables - experimental should take priority + process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '10000' + process.env.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '4000' + + // Need to reload the config module to pick up env vars + delete require.cache[require.resolve('../../src/config')] + const Config = require('../../src/config') + const config = new Config({}) + + const provider = new FlaggingProvider(mockTracer, config) + + const initPromise = provider.initialize() + + // Attach catch handler + initPromise.catch(() => { + // Expected to reject on timeout + }) + + // Verify initialization is in progress + expect(provider.initController.isInitializing()).to.be.true + + // Advance time by 3.9 seconds (before experimental timeout) + await clock.tickAsync(3900) + + // Should still be initializing + expect(provider.initController.isInitializing()).to.be.true + + // Advance by another 200ms to trigger the 4-second timeout (experimental takes priority) + await clock.tickAsync(200) + + // Wait for promise to settle + await initPromise.catch(() => {}) + + // Should now be timed out (using experimental env var, not the regular one) + expect(provider.initController.isInitializing()).to.be.false + }) + + it('should use config object value over environment variables', async () => { + // Set environment variable + process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '7000' + + // Config with explicit timeout (should override env var) + const configWithTimeout = { + ...mockConfig, + experimental: { + flaggingProvider: { + enabled: true, + initializationTimeoutMs: 3000 // This should override env var + } + } + } + + const provider = new FlaggingProvider(mockTracer, configWithTimeout) + + const initPromise = provider.initialize() + + // Attach catch handler + initPromise.catch(() => { + // Expected to reject on timeout + }) + + // Verify initialization is in progress + expect(provider.initController.isInitializing()).to.be.true + + // Advance time by 2.9 seconds (before config timeout) + await clock.tickAsync(2900) + + // Should still be initializing + expect(provider.initController.isInitializing()).to.be.true + + // Advance by another 200ms to trigger the 3-second timeout (config takes priority) + await clock.tickAsync(200) + + // Wait for promise to settle + await initPromise.catch(() => {}) + + // Should now be timed out (using config value, not env var) + expect(provider.initController.isInitializing()).to.be.false + }) + }) }) From 06d5c11d47f2a0819892b79fa4a7f487830321f1 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Wed, 19 Nov 2025 14:36:14 -0500 Subject: [PATCH 2/2] lint --- packages/dd-trace/src/config.js | 7 ++++--- .../openfeature/flagging_provider_timeout.spec.js | 14 ++------------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index 8446c482004..05c3dc372cc 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -700,9 +700,10 @@ class Config { unprocessedTarget['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS this.#setString(target, 'env', DD_ENV || tags.env) this.#setBoolean(target, 'experimental.flaggingProvider.enabled', DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED) - target['experimental.flaggingProvider.initializationTimeoutMs'] = maybeInt( - DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS || DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS - ) + const timeoutEnvVar = DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS || DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS + if (timeoutEnvVar != null) { + target['experimental.flaggingProvider.initializationTimeoutMs'] = maybeInt(timeoutEnvVar) + } this.#setBoolean(target, 'traceEnabled', DD_TRACE_ENABLED) this.#setBoolean(target, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED) this.#setString(target, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT) diff --git a/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js b/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js index e40078f0824..134379cfce7 100644 --- a/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js +++ b/packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js @@ -290,17 +290,6 @@ describe('FlaggingProvider Initialization Timeout', () => { // Set environment variable for 8-second timeout process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '8000' - // Config without timeout (should use env var) - const configWithoutTimeout = { - ...mockConfig, - experimental: { - flaggingProvider: { - enabled: true - // initializationTimeoutMs not specified - should use env var - } - } - } - // Need to reload the config module to pick up env var delete require.cache[require.resolve('../../src/config')] const Config = require('../../src/config') @@ -368,7 +357,8 @@ describe('FlaggingProvider Initialization Timeout', () => { expect(errorArg.message).to.include('6000ms') }) - it('should prioritize DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS over DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS', async () => { + it('should prioritize DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS ' + + 'over DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS', async () => { // Set both environment variables - experimental should take priority process.env.DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '10000' process.env.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS = '4000'