diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 375480a8792..64ab771a8b7 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -2017,7 +2017,12 @@ protected static final DDSpanContext buildSpanContext( requestContextDataIast = null; ciVisibilityContextData = null; } - propagationTags = tracer.propagationTagsFactory.empty(); + // Local child: share the parent's (trace-level) PropagationTags instead of allocating a + // fresh empty() per span. getPropagationTags() routes to the root, so a non-root child's + // own instance is never read for injection — the per-span empty() was pure allocation + // waste (N+1 -> 1 PropagationTags per trace). The ctor's updateTraceIdHighOrderBits stamp + // is a guarded no-op on the already-stamped root instance (same trace => same high bits). + propagationTags = ddsc.getPropagationTags(); } else { long endToEndStartTime; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/PropagationTagsChildSpanTest.java b/dd-trace-core/src/test/java/datadog/trace/core/PropagationTagsChildSpanTest.java new file mode 100644 index 00000000000..d1cdc3e60e2 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/PropagationTagsChildSpanTest.java @@ -0,0 +1,98 @@ +package datadog.trace.core; + +import static datadog.trace.api.TracePropagationStyle.DATADOG; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.api.DDTraceId; +import datadog.trace.api.sampling.PrioritySampling; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.propagation.ExtractedContext; +import datadog.trace.core.propagation.PropagationTags; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Characterization test for the open question in {@code ptags-allocation-findings.md}: when a + * distributed trace arrives carrying {@code _dd.p.*} propagation tags, does a non-root (local + * child) span still carry those inbound tags when it injects? + * + *
Inbound {@code _dd.p.*} live on the root's {@link PropagationTags} (inherited from the {@link + * ExtractedContext}, {@code CoreTracer} ~line 2031), but a local child currently receives a fresh + * {@code propagationTagsFactory.empty()} instance (~line 2020). If {@link + * #localChildCarriesInboundDdpTags()} fails, non-root injection is dropping inbound {@code + * _dd.p.*} — a latent correctness bug, not merely the known per-span allocation waste. If it + * passes, there is reconciliation and the per-span empties are pure waste (sharing the + * parent's instance is then a safe allocation win). + * + *
Either way this test is the gate + safety net for the planned "share the parent's
+ * PropagationTags for local children" fix.
+ */
+class PropagationTagsChildSpanTest extends DDCoreJavaSpecification {
+
+ private static final String INBOUND_HEADER = "_dd.p.dm=934086a686-4,_dd.p.anytag=value";
+ private static final String INBOUND_TAG = "_dd.p.anytag=value";
+
+ private CoreTracer tracer;
+
+ @BeforeEach
+ void setup() {
+ tracer = tracerBuilder().build();
+ }
+
+ private static ExtractedContext extractedWithDdpTags() {
+ return new ExtractedContext(
+ DDTraceId.ONE,
+ 2,
+ PrioritySampling.SAMPLER_KEEP,
+ null,
+ 0,
+ Collections.