Share parent's PropagationTags for local child spans#11701
Conversation
CoreTracer span-build allocates a fresh propagationTagsFactory.empty() per local child span; only the distributed ExtractedContext path reuses the parent's. Before optimizing that per-span allocation away, this test pins the load-bearing behavior: that a non-root (local child) span still carries the inbound distributed _dd.p.* tags when it injects. Verdict (both tests pass): it does. DDSpanContext.getPropagationTags() routes to the root (getRootSpanContextOrThis), so a non-root child's own propagationTags field is never read for injection. The per-span empty() is pure allocation waste, not a latent correctness bug. This test is the gate + safety net for the planned "share the parent's PropagationTags for local children" allocation fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CoreTracer span-build allocated a fresh propagationTagsFactory.empty() for every local child span, reused from the parent only on the distributed ExtractedContext path — N+1 PropagationTags per N-span trace, N of them needless empties. A non-root child's own PropagationTags is never read for injection: DDSpanContext.getPropagationTags() routes to the root (getRootSpanContextOrThis), confirmed by PropagationTagsChildSpanTest (inbound _dd.p.* survive child injection). So local children can share the parent's (trace-level) instance instead of allocating their own, collapsing N+1 -> 1 per trace. Safe: the ctor's updateTraceIdHighOrderBits stamp is guard-idempotent and a no-op on the already-stamped shared root instance (same trace => same high-order bits). Full dd-trace-core propagation + span-build suite passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Hi! 👋 Thanks for your pull request! 🎉 To help us review it, please make sure to:
If you need help, please check our contributing guidelines. |
amarziali
left a comment
There was a problem hiding this comment.
Doug the intent is ok to me. It looks some tests are still failing
The change landed, so drop the pre-implementation 'open question / planned fix' framing, the scratch-doc reference (ptags-allocation-findings.md), and the brittle CoreTracer line numbers. State what it guards: local children share the root's PropagationTags, and reads route to the root, so a non-root span still injects the inbound _dd.p.* tags. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The master merge renamed AgentSpan.context() to spanContext(); update the two call sites so :dd-trace-core:compileTestJava passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@amarziali Thanks. It looks like a rename got merged in last week that broke the tests that I ported from Groovy. It should be better now. |
|
Synced with |
What This Does
Local child spans now share the parent's (trace-level)
PropagationTagsinstead of each allocating a freshempty(). Collapses N+1 → 1PropagationTagsper N-span trace.Motivation
CoreTracerspan-build allocatedpropagationTagsFactory.empty()for every local child span (the common in-process case); only the distributedExtractedContextpath reused the parent's. A non-root child's ownPropagationTags, however, is never read —DDSpanContext.getPropagationTags()routes to the root (getRootSpanContextOrThis().propagationTags). So the per-spanempty()was pure allocation waste.Additional Notes
50s petclinic run under JMeter multi-endpoint load (Zulu 17, alloc JFR):
DDSpanContextallocations, 311PTagsallocations — 1 PTags per ~17.6 spans (vs 1:1 before)PTagsFactory$PTagsno longer appears in the top 30 allocating classes (below 0.5% of sampled allocation)Correctness — gated by a characterization test
First commit adds
PropagationTagsChildSpanTest, which pins the load-bearing behavior: a non-root (local child) span still carries the inbound distributed_dd.p.*tags on injection. Both tests pass, confirming this is pure allocation waste, not a latent correctness bug. The test is the gate + safety net for the change.Safety
updateTraceIdHighOrderBitsstamp is guard-idempotent (if (traceIdHighOrderBits != highOrderBits)) and a no-op on the already-stamped shared root instance (same trace ⇒ same high-order bits).dd-trace-corepropagation + span-build suite passes (W3C/Datadog extractors+injectors,W3CPropagationTags, OPM round-trip, OrgGuard,CoreSpanBuilderTest,DDSpanContextTest) — 0 failures.The
lastParentId/OPM inject-time mutation race (a transient per-injection value parked in the shared object) is pre-existing — injection already routes to the root's shared instance viagetPropagationTags()— so it is a separate concurrency concern, not introduced or worsened by this change. Tracked in #11702.tag: no release note
tag: ai generated
🤖 Generated with Claude Code