Skip to content

Commit a1ad28f

Browse files
authored
Implement Context.swap() on top of scope stacks and use it for Kotlin coroutines and Zio fibers (#8805)
1 parent 2fca456 commit a1ad28f

File tree

9 files changed

+150
-186
lines changed

9 files changed

+150
-186
lines changed

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

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package datadog.trace.instrumentation.kotlin.coroutines;
22

3+
import datadog.context.Context;
34
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
45
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
5-
import datadog.trace.bootstrap.instrumentation.api.ScopeState;
66
import kotlin.coroutines.CoroutineContext;
77
import kotlin.jvm.functions.Function2;
88
import kotlinx.coroutines.AbstractCoroutine;
@@ -11,7 +11,7 @@
1111
import org.jetbrains.annotations.Nullable;
1212

1313
/** Manages the Datadog context for coroutines, switching contexts as coroutines switch threads. */
14-
public final class DatadogThreadContextElement implements ThreadContextElement<ScopeState> {
14+
public final class DatadogThreadContextElement implements ThreadContextElement<Context> {
1515
private static final CoroutineContext.Key<DatadogThreadContextElement> DATADOG_KEY =
1616
new CoroutineContext.Key<DatadogThreadContextElement>() {};
1717

@@ -22,7 +22,7 @@ public static CoroutineContext addDatadogElement(CoroutineContext coroutineConte
2222
return coroutineContext.plus(new DatadogThreadContextElement());
2323
}
2424

25-
private ScopeState scopeState;
25+
private Context context;
2626
private AgentScope.Continuation continuation;
2727

2828
@NotNull
@@ -32,40 +32,38 @@ public Key<?> getKey() {
3232
}
3333

3434
public static void captureDatadogContext(@NotNull AbstractCoroutine<?> coroutine) {
35-
DatadogThreadContextElement datadogContext = coroutine.getContext().get(DATADOG_KEY);
36-
if (datadogContext != null && datadogContext.scopeState == null) {
37-
// copy scope stack to use for this coroutine
38-
datadogContext.scopeState = AgentTracer.get().oldScopeState().copy();
35+
DatadogThreadContextElement datadog = coroutine.getContext().get(DATADOG_KEY);
36+
if (datadog != null && datadog.context == null) {
37+
// record context to use for this coroutine
38+
datadog.context = Context.current();
3939
// stop enclosing trace from finishing early
40-
datadogContext.continuation = AgentTracer.captureActiveSpan();
40+
datadog.continuation = AgentTracer.captureActiveSpan();
4141
}
4242
}
4343

4444
public static void cancelDatadogContext(@NotNull AbstractCoroutine<?> coroutine) {
45-
DatadogThreadContextElement datadogContext = coroutine.getContext().get(DATADOG_KEY);
46-
if (datadogContext != null && datadogContext.continuation != null) {
45+
DatadogThreadContextElement datadog = coroutine.getContext().get(DATADOG_KEY);
46+
if (datadog != null && datadog.continuation != null) {
4747
// release enclosing trace now the coroutine has completed
48-
datadogContext.continuation.cancel();
48+
datadog.continuation.cancel();
4949
}
5050
}
5151

5252
@Override
53-
public ScopeState updateThreadContext(@NotNull CoroutineContext coroutineContext) {
54-
ScopeState oldState = AgentTracer.get().oldScopeState();
55-
if (scopeState == null) {
56-
// copy scope stack to use for this coroutine
57-
scopeState = oldState.copy();
53+
public Context updateThreadContext(@NotNull CoroutineContext coroutineContext) {
54+
if (context == null) {
55+
// record context to use for this coroutine
56+
context = Context.current();
5857
// stop enclosing trace from finishing early
5958
continuation = AgentTracer.captureActiveSpan();
6059
}
61-
scopeState.activate(); // swap in the coroutine's scope stack
62-
return oldState;
60+
return context.swap();
6361
}
6462

6563
@Override
6664
public void restoreThreadContext(
67-
@NotNull CoroutineContext coroutineContext, ScopeState oldState) {
68-
oldState.activate(); // swap bock the original scope stack
65+
@NotNull CoroutineContext coroutineContext, Context originalContext) {
66+
context = originalContext.swap();
6967
}
7068

7169
@NotNull

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,30 @@
22

33
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.captureActiveSpan;
44

5+
import datadog.context.Context;
56
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
6-
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
7-
import datadog.trace.bootstrap.instrumentation.api.ScopeState;
87

98
public class FiberContext {
10-
private final ScopeState scopeState;
9+
private Context context;
1110
private final AgentScope.Continuation continuation;
1211

13-
private ScopeState oldScopeState;
12+
private Context originalContext;
1413

1514
public FiberContext() {
16-
// copy scope stack to use for this fiber
17-
this.scopeState = AgentTracer.get().oldScopeState().copy();
15+
// record context to use for this coroutine
16+
this.context = Context.current();
1817
// stop enclosing trace from finishing early
1918
this.continuation = captureActiveSpan();
2019
}
2120

2221
public void onResume() {
23-
oldScopeState = AgentTracer.get().oldScopeState();
24-
scopeState.activate(); // swap in the fiber's scope stack
22+
originalContext = context.swap();
2523
}
2624

2725
public void onSuspend() {
28-
if (oldScopeState != null) {
29-
oldScopeState.activate(); // swap bock the original scope stack
30-
oldScopeState = null;
26+
if (originalContext != null) {
27+
context = originalContext.swap();
28+
originalContext = null;
3129
}
3230
}
3331

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
6161
import datadog.trace.bootstrap.instrumentation.api.BlackHoleSpan;
6262
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
63-
import datadog.trace.bootstrap.instrumentation.api.ScopeState;
6463
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
6564
import datadog.trace.bootstrap.instrumentation.api.SpanLink;
6665
import datadog.trace.bootstrap.instrumentation.api.TagContext;
@@ -294,16 +293,6 @@ public EndpointTracker onRootSpanStarted(AgentSpan root) {
294293
return null;
295294
}
296295

297-
@Override
298-
public ScopeState oldScopeState() {
299-
return scopeManager.oldScopeState();
300-
}
301-
302-
@Override
303-
public ScopeState newScopeState() {
304-
return scopeManager.newScopeState();
305-
}
306-
307296
public static class CoreTracerBuilder {
308297

309298
private Config config;

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

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
2525
import datadog.trace.bootstrap.instrumentation.api.ProfilerContext;
2626
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
27-
import datadog.trace.bootstrap.instrumentation.api.ScopeState;
28-
import datadog.trace.bootstrap.instrumentation.api.ScopeStateAware;
2927
import datadog.trace.core.monitor.HealthMetrics;
3028
import datadog.trace.relocate.api.RatelimitedLogger;
3129
import datadog.trace.util.AgentTaskScheduler;
@@ -45,7 +43,7 @@
4543
* from being reported even if all related spans are finished. It also delegates to other
4644
* ScopeInterceptors to provide additional functionality.
4745
*/
48-
public final class ContinuableScopeManager implements ScopeStateAware, ContextManager {
46+
public final class ContinuableScopeManager implements ContextManager {
4947

5048
static final Logger log = LoggerFactory.getLogger(ContinuableScopeManager.class);
5149
static final RatelimitedLogger ratelimitedLog = new RatelimitedLogger(log, 1, MINUTES);
@@ -349,16 +347,6 @@ ScopeStack scopeStack() {
349347
return this.tlsScopeStack.get();
350348
}
351349

352-
@Override
353-
public ScopeState oldScopeState() {
354-
return new ContinuableScopeState(tlsScopeStack.get());
355-
}
356-
357-
@Override
358-
public ScopeState newScopeState() {
359-
return new ContinuableScopeState(tlsScopeStack.initialValue());
360-
}
361-
362350
@Override
363351
public Context current() {
364352
final ContinuableScope active = scopeStack().active();
@@ -372,31 +360,33 @@ public ContextScope attach(Context context) {
372360

373361
@Override
374362
public Context swap(Context context) {
375-
throw new UnsupportedOperationException("Not yet implemented");
376-
}
377-
378-
private class ContinuableScopeState implements ScopeState {
379-
private final ScopeStack localScopeStack;
380-
381-
ContinuableScopeState(ScopeStack scopeStack) {
382-
this.localScopeStack = scopeStack;
363+
ScopeStack oldStack = tlsScopeStack.get();
364+
ContinuableScope oldScope = oldStack.top;
365+
366+
ScopeStack newStack;
367+
ContinuableScope newScope;
368+
if (context instanceof ScopeContext) {
369+
// restore previously swapped context stack
370+
newStack = ((ScopeContext) context).scopeStack;
371+
newScope = newStack.top;
372+
} else if (context != Context.root()) {
373+
// start a new stack and record the new context as active
374+
newStack = new ScopeStack(profilingContextIntegration);
375+
newScope = new ContinuableScope(this, context, CONTEXT, true, createScopeState(context));
376+
newStack.top = newScope;
377+
} else {
378+
// start a new stack with no active context
379+
newStack = new ScopeStack(profilingContextIntegration);
380+
newScope = null;
383381
}
384382

385-
@Override
386-
public void activate() {
387-
ContinuableScope oldScope = tlsScopeStack.get().top;
388-
tlsScopeStack.set(localScopeStack);
389-
ContinuableScope newScope = localScopeStack.top;
390-
if (oldScope != newScope && newScope != null) {
391-
newScope.beforeActivated();
392-
newScope.afterActivated();
393-
}
383+
tlsScopeStack.set(newStack);
384+
if (oldScope != newScope && newScope != null) {
385+
newScope.beforeActivated();
386+
newScope.afterActivated();
394387
}
395388

396-
@Override
397-
public ScopeState copy() {
398-
return new ContinuableScopeState(localScopeStack.copy());
399-
}
389+
return new ScopeContext(oldStack);
400390
}
401391

402392
static final class ScopeStackThreadLocal extends ThreadLocal<ScopeStack> {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package datadog.trace.core.scopemanager;
2+
3+
import datadog.context.Context;
4+
import datadog.context.ContextKey;
5+
import javax.annotation.Nullable;
6+
7+
/** Wraps a {@link ScopeStack} as a {@link Context} so it can be swapped back later. */
8+
final class ScopeContext implements Context {
9+
final ScopeStack scopeStack;
10+
private final Context context;
11+
12+
ScopeContext(ScopeStack scopeStack) {
13+
this(scopeStack, scopeStack.top != null ? scopeStack.top.context : Context.root());
14+
}
15+
16+
private ScopeContext(ScopeStack scopeStack, Context context) {
17+
this.scopeStack = scopeStack;
18+
this.context = context;
19+
}
20+
21+
@Nullable
22+
@Override
23+
public <T> T get(ContextKey<T> key) {
24+
return context.get(key);
25+
}
26+
27+
@Override
28+
public <T> Context with(ContextKey<T> key, @Nullable T value) {
29+
return context.with(key, value);
30+
}
31+
}

0 commit comments

Comments
 (0)