Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
11 changes: 11 additions & 0 deletions packages/dd-trace/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@
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,
Expand Down Expand Up @@ -698,6 +700,10 @@
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)
const timeoutEnvVar = DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS || DD_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS

Check failure on line 703 in packages/dd-trace/src/config.js

View workflow job for this annotation

GitHub Actions / lint

This line has a length of 135. Maximum allowed is 120
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)
Expand Down Expand Up @@ -1012,6 +1018,11 @@
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)
Expand Down
2 changes: 2 additions & 0 deletions packages/dd-trace/src/supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
183 changes: 183 additions & 0 deletions packages/dd-trace/test/openfeature/flagging_provider_timeout.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,187 @@ 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'

// 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
})
})
})
Loading