diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 44b9c2f8e5..c04a3f9a2c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -31,6 +31,10 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: === Unreleased +[float] +===== Bug fixes +* Fixed edge case where inferred spans could cause cycles in the trace parent-child relationships, subsequently resulting in the UI crashing - {pull}3588[#3588] + [[release-notes-1.x]] === Java Agent version 1.x diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index f8854468f0..cc3df527cb 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -64,7 +64,7 @@ public class TraceContext implements Recyclable, co.elastic.apm.agent.tracer.Tra public static final String ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME = "elastic-apm-traceparent"; public static final String W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME = "traceparent"; public static final String TRACESTATE_HEADER_NAME = "tracestate"; - public static final int SERIALIZED_LENGTH = 42; + public static final int SERIALIZED_LENGTH = 51; private static final int TEXT_HEADER_EXPECTED_LENGTH = 55; private static final int TEXT_HEADER_TRACE_ID_OFFSET = 3; private static final int TEXT_HEADER_PARENT_ID_OFFSET = 36; @@ -227,6 +227,7 @@ private TraceContext(ElasticApmTracer tracer, Id id) { *
* Note: the {@link #traceId} will still be 128 bit *
+ * * @param tracer a valid tracer */ public static TraceContext with64BitId(ElasticApmTracer tracer) { @@ -621,7 +622,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; TraceContext that = (TraceContext) o; return id.equals(that.id) && - traceId.equals(that.traceId); + traceId.equals(that.traceId); } public boolean idEquals(@Nullable TraceContext o) { @@ -650,6 +651,8 @@ public void serialize(byte[] buffer) { offset = traceId.toBytes(buffer, offset); offset = id.toBytes(buffer, offset); offset = transactionId.toBytes(buffer, offset); + buffer[offset++] = parentId.isEmpty() ? (byte) 0 : (byte) 1; + offset = parentId.toBytes(buffer, offset); buffer[offset++] = flags; buffer[offset++] = (byte) (discardable ? 1 : 0); ByteUtils.putLong(buffer, offset, clock.getOffset()); @@ -660,6 +663,12 @@ public void deserialize(byte[] buffer, @Nullable String serviceName, @Nullable S offset += traceId.fromBytes(buffer, offset); offset += id.fromBytes(buffer, offset); offset += transactionId.fromBytes(buffer, offset); + if (buffer[offset++] != 0) { + offset += parentId.fromBytes(buffer, offset); + } else { + parentId.resetState(); + offset += 8; + } flags = buffer[offset++]; discardable = buffer[offset++] == (byte) 1; clock.init(ByteUtils.getLong(buffer, offset)); @@ -672,6 +681,10 @@ public static long getSpanId(byte[] serializedTraceContext) { return ByteUtils.getLong(serializedTraceContext, 16); } + public static long getParentId(byte[] serializedTraceContext) { + return ByteUtils.getLong(serializedTraceContext, 33); + } + public boolean traceIdAndIdEquals(byte[] serialized) { return id.dataEquals(serialized, traceId.getLength()) && traceId.dataEquals(serialized, 0); } diff --git a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java index 551a74b8be..3042fee406 100644 --- a/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java +++ b/apm-agent-plugins/apm-profiling-plugin/src/main/java/co/elastic/apm/agent/profiler/CallTree.java @@ -79,9 +79,9 @@ public class CallTree implements Recyclable { * @see co.elastic.apm.agent.impl.transaction.AbstractSpan#childIds */ @Nullable - private LongList childIds; + private ChildList childIds; @Nullable - private LongList maybeChildIds; + private ChildList maybeChildIds; public CallTree() { } @@ -375,20 +375,21 @@ private void toString(Appendable out, int level) throws IOException { } } - int spanify(CallTree.Root root, TraceContext parentContext) { + int spanify(CallTree.Root root, TraceContext parentContext, TraceContext nonInferredParentContext) { int createdSpans = 0; if (activeContextOfDirectParent != null) { parentContext = activeContextOfDirectParent; + nonInferredParentContext = activeContextOfDirectParent; } Span span = null; if (!isPillar() || isLeaf()) { createdSpans++; - span = asSpan(root, parentContext); + span = asSpan(root, parentContext, nonInferredParentContext); this.isSpan = true; } List
* Overall, the allocation rate does not depend on the number of {@link ActivationEvent}s but only on
diff --git a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java
index aea204c257..738712e825 100644
--- a/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java
+++ b/apm-agent-plugins/apm-profiling-plugin/src/test/java/co/elastic/apm/agent/profiler/CallTreeSpanifyTest.java
@@ -35,6 +35,7 @@
import org.stagemonitor.configuration.ConfigurationRegistry;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -141,8 +142,62 @@ void testCallTreeWithActiveSpan() {
root.spanify();
assertThat(reporter.getSpans()).hasSize(2);
- assertThat(reporter.getSpans().get(1).getTraceContext().isChildOf(spanContext));
- assertThat(reporter.getSpans().get(0).getTraceContext().isChildOf(rootContext));
+ assertThat(reporter.getSpans().get(0).getTraceContext().isChildOf(spanContext)).isTrue();
+ assertThat(reporter.getSpans().get(1).getTraceContext().isChildOf(rootContext)).isTrue();
+ }
+
+
+ @Test
+ void testSpanWithInvertedActivation() {
+ TraceContext rootContext = CallTreeTest.rootTraceContext(tracer);
+
+ TraceContext childSpanContext = TraceContext.with64BitId(tracer);
+ TraceContext.fromParentContext().asChildOf(childSpanContext, rootContext);
+
+ NoopObjectPool