-
Notifications
You must be signed in to change notification settings - Fork 349
Expand file tree
/
Copy pathDDTraceIdClinitDeadlockForkedTest.java
More file actions
88 lines (81 loc) · 3.65 KB
/
Copy pathDDTraceIdClinitDeadlockForkedTest.java
File metadata and controls
88 lines (81 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package datadog.trace.api;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
/**
* Regression test for the {@code DDTraceId} <-> {@code DD64bTraceId} class-initialization
* deadlock.
*
* <p>{@code DD64bTraceId} is a subclass of {@code DDTraceId}, so the JVM must initialize {@code
* DDTraceId} before {@code DD64bTraceId}. The bug was that {@code DDTraceId.<clinit>} in turn
* initialized {@code DD64bTraceId} by building its {@code ZERO}/{@code ONE} constants via {@code
* DD64bTraceId.from(...)}. When the two classes were first touched concurrently from opposite ends
* (one thread initializing {@code DDTraceId}, another initializing {@code DD64bTraceId}), each
* thread held one class-initialization lock and waited for the other, hanging trace creation. This
* surfaced as 30s {@code LogInjectionSmokeTest} timeouts in CI.
*
* <p>{@code DDTraceId.ZERO}/{@code ONE} are now instances of a private sibling type (not {@code
* DD64bTraceId}), so {@code DDTraceId.<clinit>} no longer references {@code DD64bTraceId} and the
* cycle is gone. This test initializes the two classes for the first time concurrently from
* opposite ends and asserts neither thread hangs.
*
* <p>Runs forked ({@code forkEvery = 1}) so it gets a fresh JVM in which these classes have not yet
* been initialized by another test. Without the fix it deadlocks and fails via the join check (and
* the {@code @Timeout} backstop); with the fix it completes immediately.
*/
class DDTraceIdClinitDeadlockForkedTest {
@Test
@Timeout(value = 60, unit = SECONDS) // backstop; the join below is the primary guard
void traceIdClassPairInitializesConcurrentlyWithoutDeadlock() throws Exception {
final ClassLoader cl = getClass().getClassLoader();
final CyclicBarrier barrier = new CyclicBarrier(2);
final AtomicReference<Throwable> error = new AtomicReference<>();
// One thread enters via the superclass (mirrors blackholeSpan() -> DDTraceId.ZERO), the other
// via the subclass (mirrors IdGenerationStrategy.generateTraceId() -> DD64bTraceId.from()).
Thread viaSuper =
new Thread(
() -> {
try {
barrier.await();
Class.forName("datadog.trace.api.DDTraceId", true, cl);
} catch (Throwable t) {
error.compareAndSet(null, t);
}
},
"init-DDTraceId");
Thread viaSub =
new Thread(
() -> {
try {
barrier.await();
Class.forName("datadog.trace.api.DD64bTraceId", true, cl);
} catch (Throwable t) {
error.compareAndSet(null, t);
}
},
"init-DD64bTraceId");
// Daemon so a deadlock cannot block forked-JVM shutdown.
viaSuper.setDaemon(true);
viaSub.setDaemon(true);
viaSuper.start();
viaSub.start();
viaSuper.join(SECONDS.toMillis(15));
viaSub.join(SECONDS.toMillis(15));
if (viaSuper.isAlive() || viaSub.isAlive()) {
fail(
"DDTraceId/DD64bTraceId class-initialization deadlock: DDTraceId.<clinit> must not "
+ "reference DD64bTraceId (init-DDTraceId.alive="
+ viaSuper.isAlive()
+ ", init-DD64bTraceId.alive="
+ viaSub.isAlive()
+ ").");
}
if (error.get() != null) {
throw new AssertionError(
"Unexpected error during concurrent class initialization", error.get());
}
}
}