Skip to content

Commit b05f030

Browse files
committed
Preserve old implementation when legacy manager is disabled
1 parent e22b0c6 commit b05f030

12 files changed

Lines changed: 103 additions & 288 deletions

File tree

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,63 @@
11
package datadog.trace.bootstrap.instrumentation.java.lang;
22

33
import datadog.context.Context;
4+
import datadog.trace.api.InstrumenterConfig;
45
import datadog.trace.bootstrap.instrumentation.api.AgentScope.Continuation;
56
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
67

78
/**
8-
* The virtual thread's scope stack lives in a virtual-thread-aware {@code ThreadLocal}, so it is
9-
* seeded once on the first mount and then follows the thread across park/unpark and carrier
10-
* migration on its own. ddprof's profiler context is keyed by carrier thread instead, so it is
11-
* re-bound on mount and cleared on unmount when a carrier-bound profiling integration is active.
9+
* Holds the context and continuation for a virtual thread.
10+
*
11+
* <p>With the legacy context manager, {@code swap()} rebuilds the whole scope stack on every
12+
* mount/unmount, which is costly on the virtual-thread park/unpark hot path. There the context is
13+
* seeded once on the first mount — it then follows the thread across park/unpark and carrier
14+
* migration via its virtual-thread-aware {@code ThreadLocal} scope stack — and the profiler context
15+
* (which is keyed by carrier thread) is re-applied on each subsequent mount and cleared on unmount.
16+
*
17+
* <p>With the new context manager {@code swap()} is cheap and drives the profiler through its
18+
* context listener, so we simply swap in on mount and out on unmount.
1219
*/
1320
public final class VirtualThreadState {
14-
private Context seedContext;
15-
// Keeps the enclosing trace alive until the virtual thread finishes.
21+
private static final boolean LEGACY_CONTEXT_MANAGER =
22+
InstrumenterConfig.get().isLegacyContextManagerEnabled();
23+
24+
private Context context;
1625
private final Continuation continuation;
26+
private Context previousContext;
1727
private boolean seeded;
1828

19-
public VirtualThreadState(Context seedContext, Continuation continuation) {
20-
this.seedContext = seedContext;
29+
public VirtualThreadState(Context context, Continuation continuation) {
30+
this.context = context;
2131
this.continuation = continuation;
2232
}
2333

2434
public void onMount() {
25-
if (!seeded) {
26-
// First mount also applies the profiler context to the carrier.
27-
seedContext.swap();
28-
seeded = true;
29-
seedContext = null;
35+
if (LEGACY_CONTEXT_MANAGER) {
36+
if (!seeded) {
37+
// First mount also applies the profiler context to the carrier.
38+
context.swap();
39+
seeded = true;
40+
context = null;
41+
} else {
42+
AgentTracer.get().getProfilingContext().setContext(Context.current());
43+
}
3044
} else {
31-
AgentTracer.get().rebindProfilingContextToCarrier();
45+
previousContext = context.swap();
3246
}
3347
}
3448

3549
public void onUnmount() {
36-
AgentTracer.get().unbindProfilingContextFromCarrier();
50+
if (LEGACY_CONTEXT_MANAGER) {
51+
AgentTracer.get().getProfilingContext().clearContext();
52+
} else if (previousContext != null) {
53+
context = previousContext.swap();
54+
previousContext = null;
55+
}
3756
}
3857

3958
public void onTerminate() {
40-
if (this.continuation != null) {
41-
this.continuation.cancel();
59+
if (continuation != null) {
60+
continuation.cancel();
4261
}
4362
}
4463
}

‎dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.datadog.profiling.ddprof;
22

3+
import datadog.context.Context;
34
import datadog.trace.api.EndpointTracker;
45
import datadog.trace.api.Stateful;
56
import datadog.trace.api.profiling.ProfilingContextAttribute;
@@ -79,10 +80,14 @@ public String name() {
7980
}
8081

8182
@Override
82-
public boolean isCarrierThreadBound() {
83-
return true;
83+
public void setContext(Context context) {
84+
AgentSpan span = AgentSpan.fromContext(context);
85+
if (span != null) {
86+
contextManager.activate(span.spanContext());
87+
}
8488
}
8589

90+
@Override
8691
public void clearContext() {
8792
DDPROF.clearSpanContext();
8893
DDPROF.clearContextValue(SPAN_NAME_INDEX);

‎dd-java-agent/instrumentation/java/java-lang/java-lang-21.0/src/main/java/datadog/trace/instrumentation/java/lang/jdk21/VirtualThreadInstrumentation.java‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@
4040
* <ol>
4141
* <li>{@code init()}: captures the current {@link Context} and an {@link Continuation} to prevent
4242
* the enclosing context scope from completing early.
43-
* <li>{@code mount()}: on the first mount, seeds the virtual thread's scope stack with the
44-
* captured context; on later mounts, re-applies the profiler context to the current carrier
45-
* (no-op unless carrier-bound profiling is active).
46-
* <li>{@code unmount()}: clears the carrier's profiler context (no-op unless carrier-bound
47-
* profiling is active). The scope stack is NOT swapped out — it lives in the virtual thread's
48-
* own thread-local and follows it across park/unpark and carrier migration.
43+
* <li>{@code mount()}: with the legacy context manager, seeds the virtual thread's scope stack
44+
* with the captured context on the first mount only (it then follows the thread via its
45+
* virtual-thread-aware thread-local) and re-applies the profiler context on later mounts;
46+
* with the new context manager, swaps the context in on every mount (cheap, and drives the
47+
* profiler through its context listener).
48+
* <li>{@code unmount()}: with the legacy context manager, clears the carrier's profiler context;
49+
* with the new context manager, swaps the carrier's context back.
4950
* <li>Steps 2-3 repeat on each park/unpark cycle, potentially on different carrier threads.
5051
* <li>{@code afterDone()} / {@code afterTerminate()} for early VirtualThread support: cancels the
5152
* help continuation, releasing the context scope to be closed.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testdog.trace.instrumentation.java.lang.jdk21;
2+
3+
import datadog.trace.junit.utils.config.WithConfig;
4+
5+
/**
6+
* Runs the {@link VirtualThreadApiInstrumentationTest} cases with the legacy context manager
7+
* disabled, so {@code VirtualThreadState} takes the swap path instead of seed-once. Forked because
8+
* the legacy-context-manager choice is captured once per JVM.
9+
*/
10+
@WithConfig(key = "legacy.context-manager.enabled", value = "false")
11+
class VirtualThreadApiInstrumentationForkedTest extends VirtualThreadApiInstrumentationTest {}

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,16 +1438,6 @@ public void addScopeListener(final ScopeListener listener) {
14381438
this.scopeManager.addScopeListener(listener);
14391439
}
14401440

1441-
@Override
1442-
public void rebindProfilingContextToCarrier() {
1443-
scopeManager.rebindProfilingContextToCarrier();
1444-
}
1445-
1446-
@Override
1447-
public void unbindProfilingContextFromCarrier() {
1448-
scopeManager.unbindProfilingContextFromCarrier();
1449-
}
1450-
14511441
@Override
14521442
public SubscriptionService getSubscriptionService(RequestContextSlot slot) {
14531443
return (SubscriptionService) instrumentationGateway.getCallbackProvider(slot);

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -177,23 +177,6 @@ public final void beforeActivated() {
177177
}
178178
}
179179

180-
/**
181-
* Clears the profiler context for this scope's state without closing the scope itself. The scope
182-
* state must be idempotent and reusable, as this may be followed by another {@link
183-
* #beforeActivated()} on the same scope (e.g. across virtual-thread unmount/remount).
184-
*/
185-
public final void clearProfilingContext() {
186-
if (scopeState == Stateful.DEFAULT) {
187-
return;
188-
}
189-
try {
190-
scopeState.close();
191-
} catch (Throwable e) {
192-
ContinuableScopeManager.ratelimitedLog.warn(
193-
"ScopeState {} threw exception in clearProfilingContext()", scopeState.getClass(), e);
194-
}
195-
}
196-
197180
public final void afterActivated() {
198181
boolean hasScopeListeners = !scopeManager.scopeListeners.isEmpty();
199182
boolean hasExtendedListeners = !scopeManager.extendedScopeListeners.isEmpty();

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

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ public final class ContinuableScopeManager implements ContextManager {
6262
final HealthMetrics healthMetrics;
6363
private final ProfilingContextIntegration profilingContextIntegration;
6464
private final boolean profilingEnabled;
65-
private final boolean profilerCarrierBound;
6665
private final boolean hasDepthLimit;
6766

6867
/**
@@ -96,8 +95,6 @@ public ContinuableScopeManager(
9695
this.profilingContextIntegration = profilingContextIntegration;
9796
this.profilingEnabled =
9897
!(profilingContextIntegration instanceof ProfilingContextIntegration.NoOp);
99-
this.profilerCarrierBound =
100-
this.profilingEnabled && profilingContextIntegration.isCarrierThreadBound();
10198

10299
ContextManager.register(this);
103100
}
@@ -373,34 +370,6 @@ ScopeStack scopeStack() {
373370
return this.tlsScopeStack.get();
374371
}
375372

376-
/**
377-
* Re-applies the active scope's profiler context to the current carrier; no-op unless a
378-
* carrier-bound profiling integration is active.
379-
*/
380-
public void rebindProfilingContextToCarrier() {
381-
if (!profilerCarrierBound) {
382-
return;
383-
}
384-
final ContinuableScope active = scopeStack().active();
385-
if (active != null) {
386-
active.beforeActivated();
387-
}
388-
}
389-
390-
/**
391-
* Clears the current carrier's profiler context; no-op unless a carrier-bound profiling
392-
* integration is active.
393-
*/
394-
public void unbindProfilingContextFromCarrier() {
395-
if (!profilerCarrierBound) {
396-
return;
397-
}
398-
final ContinuableScope active = scopeStack().active();
399-
if (active != null) {
400-
active.clearProfilingContext();
401-
}
402-
}
403-
404373
@Override
405374
public Context current() {
406375
final ContinuableScope active = scopeStack().active();

‎dd-trace-core/src/test/java/datadog/trace/core/CoreTracerCarrierRebindTest.java‎

Lines changed: 0 additions & 44 deletions
This file was deleted.

‎dd-trace-core/src/test/java/datadog/trace/core/scopemanager/CarrierProfilerRebindTest.java‎

Lines changed: 0 additions & 123 deletions
This file was deleted.

0 commit comments

Comments
 (0)