Skip to content

Commit f709474

Browse files
committed
Refactor & tests
Refactored the code to clean-up the edge case handling, quick return on interceptor empty and trace empty. That reduced the nesting level and made the code easier to read. Also flipped the code that handles unaltered lists, that reads a bit better. Added tests for all these cases to CoreTracerTest2. To make this easier to test in isolation, created a static interceptCompleteTrace that can be called directly by the test methods.
1 parent 135a03d commit f709474

File tree

2 files changed

+165
-40
lines changed

2 files changed

+165
-40
lines changed

dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ public AgentDataStreamsMonitoring getDataStreamsMonitoring() {
12111211
return dataStreamsMonitoring;
12121212
}
12131213

1214-
private final RatelimitedLogger rlLog = new RatelimitedLogger(log, 1, MINUTES);
1214+
private static final RatelimitedLogger rlLog = new RatelimitedLogger(log, 1, MINUTES);
12151215

12161216
/**
12171217
* We use the sampler to know if the trace has to be reported/written. The sampler is called on
@@ -1257,49 +1257,57 @@ void write(final SpanList trace) {
12571257
}
12581258

12591259
private List<DDSpan> interceptCompleteTrace(SpanList originalTrace) {
1260-
if (!interceptors.isEmpty() && !originalTrace.isEmpty()) {
1261-
// Using TraceList to optimize the common case where the interceptors,
1262-
// don't alter the list. If the interceptors just return the provided
1263-
// List, then no need to copy to another List.
1264-
1265-
// As an extra precaution, also check the modCount before and after on
1266-
// the TraceList, since TraceInterceptor could put some other type of
1267-
// object into the List.
1268-
1269-
// There is still a risk that a TraceInterceptor holds onto the provided
1270-
// List and modifies it later on, but we cannot safeguard against
1271-
// every possible misuse.
1272-
Collection<? extends MutableSpan> interceptedTrace = originalTrace;
1273-
int originalModCount = originalTrace.modCount();
1274-
1275-
for (final TraceInterceptor interceptor : interceptors) {
1276-
try {
1277-
// If one TraceInterceptor throws an exception, then continue with the next one
1278-
interceptedTrace = interceptor.onTraceComplete(interceptedTrace);
1279-
} catch (Throwable e) {
1280-
String interceptorName = interceptor.getClass().getName();
1281-
rlLog.warn("Throwable raised in TraceInterceptor {}", interceptorName, e);
1282-
}
1283-
if (interceptedTrace == null) {
1284-
interceptedTrace = emptyList();
1285-
}
1260+
return interceptCompleteTrace(interceptors, originalTrace);
1261+
}
1262+
1263+
static final List<DDSpan> interceptCompleteTrace(
1264+
SortedSet<TraceInterceptor> interceptors, SpanList originalTrace) {
1265+
if (interceptors.isEmpty()) {
1266+
return originalTrace;
1267+
}
1268+
if (originalTrace.isEmpty()) {
1269+
return SpanList.EMPTY;
1270+
}
1271+
1272+
// Using TraceList to optimize the common case where the interceptors,
1273+
// don't alter the list. If the interceptors just return the provided
1274+
// List, then no need to copy to another List.
1275+
1276+
// As an extra precaution, also check the modCount before and after on
1277+
// the TraceList, since TraceInterceptor could put some other type of
1278+
// object into the List.
1279+
1280+
// There is still a risk that a TraceInterceptor holds onto the provided
1281+
// List and modifies it later on, but we cannot safeguard against
1282+
// every possible misuse.
1283+
Collection<? extends MutableSpan> interceptedTrace = originalTrace;
1284+
int originalModCount = originalTrace.modCount();
1285+
1286+
for (final TraceInterceptor interceptor : interceptors) {
1287+
try {
1288+
// If one TraceInterceptor throws an exception, then continue with the next one
1289+
interceptedTrace = interceptor.onTraceComplete(interceptedTrace);
1290+
} catch (Throwable e) {
1291+
String interceptorName = interceptor.getClass().getName();
1292+
rlLog.warn("Throwable raised in TraceInterceptor {}", interceptorName, e);
1293+
}
1294+
if (interceptedTrace == null) {
1295+
interceptedTrace = emptyList();
12861296
}
1297+
}
12871298

1288-
if (interceptedTrace == null || interceptedTrace.isEmpty()) {
1289-
return SpanList.EMPTY;
1290-
} else if (interceptedTrace != originalTrace || originalTrace.modCount() != originalModCount) {
1291-
SpanList trace = new SpanList(interceptedTrace.size());
1292-
for (final MutableSpan span : interceptedTrace) {
1293-
if (span instanceof DDSpan) {
1294-
trace.add((DDSpan) span);
1295-
}
1299+
if (interceptedTrace == null || interceptedTrace.isEmpty()) {
1300+
return SpanList.EMPTY;
1301+
} else if (interceptedTrace == originalTrace && originalTrace.modCount() == originalModCount) {
1302+
return originalTrace;
1303+
} else {
1304+
SpanList trace = new SpanList(interceptedTrace.size());
1305+
for (final MutableSpan span : interceptedTrace) {
1306+
if (span instanceof DDSpan) {
1307+
trace.add((DDSpan) span);
12961308
}
1297-
return trace;
1298-
} else {
1299-
return originalTrace;
13001309
}
1301-
} else {
1302-
return originalTrace;
1310+
return trace;
13031311
}
13041312
}
13051313

dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest2.java

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88
import static org.junit.jupiter.api.Assertions.assertTrue;
99

1010
import datadog.trace.api.Config;
11+
import datadog.trace.api.interceptor.MutableSpan;
12+
import datadog.trace.api.interceptor.TraceInterceptor;
1113
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
1214
import datadog.trace.core.CoreTracer.CoreSpanBuilder;
1315
import datadog.trace.core.CoreTracer.ReusableSingleSpanBuilder;
1416
import datadog.trace.core.CoreTracer.ReusableSingleSpanBuilderThreadLocalCache;
17+
import java.util.Collection;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.SortedSet;
21+
import java.util.TreeSet;
1522
import org.junit.jupiter.api.Test;
1623

1724
// named CoreTracerTest2 to avoid collision with Groovy which appears to have messed up test
@@ -179,4 +186,114 @@ public void start_not_inUse() {
179186
ReusableSingleSpanBuilder builder = new ReusableSingleSpanBuilder(TRACER);
180187
assertThrows(AssertionError.class, () -> builder.start());
181188
}
189+
190+
@Test
191+
public void noInterceptorsTest() {
192+
// No interceptors should return the original list to avoid allocation
193+
DDSpan span = (DDSpan) TRACER.startSpan("foo", "foo");
194+
195+
SpanList list = SpanList.of(span);
196+
List<DDSpan> interceptedList =
197+
CoreTracer.interceptCompleteTrace(Collections.emptySortedSet(), list);
198+
assertSame(list, interceptedList);
199+
}
200+
201+
@Test
202+
public void interceptNoInterceptors() {
203+
// No interceptors should return the original list to avoid allocation
204+
DDSpan span = (DDSpan) TRACER.startSpan("foo", "foo");
205+
206+
SpanList list = SpanList.of(span);
207+
List<DDSpan> interceptedList =
208+
CoreTracer.interceptCompleteTrace(Collections.emptySortedSet(), list);
209+
assertSame(list, interceptedList);
210+
}
211+
212+
@Test
213+
public void interceptEmptyList() {
214+
DDSpan span = (DDSpan) TRACER.startSpan("foo", "foo");
215+
SortedSet<TraceInterceptor> interceptors = interceptors((list) -> SpanList.of(span));
216+
217+
SpanList list = new SpanList(0); // not using EMPTY deliberately
218+
List<DDSpan> interceptedList = CoreTracer.interceptCompleteTrace(interceptors, list);
219+
assertTrue(interceptedList.isEmpty());
220+
}
221+
222+
@Test
223+
public void interceptUnchanged() {
224+
SortedSet<TraceInterceptor> interceptors = interceptors((list) -> list, (list) -> list);
225+
226+
DDSpan span = (DDSpan) TRACER.startSpan("foo", "foo");
227+
SpanList list = SpanList.of(span);
228+
List<DDSpan> interceptedList = CoreTracer.interceptCompleteTrace(interceptors, list);
229+
assertSame(list, interceptedList);
230+
}
231+
232+
@Test
233+
public void interceptNewList() {
234+
DDSpan substituteSpan = (DDSpan) TRACER.startSpan("sub", "sub");
235+
SpanList substituteList = SpanList.of(substituteSpan);
236+
SortedSet<TraceInterceptor> interceptors =
237+
interceptors((list) -> list, (list) -> substituteList);
238+
239+
DDSpan span = (DDSpan) TRACER.startSpan("foo", "foo");
240+
SpanList list = SpanList.of(span);
241+
List<DDSpan> interceptedList = CoreTracer.interceptCompleteTrace(interceptors, list);
242+
assertEquals(1, interceptedList.size());
243+
assertEquals("sub", interceptedList.get(0).getOperationName());
244+
}
245+
246+
@Test
247+
public void interceptAlteredList() {
248+
// This is an unlikely case and arguably not something we need to support
249+
250+
DDSpan substituteSpan = (DDSpan) TRACER.startSpan("sub", "sub");
251+
SortedSet<TraceInterceptor> interceptors =
252+
interceptors(
253+
(list) -> list,
254+
(list) -> {
255+
List erasedList = (List) list;
256+
erasedList.clear();
257+
erasedList.add(substituteSpan);
258+
return erasedList;
259+
});
260+
261+
DDSpan span = (DDSpan) TRACER.startSpan("foo", "foo");
262+
SpanList list = SpanList.of(span);
263+
List<DDSpan> interceptedList = CoreTracer.interceptCompleteTrace(interceptors, list);
264+
assertNotSame(interceptedList, list);
265+
assertEquals(1, interceptedList.size());
266+
assertEquals("sub", interceptedList.get(0).getOperationName());
267+
}
268+
269+
static final SortedSet<TraceInterceptor> interceptors(TestInterceptor... interceptors) {
270+
TreeSet<TraceInterceptor> interceptorSet =
271+
new TreeSet<>((lhs, rhs) -> Integer.compare(lhs.priority(), rhs.priority()));
272+
for (int i = 0; i < interceptors.length; ++i) {
273+
int priority = i;
274+
TestInterceptor interceptor = interceptors[i];
275+
276+
interceptorSet.add(
277+
new TraceInterceptor() {
278+
@Override
279+
public int priority() {
280+
return priority;
281+
}
282+
283+
@Override
284+
public Collection<? extends MutableSpan> onTraceComplete(
285+
Collection<? extends MutableSpan> trace) {
286+
return interceptor.onTraceComplete(trace);
287+
}
288+
});
289+
}
290+
return interceptorSet;
291+
}
292+
293+
// Matches TraceInterceptor but priority is implied in interceptors
294+
// Only having onTraceComplete allows this to @FunctionalInterface and a little nicer for a test
295+
@FunctionalInterface
296+
interface TestInterceptor {
297+
Collection<? extends MutableSpan> onTraceComplete(Collection<? extends MutableSpan> trace);
298+
}
182299
}

0 commit comments

Comments
 (0)