Skip to content

Commit 33d0110

Browse files
[mq] [skip ddci] working branch - merge 19885f9 on top of master at a4490c7
{"baseBranch":"master","baseCommit":"a4490c712f733182aacb3403b2a9fb82201581a9","createdAt":"2026-09-30T08:57:58.368861Z","headSha":"19885f93f1b47fb2c9ef775e39167a09260272bb","id":"36e0e72d-88c7-47a9-82dc-33fa531f1838","priority":"200","pullRequestNumber":"12546","queuedAt":"2026-09-30T08:57:58.368012Z","retryAttempt":"1","status":"STATUS_QUEUED"}
2 parents 80eb199 + 19885f9 commit 33d0110

14 files changed

Lines changed: 1086 additions & 39 deletions

File tree

‎dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java‎

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import datadog.trace.api.profiling.ProfilingEnablement;
5454
import datadog.trace.api.scopemanager.ScopeListener;
5555
import datadog.trace.bootstrap.benchmark.StaticEventLogger;
56+
import datadog.trace.bootstrap.config.provider.ConfigProvider;
5657
import datadog.trace.bootstrap.config.provider.StableConfigSource;
5758
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
5859
import datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI;
@@ -1489,35 +1490,96 @@ public void withTracer(TracerAPI tracer) {
14891490
* {@see com.datadog.profiling.ddprof.DatadogProfilingIntegration} must not be modified to depend
14901491
* on JFR.
14911492
*/
1492-
private static ProfilingContextIntegration createProfilingContextIntegration() {
1493-
if (Config.get().isProfilingEnabled()) {
1494-
if (Config.get().isDatadogProfilerEnabled() && !OperatingSystem.isWindows()) {
1495-
try {
1496-
return (ProfilingContextIntegration)
1497-
AGENT_CLASSLOADER
1498-
.loadClass("com.datadog.profiling.ddprof.DatadogProfilingIntegration")
1499-
.getDeclaredConstructor()
1500-
.newInstance();
1501-
} catch (Throwable t) {
1502-
log.debug("ddprof-based profiling context labeling not available. {}", t.getMessage());
1493+
static ProfilingContextIntegration createProfilingContextIntegration() {
1494+
Config config = Config.get();
1495+
// Windows is already excluded by Config (isDatadogProfilerSafeAndConfigured), so only AWS
1496+
// Lambda needs to be excluded here: it has no ddprof native library support, same as
1497+
// startProfilingAgent().
1498+
if (!isAwsLambdaRuntime()) {
1499+
if (config.isDatadogProfilerEnabled()) {
1500+
// The profiler itself is running: load ddprof now, and let ProfilingAgent.run() register
1501+
// the process context as it always has.
1502+
ProfilingContextIntegration integration = loadDdprofContextIntegration(AGENT_CLASSLOADER);
1503+
if (integration != null) {
1504+
return integration;
15031505
}
1506+
} else if (!config.isProfilingEnabled() && config.isOtelThreadContextEnabled()) {
1507+
// No profiler, we only want the context exposed: loading ddprof pulls in the native
1508+
// library and touches java.nio.file, which must not happen on the primordial premain
1509+
// thread, so it is deferred.
1510+
// The explicit !isProfilingEnabled() guard (redundant with isOtelThreadContextEnabled()'s
1511+
// own isDatadogProfilerSafeAndConfigured() factor) keeps this branch provably unreachable
1512+
// whenever profiling is enabled, so the JFR-events fallback below is never skipped.
1513+
return deferDdprofContextIntegration(AGENT_CLASSLOADER);
15041514
}
1505-
if (Config.get().isProfilingTimelineEventsEnabled()) {
1506-
// important: note that this will not initialise JFR until onStart is called
1507-
try {
1508-
return (ProfilingContextIntegration)
1509-
AGENT_CLASSLOADER
1510-
.loadClass("com.datadog.profiling.controller.openjdk.JFREventContextIntegration")
1511-
.getDeclaredConstructor()
1512-
.newInstance();
1513-
} catch (Throwable t) {
1514-
log.debug("JFR event-based profiling context labeling not available. {}", t.getMessage());
1515-
}
1515+
}
1516+
if (config.isProfilingEnabled() && config.isProfilingTimelineEventsEnabled()) {
1517+
// important: note that this will not initialise JFR until onStart is called
1518+
try {
1519+
return (ProfilingContextIntegration)
1520+
AGENT_CLASSLOADER
1521+
.loadClass("com.datadog.profiling.controller.openjdk.JFREventContextIntegration")
1522+
.getDeclaredConstructor()
1523+
.newInstance();
1524+
} catch (Throwable t) {
1525+
log.debug("JFR event-based profiling context labeling not available. {}", t.getMessage());
15161526
}
15171527
}
15181528
return ProfilingContextIntegration.NoOp.INSTANCE;
15191529
}
15201530

1531+
/**
1532+
* Loads the ddprof-based profiling context integration on the calling thread, for when the
1533+
* Datadog profiler is running. Returns {@code null} when it isn't available, so the caller can
1534+
* fall back to another integration.
1535+
*/
1536+
static ProfilingContextIntegration loadDdprofContextIntegration(final ClassLoader classLoader) {
1537+
try {
1538+
return newDdprofContextIntegration(classLoader);
1539+
} catch (Throwable t) {
1540+
log.debug("ddprof-based profiling context labeling not available. {}", t.getMessage());
1541+
return null;
1542+
}
1543+
}
1544+
1545+
/**
1546+
* Returns a placeholder integration that loads the ddprof-based one off the calling thread and
1547+
* registers the OTel process context alongside it. Only used when the profiler isn't running:
1548+
* otherwise {@code ProfilingAgent.run()} registers the process context itself.
1549+
*/
1550+
static ProfilingContextIntegration deferDdprofContextIntegration(final ClassLoader classLoader) {
1551+
DeferredProfilingContextIntegration deferred =
1552+
new DeferredProfilingContextIntegration(
1553+
"ddprof",
1554+
() -> {
1555+
ProfilingContextIntegration integration = newDdprofContextIntegration(classLoader);
1556+
registerProcessContext(classLoader);
1557+
return integration;
1558+
});
1559+
deferred.scheduleInitialization();
1560+
return deferred;
1561+
}
1562+
1563+
private static ProfilingContextIntegration newDdprofContextIntegration(
1564+
final ClassLoader classLoader) throws ReflectiveOperationException {
1565+
return (ProfilingContextIntegration)
1566+
classLoader
1567+
.loadClass("com.datadog.profiling.ddprof.DatadogProfilingIntegration")
1568+
.getDeclaredConstructor()
1569+
.newInstance();
1570+
}
1571+
1572+
private static void registerProcessContext(final ClassLoader classLoader) {
1573+
try {
1574+
classLoader
1575+
.loadClass("com.datadog.profiling.agent.ProcessContext")
1576+
.getMethod("register", ConfigProvider.class)
1577+
.invoke(null, ConfigProvider.getInstance());
1578+
} catch (Throwable t) {
1579+
log.debug("Process context registration not available. {}", t.getMessage());
1580+
}
1581+
}
1582+
15211583
private static boolean startProfilingAgent(
15221584
final boolean earlyStart, final boolean firstAttempt, Instrumentation inst) {
15231585
if (isAwsLambdaRuntime()) {
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package datadog.trace.bootstrap;
2+
3+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
4+
5+
import datadog.context.Context;
6+
import datadog.trace.api.EndpointTracker;
7+
import datadog.trace.api.Stateful;
8+
import datadog.trace.api.profiling.ProfilingContextAttribute;
9+
import datadog.trace.api.profiling.ProfilingScope;
10+
import datadog.trace.api.profiling.Timing;
11+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
12+
import datadog.trace.bootstrap.instrumentation.api.ProfilerContext;
13+
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
14+
import datadog.trace.util.AgentTaskScheduler;
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import java.util.concurrent.Callable;
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
/**
22+
* A {@link ProfilingContextIntegration} handed out synchronously during {@code premain} while the
23+
* real ddprof-based integration is constructed later, off the premain thread, to avoid loading the
24+
* ddprof native library (and touching {@code java.nio.file}) before {@code main} gets a chance to
25+
* set its own {@code java.nio.file.spi.DefaultFileSystemProvider}. Delegates to {@link
26+
* ProfilingContextIntegration.NoOp} until the swap happens; stays a no-op forever if construction
27+
* fails.
28+
*/
29+
final class DeferredProfilingContextIntegration implements ProfilingContextIntegration {
30+
private static final Logger log =
31+
LoggerFactory.getLogger(DeferredProfilingContextIntegration.class);
32+
33+
/**
34+
* Delay before the deferred construction runs, giving {@code main} a chance to install its own
35+
* {@code java.nio.file.spi.DefaultFileSystemProvider} first; not user-tunable since losing the
36+
* first second of context exposure is not observable.
37+
*/
38+
private static final long INITIALIZATION_DELAY_MILLIS = 1_000;
39+
40+
private final String name;
41+
private final Callable<ProfilingContextIntegration> factory;
42+
43+
/**
44+
* Swapped to the real integration once construction succeeds; volatile since scopes may already
45+
* be running when the swap happens.
46+
*/
47+
private volatile ProfilingContextIntegration delegate = ProfilingContextIntegration.NoOp.INSTANCE;
48+
49+
/**
50+
* Callbacks queued via {@link #whenAvailable(Runnable)} before the swap; guarded by {@code this}
51+
* together with the {@link #delegate} write so none is run twice or dropped.
52+
*/
53+
private final List<Runnable> pendingAvailabilityCallbacks = new ArrayList<>(1);
54+
55+
/**
56+
* @param name the name reported by {@link #name()}, i.e. the name of the integration being
57+
* deferred.
58+
* @param factory creates the real integration; invoked at most once, off the premain thread.
59+
*/
60+
DeferredProfilingContextIntegration(
61+
final String name, final Callable<ProfilingContextIntegration> factory) {
62+
this.name = name;
63+
this.factory = factory;
64+
}
65+
66+
/**
67+
* Schedules the deferred construction to run off this (premain) thread, after {@link
68+
* #INITIALIZATION_DELAY_MILLIS}.
69+
*/
70+
void scheduleInitialization() {
71+
AgentTaskScheduler.get().schedule(this::initialize, INITIALIZATION_DELAY_MILLIS, MILLISECONDS);
72+
}
73+
74+
/**
75+
* Runs the deferred construction; called exactly once per instance, from {@link
76+
* #scheduleInitialization()}. On failure this instance keeps behaving as {@link
77+
* ProfilingContextIntegration.NoOp} forever; a background failure must never propagate.
78+
*/
79+
void initialize() {
80+
try {
81+
final ProfilingContextIntegration integration = factory.call();
82+
if (integration == null) {
83+
return;
84+
}
85+
final List<Runnable> callbacks;
86+
synchronized (this) {
87+
delegate = integration;
88+
callbacks = new ArrayList<>(pendingAvailabilityCallbacks);
89+
pendingAvailabilityCallbacks.clear();
90+
}
91+
for (final Runnable callback : callbacks) {
92+
try {
93+
callback.run();
94+
} catch (final Throwable t) {
95+
log.debug("Availability callback for {} profiling context failed.", name, t);
96+
}
97+
}
98+
} catch (final Throwable t) {
99+
// toString() because failures here (UnsatisfiedLinkError etc.) often carry no message.
100+
log.info("Deferred {} profiling context labeling not available. {}", name, t.toString());
101+
}
102+
}
103+
104+
/**
105+
* Runs {@code callback} once the real integration is swapped in, or immediately if it already is;
106+
* never runs it if the deferred construction failed.
107+
*/
108+
@Override
109+
public void whenAvailable(final Runnable callback) {
110+
// double-checked: delegate is volatile, so a post-swap caller never takes the lock
111+
if (delegate == ProfilingContextIntegration.NoOp.INSTANCE) {
112+
synchronized (this) {
113+
if (delegate == ProfilingContextIntegration.NoOp.INSTANCE) {
114+
pendingAvailabilityCallbacks.add(callback);
115+
return;
116+
}
117+
}
118+
}
119+
callback.run();
120+
}
121+
122+
/**
123+
* The name of the deferred integration, not of the current delegate: read once at tracer build
124+
* time, possibly before the deferred construction completes.
125+
*/
126+
@Override
127+
public String name() {
128+
return name;
129+
}
130+
131+
@Override
132+
public void onStart() {
133+
delegate.onStart();
134+
}
135+
136+
@Override
137+
public void onAttach() {
138+
delegate.onAttach();
139+
}
140+
141+
@Override
142+
public void onDetach() {
143+
delegate.onDetach();
144+
}
145+
146+
@Override
147+
public boolean isThreadContextBindingRequired() {
148+
return delegate.isThreadContextBindingRequired();
149+
}
150+
151+
@Override
152+
public void setContext(final Context context) {
153+
delegate.setContext(context);
154+
}
155+
156+
@Override
157+
public Stateful newScopeState(final ProfilerContext profilerContext) {
158+
return delegate.newScopeState(profilerContext);
159+
}
160+
161+
@Override
162+
public int encode(final CharSequence constant) {
163+
return delegate.encode(constant);
164+
}
165+
166+
@Override
167+
public int encodeOperationName(final CharSequence constant) {
168+
return delegate.encodeOperationName(constant);
169+
}
170+
171+
@Override
172+
public int encodeResourceName(final CharSequence constant) {
173+
return delegate.encodeResourceName(constant);
174+
}
175+
176+
@Override
177+
public ProfilingContextAttribute createContextAttribute(final String attribute) {
178+
return delegate.createContextAttribute(attribute);
179+
}
180+
181+
@Override
182+
public ProfilingScope newScope() {
183+
return delegate.newScope();
184+
}
185+
186+
@Override
187+
public void onRootSpanFinished(final AgentSpan rootSpan, final EndpointTracker tracker) {
188+
delegate.onRootSpanFinished(rootSpan, tracker);
189+
}
190+
191+
@Override
192+
public EndpointTracker onRootSpanStarted(final AgentSpan rootSpan) {
193+
return delegate.onRootSpanStarted(rootSpan);
194+
}
195+
196+
@Override
197+
public Timing start(final TimerType type) {
198+
return delegate.start(type);
199+
}
200+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package datadog.trace.bootstrap;
2+
3+
import static datadog.trace.api.config.AppSecConfig.APPSEC_ENABLED;
4+
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_ENABLED;
5+
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ENABLED;
6+
import static org.junit.jupiter.api.Assertions.assertSame;
7+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
8+
9+
import datadog.trace.api.Config;
10+
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
11+
import datadog.trace.test.junit.utils.config.WithConfig;
12+
import datadog.trace.test.junit.utils.config.WithConfigExtension;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
16+
/**
17+
* Profiling is explicitly unsupported in AWS Lambda runtimes ({@code Agent#startProfilingAgent}
18+
* bails out there). The AppSec-driven OTel context exposure path must honour the same exclusion, so
19+
* that enabling AppSec inside a Lambda function never loads the ddprof native library.
20+
*
21+
* <p>Forked because {@link WithConfigExtension} swaps the process-wide environment variable
22+
* provider.
23+
*/
24+
@ExtendWith(WithConfigExtension.class)
25+
class AgentLambdaProfilingContextForkedTest {
26+
27+
@Test
28+
@WithConfig(key = APPSEC_ENABLED, value = "true")
29+
@WithConfig(key = PROFILING_ENABLED, value = "false")
30+
@WithConfig(key = PROFILING_DATADOG_PROFILER_ENABLED, value = "true")
31+
@WithConfig(
32+
key = "AWS_LAMBDA_FUNCTION_NAME",
33+
value = "my-function",
34+
env = true,
35+
addPrefix = false)
36+
void doesNotCreateTheDdprofIntegrationInAwsLambda() {
37+
// The exclusion is only observable when the configuration would otherwise have triggered the
38+
// ddprof context integration; the Datadog profiler is vetoed on some platforms and JVMs.
39+
assumeTrue(
40+
Config.get().isOtelThreadContextEnabled(),
41+
"OTel context exposure is unavailable on this platform/JVM version");
42+
43+
// AGENT_CLASSLOADER is null in this unit test, so reaching the ddprof branch at all would fail
44+
// loudly rather than silently return the no-op integration.
45+
assertSame(
46+
ProfilingContextIntegration.NoOp.INSTANCE, Agent.createProfilingContextIntegration());
47+
}
48+
}

0 commit comments

Comments
 (0)