Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import datadog.trace.bootstrap.instrumentation.jfr.InstrumentationBasedProfiling;
import datadog.trace.util.AgentTaskScheduler;
import datadog.trace.util.AgentThreadFactory.AgentThread;
import datadog.trace.util.JDK9ModuleAccess;
import datadog.trace.util.throwable.FatalAgentMisconfigurationError;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.instrument.Instrumentation;
Expand Down Expand Up @@ -659,6 +660,8 @@ public InstallDatadogTracerCallback(
}

installDatadogMeter(initTelemetry);
// Must run before installDatadogTracer, which triggers the ddprof profiler load.
prepareDatadogProfilerContextStorage(instrumentation);
installDatadogTracer(initTelemetry, scoClass, sco);
maybeInstallLogsIntake(scoClass, sco);
maybeStartIast(instrumentation);
Expand Down Expand Up @@ -1356,6 +1359,48 @@ public void withTracer(TracerAPI tracer) {
});
}

/**
* Prepares carrier-scoped OTEL context storage for the Datadog profiler before it is loaded.
*
* <p>On JDK 21+, the profiler can scope its context {@code ThreadContext} storage to the carrier
* thread using {@code jdk.internal.misc.CarrierThreadLocal}, so a mounted virtual thread resolves
* to its current carrier's record — fixing a virtual-thread context use-after-free. That type
* lives in a non-exported package, so we export it to the classloader that loads {@code
* com.datadoghq.profiler.*}, and request carrier scoping via the profiler's {@code
* ddprof.debug.context.storage.mode} system property, which it reads at construction.
*
* <p>Both actions are inert against profiler builds that predate carrier support (the property is
* ignored; the export goes unused), so this is safe to ship ahead of the corresponding
* java-profiler version bump — it simply activates when that lands.
*
* <p>Operators disable it with {@code -Dddprof.debug.context.storage.mode=thread} (or select {@code
* auto}); we only set the property when it is unset, so an explicit choice always wins. Must run
* before {@code installDatadogTracer}, which loads the profiler via {@link
* #createProfilingContextIntegration()}.
*/
private static void prepareDatadogProfilerContextStorage(Instrumentation inst) {
try {
if (inst == null
|| !Config.get().isProfilingEnabled()
|| !Config.get().isDatadogProfilerEnabled()
|| OperatingSystem.isWindows()
|| !isJavaVersionAtLeast(21)) {
return;
}
// Export jdk.internal.misc to the profiler's classloader so CarrierThreadLocal is reachable.
// Harmless when unused (e.g. thread mode, or a profiler build without carrier support).
JDK9ModuleAccess.exportModuleToUnnamedModule(
inst, "java.base", new String[] {"jdk.internal.misc"}, AGENT_CLASSLOADER);
// Request carrier scoping unless an operator has explicitly set the profiler's mode property
// (which also serves as the kill-switch: -Dddprof.debug.context.storage.mode=thread).
if (SystemProperties.get("ddprof.debug.context.storage.mode") == null) {
SystemProperties.set("ddprof.debug.context.storage.mode", "carrier");
}
} catch (Throwable t) {
log.debug("Unable to prepare carrier-scoped profiler context storage", t);
}
}

/**
* {@see com.datadog.profiling.ddprof.DatadogProfilingIntegration} must not be modified to depend
* on JFR.
Expand Down