Skip to content

Commit e8a2156

Browse files
authored
Take defensive copy of parent scope stack when closing nested coroutines (#8749)
* Take defensive copy of parent scope stack when closing nested coroutines * Only copy when we detect the parent scope stack is on a different thread
1 parent 37f0190 commit e8a2156

File tree

9 files changed

+52
-20
lines changed

9 files changed

+52
-20
lines changed

dd-java-agent/instrumentation/kotlin-coroutines/src/main/java/datadog/trace/instrumentation/kotlin/coroutines/ScopeStateCoroutineContext.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ public void restoreThreadContext(
3939

4040
@Override
4141
public ScopeState updateThreadContext(@NotNull final CoroutineContext coroutineContext) {
42-
final ScopeState oldScopeState = AgentTracer.get().newScopeState();
43-
oldScopeState.fetchFromActive();
42+
final ScopeState oldScopeState = AgentTracer.get().oldScopeState();
4443

4544
final Job coroutine = CoroutineContextHelper.getJob(coroutineContext);
4645
final ScopeStateCoroutineContextItem contextItem = contextItemPerCoroutine.get(coroutine);
@@ -53,22 +52,20 @@ public ScopeState updateThreadContext(@NotNull final CoroutineContext coroutineC
5352

5453
/** If there's a context item for the coroutine then try to close it */
5554
public void maybeCloseScopeAndCancelContinuation(final Job coroutine, final Job parent) {
56-
final ScopeStateCoroutineContextItem contextItem = contextItemPerCoroutine.get(coroutine);
55+
final ScopeStateCoroutineContextItem contextItem = contextItemPerCoroutine.remove(coroutine);
5756
if (contextItem != null) {
5857
ScopeState currentThreadScopeState = null;
5958
if (parent != null) {
6059
final ScopeStateCoroutineContextItem parentItem = contextItemPerCoroutine.get(parent);
6160
if (parentItem != null) {
62-
currentThreadScopeState = parentItem.getScopeState();
61+
currentThreadScopeState = parentItem.maybeCopyScopeState();
6362
}
6463
}
6564
if (currentThreadScopeState == null) {
66-
currentThreadScopeState = AgentTracer.get().newScopeState();
67-
currentThreadScopeState.fetchFromActive();
65+
currentThreadScopeState = AgentTracer.get().oldScopeState();
6866
}
6967

7068
contextItem.maybeCloseScopeAndCancelContinuation();
71-
contextItemPerCoroutine.remove(coroutine);
7269

7370
currentThreadScopeState.activate();
7471
}
@@ -111,16 +108,23 @@ public static class ScopeStateCoroutineContextItem {
111108
@Nullable private AgentScope.Continuation continuation;
112109
@Nullable private AgentScope continuationScope;
113110
private boolean isInitialized = false;
111+
private volatile Thread activatedOn;
114112

115113
public ScopeStateCoroutineContextItem() {
116114
coroutineScopeState = AgentTracer.get().newScopeState();
117115
}
118116

119-
public ScopeState getScopeState() {
120-
return coroutineScopeState;
117+
public ScopeState maybeCopyScopeState() {
118+
// take defensive copy of scope stack if on different thread
119+
if (activatedOn != null && activatedOn != Thread.currentThread()) {
120+
return coroutineScopeState.copy();
121+
} else {
122+
return coroutineScopeState;
123+
}
121124
}
122125

123126
public void activate() {
127+
activatedOn = Thread.currentThread();
124128
coroutineScopeState.activate();
125129

126130
if (continuation != null && continuationScope == null) {
@@ -144,6 +148,7 @@ public void maybeInitialize() {
144148
* scope and cancels the continuation.
145149
*/
146150
public void maybeCloseScopeAndCancelContinuation() {
151+
// only temporary activation, will be replaced by another activate in caller
147152
coroutineScopeState.activate();
148153

149154
if (continuationScope != null) {

dd-java-agent/instrumentation/zio/zio-2.0/src/main/java/datadog/trace/instrumentation/zio/v2_0/FiberContext.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ public void onSuspend() {
5252
}
5353

5454
public void onResume() {
55-
this.oldState = AgentTracer.get().newScopeState();
56-
this.oldState.fetchFromActive();
55+
this.oldState = AgentTracer.get().oldScopeState();
5756

5857
this.state.activate();
5958

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

+5
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ public EndpointTracker onRootSpanStarted(AgentSpan root) {
294294
return null;
295295
}
296296

297+
@Override
298+
public ScopeState oldScopeState() {
299+
return scopeManager.oldScopeState();
300+
}
301+
297302
@Override
298303
public ScopeState newScopeState() {
299304
return scopeManager.newScopeState();

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

+12-4
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,14 @@ ScopeStack scopeStack() {
349349
return this.tlsScopeStack.get();
350350
}
351351

352+
@Override
353+
public ScopeState oldScopeState() {
354+
return new ContinuableScopeState(tlsScopeStack.get());
355+
}
356+
352357
@Override
353358
public ScopeState newScopeState() {
354-
return new ContinuableScopeState();
359+
return new ContinuableScopeState(tlsScopeStack.initialValue());
355360
}
356361

357362
@Override
@@ -371,17 +376,20 @@ public Context swap(Context context) {
371376
}
372377

373378
private class ContinuableScopeState implements ScopeState {
379+
private final ScopeStack localScopeStack;
374380

375-
private ScopeStack localScopeStack = tlsScopeStack.initialValue();
381+
ContinuableScopeState(ScopeStack scopeStack) {
382+
this.localScopeStack = scopeStack;
383+
}
376384

377385
@Override
378386
public void activate() {
379387
tlsScopeStack.set(localScopeStack);
380388
}
381389

382390
@Override
383-
public void fetchFromActive() {
384-
localScopeStack = tlsScopeStack.get();
391+
public ScopeState copy() {
392+
return new ContinuableScopeState(localScopeStack.copy());
385393
}
386394
}
387395

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

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ final class ScopeStack {
2424
this.profilingContextIntegration = profilingContextIntegration;
2525
}
2626

27+
ScopeStack copy() {
28+
ScopeStack copy = new ScopeStack(profilingContextIntegration);
29+
copy.stack.addAll(stack);
30+
copy.top = top;
31+
copy.overdueRootScope = overdueRootScope;
32+
return copy;
33+
}
34+
2735
ContinuableScope active() {
2836
// avoid attaching further spans to the root scope when it's been marked as overdue
2937
return top != overdueRootScope ? top : null;

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ class ScopeManagerTest extends DDCoreSpecification {
7272

7373
def "scope state should be able to fetch and activate state when there is no active span"() {
7474
when:
75-
def initialScopeState = scopeManager.newScopeState()
76-
initialScopeState.fetchFromActive()
75+
def initialScopeState = scopeManager.oldScopeState()
7776

7877
then:
7978
scopeManager.active() == null
@@ -125,8 +124,7 @@ class ScopeManagerTest extends DDCoreSpecification {
125124
when:
126125
def span = tracer.buildSpan("test", "test").start()
127126
def scope = tracer.activateSpan(span)
128-
def initialScopeState = scopeManager.newScopeState()
129-
initialScopeState.fetchFromActive()
127+
def initialScopeState = scopeManager.oldScopeState()
130128

131129
then:
132130
scope.span() == span

internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java

+5
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,11 @@ public AgentSpanContext notifyExtensionStart(Object event) {
638638
@Override
639639
public void notifyExtensionEnd(AgentSpan span, Object result, boolean isError) {}
640640

641+
@Override
642+
public ScopeState oldScopeState() {
643+
return null;
644+
}
645+
641646
@Override
642647
public ScopeState newScopeState() {
643648
return null;

internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ScopeState.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
public interface ScopeState {
44
void activate();
55

6-
void fetchFromActive();
6+
ScopeState copy();
77
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package datadog.trace.bootstrap.instrumentation.api;
22

33
public interface ScopeStateAware {
4+
/** @return The old connected scope stack */
5+
ScopeState oldScopeState();
6+
7+
/** @return A new disconnected scope stack */
48
ScopeState newScopeState();
59
}

0 commit comments

Comments
 (0)