DEBUG-5433 DI: implicit enablement via remote configuration#5525
Conversation
|
Thank you for updating Change log entry section 👏 Visited at: 2026-05-19 23:34:11 UTC |
Typing analysisNote: Ignored files are excluded from the next sections.
|
BenchmarksBenchmark execution time: 2026-06-25 16:21:04 Comparing candidate commit 72bfce8 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 48 metrics, 1 unstable metrics.
|
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 72bfce8 | Docs | Datadog PR Page | Give us feedback! |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb6e3bd060
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Restructures the Dynamic Instrumentation (DI) lifecycle to support implicit enablement via Remote Configuration (RC): the DI component can now be built in a stopped state and later started/stopped at runtime based on an RC enablement signal, without requiring DD_DYNAMIC_INSTRUMENTATION_ENABLED=true at boot.
Changes:
- Adds a runtime lifecycle to DI (
start!,stop!,started?) and wires RC-driven enablement/disablement throughDatadog::Tracing::Remote→Datadog::DI::Remote.handle_rc_enablement. - Declares a new APM_TRACING capability bit (bit 38) for DI implicit enablement and updates RC capabilities/product registration logic accordingly.
- Adds/updates unit + integration specs, RBS signatures, and docs to cover implicit enablement, restart behavior, and probe coverage across enablement timing.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| spec/datadog/tracing/remote_spec.rb | Updates expectations to include DI enablement capability bit 38. |
| spec/datadog/di/remote_spec.rb | Adds coverage for .handle_rc_enablement and .explicitly_disabled?, and updates receiver gating expectations. |
| spec/datadog/di/remote_integration_spec.rb | Starts DI component in setup to reflect new lifecycle gating. |
| spec/datadog/di/probe_manager_spec.rb | Updates TracePoint construction expectations and adds reopen coverage. |
| spec/datadog/di/integration/probe_coverage_spec.rb | New integration matrix validating line/method probes across enablement timing. |
| spec/datadog/di/integration/probe_coverage_method_pre_target_class.rb | Fixture for method-probe “pre-enablement load” case. |
| spec/datadog/di/integration/probe_coverage_method_post_target_class.rb | Fixture for method-probe “post-enablement load” case. |
| spec/datadog/di/integration/probe_coverage_line_pre_target_class.rb | Fixture for line-probe “pre-enablement load” case. |
| spec/datadog/di/integration/probe_coverage_line_post_target_class.rb | Fixture for line-probe “post-enablement load” case. |
| spec/datadog/di/integration/instrumentation_spec.rb | Starts DI component in setup to ensure probes install under new lifecycle. |
| spec/datadog/di/integration/implicit_enablement_spec.rb | New end-to-end in-process integration spec for RC enable/disable behavior. |
| spec/datadog/di/integration/everything_from_remote_config_spec.rb | Starts DI component so DI receiver processes RC changes. |
| spec/datadog/di/component_spec.rb | Updates build semantics + adds lifecycle (start/stop/idempotence/thread) coverage. |
| spec/datadog/di/code_tracker_spec.rb | Adds regression for code-tracker registry persistence across stop/start. |
| spec/datadog/di_spec.rb | Adds .unsupported_reason as shared precondition source with specs. |
| spec/datadog/core/telemetry/integration/telemetry_spec.rb | Adjusts DI fakes to satisfy new startup lifecycle calls (start!, started?). |
| spec/datadog/core/remote/client/capabilities_spec.rb | Updates base64 capabilities and DI/SymDB registration expectations. |
| spec/datadog/core/configuration/components_state_spec.rb | New unit spec for ComponentsState#di_implicitly_enabled?. |
| spec/datadog/core/configuration/components_spec.rb | Updates DI component expectations (built-but-stopped), adds state/startup carry-over coverage. |
| sig/datadog/di/remote.rbs | Adds signatures for handle_rc_enablement and explicitly_disabled?. |
| sig/datadog/di/probe_manager.rbs | Adds stop/reopen signatures. |
| sig/datadog/di/component.rbs | Adds lifecycle fields and start!/stop!/started? signatures; adds explicitly_enabled?. |
| sig/datadog/di.rbs | Adds signature for .unsupported_reason. |
| sig/datadog/core/configuration/settings.rbs | Adds signature for using_default? on DI settings. |
| sig/datadog/core/configuration/components_state.rbs | Extends state with di_implicitly_enabled? and tightens types. |
| lib/datadog/tracing/remote.rb | Declares bit 38 and invokes DI RC enablement handler on APM_TRACING payload. |
| lib/datadog/di/remote.rb | Implements RC-driven enable/disable entry point and gates probe processing by started?. |
| lib/datadog/di/probe_notifier_worker.rb | Resets stop flag on start to support stop/start cycles. |
| lib/datadog/di/probe_manager.rb | Switches to TracePoint.new and adds reversible stop/reopen APIs. |
| lib/datadog/di/component.rb | Always-build semantics (when settings present), adds lifecycle mutex + start/stop/started?. |
| lib/datadog/di.rb | Adds .unsupported_reason helper for consistent precondition messaging. |
| lib/datadog/core/telemetry/event/app_started.rb | Reports DI enabled based on started? rather than component presence. |
| lib/datadog/core/remote/client/capabilities.rb | Registers DI RC products/receivers whenever DI settings exist (no longer gated by enabled flag). |
| lib/datadog/core/configuration/components.rb | Starts DI during startup based on env-var or carried RC state; captures runtime state into ComponentsState. |
| lib/datadog/core/configuration/components_state.rb | Adds di_implicitly_enabled storage and accessor. |
| docs/DynamicInstrumentation.md | Documents env-var vs UI-driven implicit enablement and precedence rules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…al, doc fixes Apply review fixes: - handle_rc_enablement now calls Datadog.send(:components, allow_initialization: false) in both lookup sites (lookup + rescue telemetry). The RC dispatch runs on the remote-config thread; the default allow_initialization: true would synchronously build the component tree from a callback context if a signal arrives before Components#initialize completes. - Trailing commas added on the two multi-line Datadog.logger.warn calls in lib/datadog/di/remote.rb. lib/datadog/di/ requires trailing commas per rules/dd-trace-rb.md. - Replace build-dependency skip guards (`skip 'Test requires DI C extension' unless Datadog::DI.respond_to?(:exception_message)`) with stub-based tests that exercise the Components#state / @environment_logger_extra logic without requiring the DI C extension. The tests' subject is the Components integration with DI lifecycle, not DI's internals — `instance_double` with a state-aware double for the startup! transitions covers the same surface. Applies to spec/datadog/core/configuration/components_spec.rb (4 sites) and spec/datadog/di_spec.rb (1 site). - Remove the Thread.pass polling loop in the 'spawns a background thread on start! and reaps it on stop!' test. ProbeNotifierWorker#stop calls thread.join (or thread.kill on timeout), so by the time component.stop! returns the worker thread is gone — the polling was defensive against a deterministic operation and resembled the sleep antipattern in disguise. - Update the DD_DYNAMIC_INSTRUMENTATION_ENABLED entry in docs/GettingStarted.md to describe the new three-valued semantics: true enables at boot, false blocks RC enablement, unset allows UI-driven enablement. - Update the `Datadog.send(:components)` stubs in remote_spec.rb and implicit_enablement_spec.rb to match the new allow_initialization: false signature.
Commit 5109f21 removed these two guards with the rationale that "tests now skip on <2.6" — but spec/datadog/core/telemetry/ integration/telemetry_spec.rb is in spec:main (no platform skip) and stubs Datadog::DI::Component.build to a non-nil fake to exercise the "DI enabled" code path on Rubys without the C extension. On Ruby 2.5 that bypasses Component.build's runtime check; the call then reaches DI.activate_tracking, which is defined in di/base.rb and only loaded on Ruby >= 2.6. The previous commit's analysis was correct for production (Component.build returns nil on 2.5, so the outer guard is sufficient) and for di_test-using specs (those skip on <2.6). It missed the cross-component spec that mocks past the platform check. verify_partial_doubles is enabled, so a test-side allow(DI).to receive(:activate_tracking) workaround does not work on Ruby 2.5 — the production guard is the correct fix. Failing job: Ruby 2.5 / build & test (standard) [0] — NoMethodError: undefined method `activate_tracking' for Datadog::DI:Module at components.rb:245. Verified spec/datadog/di/component_spec.rb (45/45 pass) and spec/datadog/core/telemetry/integration/telemetry_spec.rb's "when dynamic instrumentation is fully enabled" example on Ruby 3.2. Ruby 2.5 not runnable locally (no mise/Docker access); the restored pattern matches the pre-5109f21277 code that passed CI on Ruby 2.5 across many runs.
This reverts commit 51eec38.
The 'when dynamic instrumentation is fully enabled' context stubs Datadog::DI::Component.build to a non-nil fake so the enabled-DI code path runs in CI configurations without the DI C extension. That stub bypasses Component.build's runtime check, so Components#startup! reaches Datadog::DI.activate_tracking — which is loaded only from datadog/di/boot.rb under RUBY_VERSION >= '2.6' && RUBY_ENGINE != 'jruby' (lib/datadog.rb:19). On Ruby 2.5 the method is undefined, and the spec fails with NoMethodError. 'DI fully enabled' is a state that cannot exist on Ruby 2.5 in production (DI requires MRI 2.6+, per DI.unsupported_reason). Skip the context on Ruby <2.6, matching the existing pattern in spec/datadog/core/configuration/components_spec.rb:797. This is the test-side fix for the failure that 51eec38 patched in production. The production-side workaround was reverted at 51c1fef because it added respond_to?(:activate_tracking) guards to lib/datadog/core/configuration/components.rb:245 and lib/datadog/di/remote.rb:77 — guards that are dead in production (the outer 'if dynamic_instrumentation' already short-circuits on every platform where activate_tracking could be undefined) and exist only to make this fixture work on Ruby 2.5. The fix belongs in the fixture.
The 'Tracing before DI' registration order in lib/datadog/core/remote/client/capabilities.rb is load-bearing: on a combined RC dispatch (LIVE_DEBUGGING + APM_TRACING in one transaction), the Tracing receiver must run first so handle_rc_enablement starts DI before the DI receiver processes the probe change. The end-to-end behavior is covered by the combined-RC-transaction test in spec/datadog/di/integration/implicit_enablement_spec.rb, but that test exercises the order indirectly through dispatcher behavior. Add a direct unit assertion on capabilities.receivers order so a future refactor that reorders registrations is caught at the registration site rather than via a multi-step integration failure.
Two comment-density tightenings flagged in PR review (non-blocking): - lib/datadog/core/remote/client/capabilities.rb: the registration-order paragraph went from 10 lines to 7. Keeps the constraint (Tracing registers before DI) and the silent-drop consequence; drops the step-by-step retelling. The full mechanism is regression-tested by spec/datadog/di/integration/implicit_enablement_spec.rb's combined-payload case. - lib/datadog/core/configuration/components.rb #state: the using_default? paragraph went from 9 lines to 6. Keeps the unique rationale (why using_default? rather than !enabled — the Datadog.configure-mutation timing), drops the worked example. The "vs default_precedence?" rationale lives at DI::Component#explicitly_enabled?; this site's rationale is the orthogonal "vs !enabled" choice.
The skip guard added in b1ed2d6 used before(:each), but the outer `before do` block at telemetry_spec.rb:570 runs Datadog.configure first — and configure reaches DI.activate_tracking before the inner before(:each) hook fires. On Ruby 2.5 the skip never ran, the configure crashed, and `Ruby 2.5 / build & test (standard) [0]` kept failing with NoMethodError. before(:all) runs ahead of all before(:each) hooks in the group, so the skip lands before the crash. Companion guard at components_spec.rb:797 already uses before(:all) — this matches that. Verified by reading RSpec hook ordering: before(:all) → outer before(:each) → inner before(:each).
The outer rescue in handle_rc_enablement catches an exception from the body (e.g. component.start! raising) and then attempts to report it via `Datadog.send(:components, allow_initialization: false)&.telemetry&.report`. If the components lookup itself raises (extremely unlikely in steady state, but possible during teardown races or during early boot before Components#initialize completes), the rescue body propagates out of handle_rc_enablement \u2014 defeating the DI contract that DI never raises into Tracing::Remote.process_config or further into the RC dispatch. Wrap the telemetry lookup+report in a defensive begin/rescue that swallows any secondary exception and logs at debug. Add a regression test that stubs Datadog.send to succeed once then raise, and asserts handle_rc_enablement does not propagate. Verified: bundle exec rspec spec/datadog/di/remote_spec.rb (locally, syntax check only; full run gated on Ruby 2.6+ MRI with C ext).
Commit ea10a35 added an inner begin/rescue around the telemetry lookup in handle_rc_enablement's rescue clause, plus a test that simulated the inner call raising. Tracing both call sites shows neither can actually raise in this codebase: - Datadog.send(:components, allow_initialization: false) with allow_initialization: false always returns the (possibly nil/false) @components value via the COMPONENTS_READ_LOCK.synchronize block. No raise path on this branch. (lib/datadog/core/configuration.rb:196) - telemetry.report(e, description:) does string-building from exception.class.name/inspect (safe on every Exception), then log!(event) which short-circuits on enabled? && @log_collection_enabled and otherwise enqueues to the worker. No raise path under realistic conditions; the only theoretical path is a customer Exception subclass overriding #backtrace to raise, which does not apply here because e originates from DI's own code. (lib/datadog/core/telemetry/logging.rb:51-69) The guard was defending against scenarios that don't exist. Remove the rescue and the simulation test; the outer rescue at line 82 is sufficient.
…eason
The project-established pattern for Ruby version checks is the
Datadog::RubyVersion helper:
lib/datadog/di/base.rb:50 -- RubyVersion.is?('>= 2.6')
lib/datadog.rb:22 -- Datadog::RubyVersion.is?('>= 2.6')
The newly-added DI.unsupported_reason used a bare `RUBY_VERSION < '2.6'`,
which works but diverges from the helper. RubyVersion captures the
running version once as Gem::Version (handles the 3.2.10 < 3.2.3
string-comparison bug) and is the standard idiom.
Switch to Datadog::RubyVersion.is?('< 2.6').
Test impact: the tests that stub_const('RUBY_VERSION', ...) to
exercise the version branch must also stub
Datadog::RubyVersion::CURRENT_RUBY_VERSION, which is the cached
Gem::Version captured at module load time. Established pattern in
spec/datadog/core/configuration/settings_spec.rb:907-908 etc.
Updated stubs:
- spec/datadog/di_spec.rb: 'all preconditions met',
'Ruby older than 2.6', 'C extension is not loaded' contexts
- spec/datadog/di/component_spec.rb: 'Ruby 2.6+ required, mocked'
context
The error message still interpolates RUBY_VERSION (not CURRENT_RUBY_VERSION),
so both stubs are needed: one for the gate, one for the message.
The four 'returns nil and logs at debug only' tests in component_spec asserted `expect(logger).to receive(:debug)` without a .with matcher. That passes for any debug call, including an empty one \u2014 the test verifies that debug was called and warn was not, but not that the specific build-precondition reason landed in the log. The companion 'with DD_DYNAMIC_INSTRUMENTATION_ENABLED explicitly true' tests in each context already match the warn message content. The debug counterparts should match the same content, since production code emits the same string at either level (logger.public_send(level, 'di: dynamic instrumentation is disabled: <reason>')). Tighten each debug assertion with a .with(a_string_matching(...)) matcher that pins the specific reason: - 'Remote Configuration is not enabled' (RC disabled) - 'MRI is required' (non-MRI engine) - 'Ruby 2.6+ is required' (Ruby <2.6) - 'C extension is not available' (C ext absent) Also rename each test to name the specific reason instead of the generic 'logs at debug only'.
The context 'when DI component is started via RC (settings.enabled false)' did not actually drive RC \u2014 it stubbed started?: true and left settings.dynamic_instrumentation.enabled at default. The combination (started + using_default? on :enabled) is exactly what production code reads to compute di_implicitly_enabled, so the assertion was correct, but the 'via RC' phrasing overstated the scope: a unit test against #state cannot, by construction, drive through Remote.handle_rc_enablement. Rename both sibling contexts to describe the property under test (env-var explicit set vs customer never touched the env var) rather than the upstream driver. Add explanatory comments tying each context to the production path it models. No test logic changed.
The component_spec covered the sequential lifecycle (start!/stop!, idempotence, restart) but not the @lifecycle_mutex's role in serializing concurrent calls. The mutex comment in component.rb claimed serialization across start!/stop!/shutdown! \u2014 the claim was not test-protected. The behavioral guarantee (two threads calling lifecycle methods concurrently are serialized) is Ruby's Mutex#synchronize contract, which we do not re-test here. The mechanism guarantee (each lifecycle method actually wraps its body in @lifecycle_mutex.synchronize) is what this PR claims and what these tests verify. A future refactor that drops the synchronize call from one of the three methods would be caught at this layer; Ruby's mutex then handles the rest. Three mechanism tests added, one per lifecycle method: - start! acquires @lifecycle_mutex - stop! acquires @lifecycle_mutex - shutdown! acquires @lifecycle_mutex Lifecycle method bodies are stubbed so the assertion targets the mutex acquisition alone; the worker/probe-manager side effects are already covered by the #start! and #stop! describe above.
process_config runs on the remote-config worker thread. The telemetry-reporting call at the end used Datadog.send(:components) with the default allow_initialization: true, which would synchronously build the entire component tree from the RC worker thread if @components were nil at dispatch time \u2014 lock acquisition from the wrong thread, plus the cost of a full tree build on a hot path. The realistic exposure is small (the RC client thread is itself part of the components tree, so dispatch generally cannot precede initialization), but the asymmetry is real: PR 5525 added DI::Remote.handle_rc_enablement on the same dispatch path and used allow_initialization: false correctly. Bring the existing telemetry call into line. Switch to: Datadog.send(:components, allow_initialization: false) &.telemetry&.client_configuration_change!(env_vars) The &. chain matches the pattern in DI::Remote.handle_rc_enablement. When components is nil the call is a no-op, which is the right behavior \u2014 no point reporting a configuration change to a telemetry component that doesn't exist yet.
Empty line left behind by commit b0fcdb8 when the defensive rescue test was removed. CI flagged: spec/datadog/di/remote_spec.rb:146:1: Layout/EmptyLinesAroundBlockBody: Extra empty line detected at block body end. Trailing blank line removed.
…action under test The three @lifecycle_mutex serialization tests added in f28d5a8 assert exactly one synchronize call per lifecycle method: expect(mutex).to receive(:synchronize).and_call_original component.start! (or .stop!, .shutdown!) The after block ran component&.shutdown! for cleanup, which itself calls @lifecycle_mutex.synchronize. RSpec mock verification runs after the example's after hooks, so the cleanup's synchronize call was counted against the expectation — yielding "expected 1 time, received 2 times" on every Ruby version's batch [0]. Production code (start!, stop!, shutdown!) each calls synchronize exactly once, as the tests intended to verify. The bug is in the test fixture, not the production code. Replace shutdown!-based cleanup with DI.remove_current_component, which acquires DI::LOCK (a different Mutex on the DI module per lib/datadog/di/base.rb:21) and does not touch @lifecycle_mutex. The before block stubs all worker/probe-manager side effects, so no thread or hook needs stopping — the only state that escapes the example is the entry that Component.build adds to DI.current_components via add_current_component. The expectation remains strict (default "exactly once"); only the teardown mechanism changes. Co-Authored-By: Claude <[email protected]>
Addresses review comment from @mabdinur: a LIVE_DEBUGGING probe that lands in an RC poll while DI is stopped, with the dynamic_instrumentation_enabled=true signal arriving in a separate later poll, was dropped and never installed until the customer edited the probe. Root cause: DI::Remote.receivers ignores LIVE_DEBUGGING changes while the component is stopped, so the probe never enters the probe repository. When a later poll flips enablement, Core::Remote::Client#apply_config marks the unchanged probe content `same` and does not re-dispatch it, and component.start! -> probe_manager.reopen only replays what is already in the repository. The existing fixes did not cover this: the Tracing-before-DI receiver ordering handles the same-poll combined transaction, and the stop!/reopen repository preservation handles toggling DI off then on after the probe was already installed. Fix: on the stopped->started transition, handle_rc_enablement reconciles against the current LIVE_DEBUGGING contents in the RC repository (threaded through from Tracing::Remote.process_config) and installs any probe not already tracked. The DI receiver's :insert path now skips a probe the reconcile already installed in the same dispatch, so the same-poll combined transaction does not double-add (which would surface a false errored status). Verified: new integration test in implicit_enablement_spec drives two real client.sync polls (probe while stopped, then enable) and asserts the probe installs; it fails without the fix (installed=0) and passes with it. Full non-contrib DI suite + tracing remote spec green (862 examples). Steep and StandardRB clean on changed files.
|
@mabdinur Good catch — confirmed with a test, and you've got the mechanism exactly right. The receiver-ordering fix only covers the same-poll combined transaction, and the Fixed in c54c088: the enable path now reconciles against the current |
… RC registration When DI is explicitly disabled, DI::Component.build returns nil and the DI RC capability/product/receiver block (including bit 38) is not registered. Bit 38 moves from Tracing::Remote::CAPABILITIES to DI::Remote.capabilities so it is gated with the rest of the DI block. The unset (default) case is unchanged: the component is built stopped and DI is registered so RC can enable it. handle_rc_enablement's nil-component branch now distinguishes explicit disable from unsupported-environment reasons.
Reword four spec comments to drop provenance framing ("raised in PR
review", "codex review scenario", "codex P2 review comment") while
keeping the technical scenario each describes. Process narration belongs
in the PR thread, not in source.
The previous comment claimed the DI component exists "whenever the runtime supports it". That overstates: Component.build also returns nil on a supported runtime when DI is explicitly disabled (DD_DYNAMIC_INSTRUMENTATION_ENABLED=false) or when Remote Configuration is unavailable. Reword to state the actual build preconditions.
Resolves the TODO in DI::Remote.products. With always-build (#5525) the DI remote-config block registers the LIVE_DEBUGGING product and the implicit-enablement capability (bit 38) whenever DI is not explicitly disabled — including on JRuby and Ruby 2.5, where DI can never run. The backend would then send probe configs and enable signals the tracer silently drops. Gate the DI registration block on a new platform check, Datadog::DI.supported_runtime? (MRI on Ruby 2.6+), which is the platform-support subset of DI.unsupported_reason. It deliberately omits the C-extension check so behaviour does not depend on a build artifact that is always present in shipped gems on supported runtimes. Tests: capabilities_spec gains an unsupported-runtime context (DI not registered even when enabled); di_spec covers supported_runtime? across MRI 2.6+, JRuby, and MRI < 2.6.


What does this PR do?
Restructures the Dynamic Instrumentation lifecycle to support implicit enablement via Remote Configuration. The DI component is now always built when not explicitly disabled (
DD_DYNAMIC_INSTRUMENTATION_ENABLED=false) and can be started either by the env var set totrueor by a remote-config signal at runtime.Motivation
We want to reduce customer friction by making creating a probe in the Datadog UI turn DI on automatically — customers should not have to set
DD_DYNAMIC_INSTRUMENTATION_ENABLED=trueand restart their service first.Change log entry
Yes. Dynamic Instrumentation can now be enabled from the Datadog UI without setting
DD_DYNAMIC_INSTRUMENTATION_ENABLEDor restarting the application. SettingDD_DYNAMIC_INSTRUMENTATION_ENABLED=falseexplicitly still disables DI and blocks UI-driven enablement.Architecture
Component#start!/#stop!/#started?— runtime lifecycle, serialized by an internal mutex so concurrent RC callbacks cannot race.ProbeManager#stop/#reopen— wraps the definition trace point's disable/enable so the component can re-enter the same instance across stop/start cycles.Component.buildnow succeeds whenever settings respond todynamic_instrumentation(no longer requiresenabled). When a precondition is unmet (RC disabled, MRI required, Ruby 2.6+ required, dev environment, C extension absent), the build returns nil. Log level mirrors customer intent:warnwhenDD_DYNAMIC_INSTRUMENTATION_ENABLED=truewas explicitly set, otherwisedebug— implicit-enablement customers receive theirwarnfromRemote.handle_rc_enablementwhen the RC signal lands on a nil component.Components#startup!starts DI if eithersettings.dynamic_instrumentation.enabledis true (env-var path) orold_state.di_implicitly_enabled?is true (RC-driven enablement carried across a component rebuild viaComponentsState).Datadog::DI::Remote.handle_rc_enablement(enabled)is the entry point for the RC-driven path. It's invoked fromDatadog::Tracing::Remotewhen anAPM_TRACINGpayload containsdynamic_instrumentation_enabled. Precedence:explicitly_disabled?(env var set tofalse) blocks RC enablement.APM_TRACINGcapability bit 38 (APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION) declared inTracing::Remote.capabilitiesso the backend knows the tracer can be enabled this way.DI::Remote.receiversnow gates change processing oncomponent.started?— probe changes received while DI is stopped are dropped. The next RC poll re-delivers the current backend state, so probes added in the UI before DI started arrive on a subsequent poll oncestart!is called.How to test the change?
Use Debugger UI to set a probe on a running Ruby service. Requires https://github.com/DataDog/dd-go/pull/241136 + https://github.com/DataDog/web-ui/pull/286651 + tracer version faked to 2.37.0 (next planned release).