Skip to content

Commit e42b7c8

Browse files
committed
Akka/Pekko actor instrumentations use the scope checkpoint and rollback mechanism to clean up scopes after async operations. To properly handle reentrant scope checkpointing and rollback we need to use a count in ContinuableScope like referenceCount rather than just a flag.
This is necessary because ContinuableScopeManager re-uses scope instances when activating the same span twice. (cherry picked from commit 8b77a18)
1 parent 2640c2b commit e42b7c8

File tree

2 files changed

+67
-18
lines changed

2 files changed

+67
-18
lines changed

dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScope.java

+10-18
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@ class ContinuableScope implements AgentScope {
1919

2020
final Context context; // package-private so scopeManager can access it directly
2121

22-
/** Flag that this scope should be allowed to propagate across async boundaries. */
23-
private static final byte ASYNC_PROPAGATING = 1;
22+
private boolean asyncPropagating;
2423

25-
/** Flag that we intend to roll back the scope stack to this scope in the future. */
26-
private static final byte CHECKPOINTED = 2;
27-
28-
private byte flags;
24+
private short checkpointCount = 0;
2925

3026
private final byte source;
3127

@@ -37,12 +33,12 @@ class ContinuableScope implements AgentScope {
3733
final ContinuableScopeManager scopeManager,
3834
final Context context,
3935
final byte source,
40-
final boolean isAsyncPropagating,
36+
final boolean asyncPropagating,
4137
final Stateful scopeState) {
4238
this.scopeManager = scopeManager;
4339
this.context = context;
4440
this.source = source;
45-
this.flags = isAsyncPropagating ? ASYNC_PROPAGATING : 0;
41+
this.asyncPropagating = asyncPropagating;
4642
this.scopeState = scopeState;
4743
}
4844

@@ -121,7 +117,7 @@ final boolean alive() {
121117

122118
@Override
123119
public final boolean isAsyncPropagating() {
124-
return (flags & ASYNC_PROPAGATING) != 0;
120+
return asyncPropagating;
125121
}
126122

127123
@Override
@@ -136,11 +132,7 @@ public Context context() {
136132

137133
@Override
138134
public final void setAsyncPropagation(final boolean value) {
139-
if (value) {
140-
flags |= ASYNC_PROPAGATING;
141-
} else {
142-
flags &= ~ASYNC_PROPAGATING;
143-
}
135+
asyncPropagating = value;
144136
}
145137

146138
@Override
@@ -149,13 +141,13 @@ public final String toString() {
149141
}
150142

151143
public void checkpoint() {
152-
flags |= CHECKPOINTED;
144+
checkpointCount++;
153145
}
154146

155147
public boolean rollback() {
156-
if ((flags & CHECKPOINTED) != 0) {
157-
flags &= ~CHECKPOINTED;
158-
return false;
148+
if (checkpointCount > 0) {
149+
checkpointCount--;
150+
return false; // stop rollback at checkpoint
159151
} else {
160152
return true;
161153
}

dd-trace-core/src/test/groovy/datadog/trace/core/scopemanager/ScopeManagerTest.groovy

+57
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,63 @@ class ScopeManagerTest extends DDCoreSpecification {
11571157
scopeManager.activeSpan() == null
11581158
}
11591159

1160+
def "rollback stops at most recent checkpoint"() {
1161+
when:
1162+
def span1 = tracer.buildSpan("test1", "test1").start()
1163+
def span2 = tracer.buildSpan("test2", "test2").start()
1164+
def span3 = tracer.buildSpan("test3", "test3").start()
1165+
then:
1166+
scopeManager.activeSpan() == null
1167+
1168+
when:
1169+
tracer.checkpointActiveForRollback()
1170+
tracer.activateSpan(span1)
1171+
tracer.checkpointActiveForRollback()
1172+
tracer.activateSpan(span2)
1173+
tracer.checkpointActiveForRollback()
1174+
tracer.activateSpan(span1)
1175+
tracer.checkpointActiveForRollback()
1176+
tracer.activateSpan(span2)
1177+
tracer.checkpointActiveForRollback()
1178+
tracer.activateSpan(span2)
1179+
tracer.checkpointActiveForRollback()
1180+
tracer.activateSpan(span1)
1181+
tracer.activateSpan(span2)
1182+
tracer.activateSpan(span3)
1183+
then:
1184+
scopeManager.activeSpan() == span3
1185+
1186+
when:
1187+
tracer.rollbackActiveToCheckpoint()
1188+
then:
1189+
scopeManager.activeSpan() == span2
1190+
1191+
when:
1192+
tracer.rollbackActiveToCheckpoint()
1193+
then:
1194+
scopeManager.activeSpan() == span2
1195+
1196+
when:
1197+
tracer.rollbackActiveToCheckpoint()
1198+
then:
1199+
scopeManager.activeSpan() == span1
1200+
1201+
when:
1202+
tracer.rollbackActiveToCheckpoint()
1203+
then:
1204+
scopeManager.activeSpan() == span2
1205+
1206+
when:
1207+
tracer.rollbackActiveToCheckpoint()
1208+
then:
1209+
scopeManager.activeSpan() == span1
1210+
1211+
when:
1212+
tracer.rollbackActiveToCheckpoint()
1213+
then:
1214+
scopeManager.activeSpan() == null
1215+
}
1216+
11601217
boolean spanFinished(AgentSpan span) {
11611218
return ((DDSpan) span)?.isFinished()
11621219
}

0 commit comments

Comments
 (0)