Conversation
The snapshot logger object now carries a generation token alongside thread_id. A thread's ident can be reused after the thread exits, so two snapshots sharing a thread_id are not guaranteed to come from the same execution context; pairing the id with a per-thread generation counter makes the ambiguity detectable. The token is a lazily assigned counter keyed by the Thread object (not its ident), held in a process-wide ObjectSpace::WeakMap so finalized threads do not leak. Tokens are unique only within a runtime id, which is emitted alongside them in the snapshot envelope. Closes the generation-token gap flagged in the Casual Correlation RFC and matches the system-tests correlation gate (Test_Debugger_Snapshot_Correlation_Fields::test_generation_token), which reads logger.generation.
|
Thank you for updating Change log entry section 👏 Visited at: 2026-08-26 17:20:08 UTC |
Typing analysisNote: Ignored files are excluded from the next sections. Untyped methodsThis PR introduces 5 partially typed methods, and clears 30 partially typed methods. It increases the percentage of typed methods from 70.06% to 71.16% (+1.1%). Partially typed methods (+5-30)❌ Introduced:Untyped other declarationsThis PR clears 2 partially typed other declarations. It increases the percentage of typed other declarations from 85.33% to 85.69% (+0.36%). Partially typed other declarations (+0-2)✅ Cleared:If you believe a method or an attribute is rightfully untyped or partially typed, you can add |
BenchmarksBenchmark execution time: 2026-08-26 17:44:27 Comparing candidate commit af216e0 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 49 metrics, 0 unstable metrics.
|
|
✅ All CI checks and tests passed. 🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: af216e0 | Docs | View more details | Give us feedback! |
ThreadGeneration stored per-thread generation tokens in an ObjectSpace::WeakMap keyed by Thread. On Ruby 2.5 and 2.6, ObjectSpace::WeakMap#[]= raises "ArgumentError: cannot define finalizer for Integer" for any object key, because WeakMap's internal finalizer registration is broken on those versions. Every DI snapshot build therefore crashed in ProbeNotificationBuilder#build_snapshot_base when it read the generation token via ThreadGeneration.current, breaking all 114 snapshot-producing tests on the Ruby 2.6 CI job. Replace the WeakMap with a thread-local stored on the Thread object via Thread#thread_variable_get/set, keyed by :datadog_di_thread_generation. This matches the codebase's existing per-thread state pattern (lib/datadog/appsec/rate_limiter.rb and lib/datadog/appsec/api_security/sampler.rb use the same THREAD_KEY + thread_variable approach). Thread-locals are reclaimed when the thread is garbage collected, so finalized threads do not leak, and the approach works across the full supported matrix (Ruby 2.6..4.0). DI requires Ruby 2.6+ (script_compiled), so 2.6 is the lower bound where this code executes. Verified: spec/datadog/di/thread_generation_spec.rb passes (4 examples, 0 failures) on Ruby 3.3.12; the four spec assertions were replicated standalone on Ruby 2.6.10, 2.7.8, 3.4.10, and 4.0.6 (WeakMap error gone, per-thread token stable and distinct across threads). steep check and standardrb clean on the changed file.
The generation token added to the snapshot logger object in probe_notification_builder.rb#build_snapshot_base was missing from the expected_snapshot_payload logger hashes in everything_from_remote_config_spec.rb, so the full-payload match assertions failed on Ruby 2.7..4.0 (5 tests) because the actual payload includes logger.generation, which the expected payload omitted. Add generation: Integer to the four expected logger hashes, matching the matcher pattern the PR already used in probe_notification_builder_spec.rb. The Integer matcher accommodates the token's non-deterministic value (a process-wide monotonic counter), the same way timestamp and duration use Integer. Verified: spec/datadog/di/integration/everything_from_remote_config_spec.rb passes (11 examples, 0 failures) on Ruby 3.3.12 with TEST_DATADOG_INTEGRATION=1. standardrb clean on the changed file.
thread_generation.rbs used bare Integer, Symbol, Thread, and Thread::Mutex. rbs.md requires core types and stdlib classes in RBS to carry the :: prefix so Steep resolves them to the global class rather than a same-named constant in the current module namespace; existing DI sigs follow this (component.rbs, instrumenter.rbs). Verified: rspec spec/datadog/di/thread_generation_spec.rb spec/datadog/di/probe_notification_builder_spec.rb (0 failures). standard/steep run in CI (absent from the default Gemfile on Ruby 3.2).
State and State#initialize carried only an @api private tag with no docstring. documentation.md requires docstrings on every public method including initialize, and comments.md requires a class docstring stating what the class is. Add a class docstring for State (the process-wide per-thread generation ledger) and an initializer docstring. Verified: rspec spec/datadog/di/thread_generation_spec.rb (0 failures).
There was a problem hiding this comment.
Pull request overview
This PR adds a per-thread execution-context generation token to Dynamic Instrumentation (Live Debugger) snapshot payloads, emitted as logger.generation, to disambiguate cases where a thread ident may be reused after thread exit.
Changes:
- Introduces
Datadog::DI::ThreadGenerationto lazily assign a monotonically increasing, per-thread generation token via thread-local storage. - Adds
logger.generationto the snapshot logger envelope produced byProbeNotificationBuilder. - Updates DI unit/integration specs and adds an RBS signature for the new module.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/datadog/di/thread_generation_spec.rb | New unit spec for thread generation token behavior. |
| spec/datadog/di/probe_notification_builder_spec.rb | Asserts logger.generation is present and matches the current thread’s token; updates expected payload shapes. |
| spec/datadog/di/integration/everything_from_remote_config_spec.rb | Updates integration expectations to include logger.generation. |
| sig/datadog/di/thread_generation.rbs | Adds RBS typings for Datadog::DI::ThreadGeneration. |
| lib/datadog/di/thread_generation.rb | Implements the per-thread generation token ledger/state. |
| lib/datadog/di/probe_notification_builder.rb | Adds logger.generation to snapshot base payload. |
| lib/datadog/di/boot.rb | Ensures DI boot sequence loads the new thread generation implementation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # Execution-context generation token, paired with thread_id: a reused | ||
| # thread id is distinguishable by a different generation. | ||
| generation: DI::ThreadGeneration.current, | ||
| version: 2, |
| require "datadog/di/thread_generation" | ||
|
|
||
| RSpec.describe Datadog::DI::ThreadGeneration do | ||
| it "returns the same token for the same thread" do |
What does this PR do?
Adds a per-thread execution-context generation token to DI snapshots. Each snapshot now includes a generation value alongside the existing thread id, so snapshots from different execution contexts that share a recycled thread id can be distinguished.
Motivation:
A thread's id can be reused after the thread exits, so two snapshots sharing a thread id are not guaranteed to come from the same execution context. Pairing the thread id with a generation token makes the ambiguity detectable: same id with different generation means different threads.
Change log entry
Yes. Dynamic Instrumentation: snapshots now carry an execution-context generation token to disambiguate reused thread ids.
Additional Notes:
N/A
How to test the change?