Request carrier-scoped ddprof context storage on JDK 21+#11826
Conversation
…PROF-15271) The ddprof profiler exposes its OTEL context ThreadContext as a DirectByteBuffer over the carrier thread's native record. When that storage is keyed by the virtual thread (the pre-fix default), a mounted vthread pins to its first carrier: writes land on the wrong carrier after migration, and become a use-after-free once that carrier's OS thread exits. The java-profiler fix (PROF-15271) adds carrier-scoped storage via jdk.internal.misc.CarrierThreadLocal, selected by the ddprof.context.storage.mode system property. Before the profiler is loaded, on JDK 21+ (profiler enabled, non-Windows): - export java.base/jdk.internal.misc to the classloader that loads com.datadoghq.profiler.* so CarrierThreadLocal is reachable, and - set ddprof.context.storage.mode=carrier (only if unset), which requests carrier scoping and makes the profiler fail fast if it cannot honor it. Both actions are inert against ddprof builds that predate carrier support (the property is ignored; the export goes unused), so this is safe to ship ahead of the ddprof version bump — it activates automatically when that lands. Operators opt out with -Dddprof.context.storage.mode=thread (or =auto); we only set the property when unset, so an explicit choice always wins. No separate dd.* config key: the profiler's own system property is the single kill-switch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🎯 Code Coverage (details) 🔗 Commit SHA: 7ba6d03 | Docs | Datadog PR Page | Give us feedback! |
🟡 Java Benchmark SLOs — Performance SLO warning (near threshold)
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
…dogTracer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ROF-15271) The java-profiler side renamed the selector to ddprof.debug.context.storage.mode (ddprof.debug.*, signalling an internal knob). Match the property name we export and set here so the carrier request still takes effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jbachorik
left a comment
There was a problem hiding this comment.
In general, looking good.
My only concerns are about not entangling the generic Agent with profiler specific configs and quirks.
…he export Per review, don't set the profiler's internal ddprof.debug.context.storage.mode property from tracer code -- it's an implementation detail that may change and the profiler bump activating carrier mode is already in the merge queue. The remaining change is exporting java.base/jdk.internal.misc so java-profiler can reach CarrierThreadLocal.
|
/merge |
|
View all feedbacks in Devflow UI.
This pull request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
The expected merge time in
|
…11826) profiling: request carrier-scoped ddprof context storage on JDK 21+ (PROF-15271) The ddprof profiler exposes its OTEL context ThreadContext as a DirectByteBuffer over the carrier thread's native record. When that storage is keyed by the virtual thread (the pre-fix default), a mounted vthread pins to its first carrier: writes land on the wrong carrier after migration, and become a use-after-free once that carrier's OS thread exits. The java-profiler fix (PROF-15271) adds carrier-scoped storage via jdk.internal.misc.CarrierThreadLocal, selected by the ddprof.context.storage.mode system property. Before the profiler is loaded, on JDK 21+ (profiler enabled, non-Windows): - export java.base/jdk.internal.misc to the classloader that loads com.datadoghq.profiler.* so CarrierThreadLocal is reachable, and - set ddprof.context.storage.mode=carrier (only if unset), which requests carrier scoping and makes the profiler fail fast if it cannot honor it. Both actions are inert against ddprof builds that predate carrier support (the property is ignored; the export goes unused), so this is safe to ship ahead of the ddprof version bump — it activates automatically when that lands. Operators opt out with -Dddprof.context.storage.mode=thread (or =auto); we only set the property when unset, so an explicit choice always wins. No separate dd.* config key: the profiler's own system property is the single kill-switch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> docs: clarify profiler context-storage helper runs before installDatadogTracer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> profiling: align ddprof context-storage property to ddprof.debug.* (PROF-15271) The java-profiler side renamed the selector to ddprof.debug.context.storage.mode (ddprof.debug.*, signalling an internal knob). Match the property name we export and set here so the carrier request still takes effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Merge branch 'master' into rkennke/PROF-15271 profiling: drop internal ddprof context-storage property, keep only the export Per review, don't set the profiler's internal ddprof.debug.context.storage.mode property from tracer code -- it's an implementation detail that may change and the profiler bump activating carrier mode is already in the merge queue. The remaining change is exporting java.base/jdk.internal.misc so java-profiler can reach CarrierThreadLocal. [ci: NON_DEFAULT_JVMS] trigger graalvm smoke tests Merge branch 'master' into rkennke/PROF-15271 Merge branch 'master' into rkennke/PROF-15271 Co-authored-by: jbachorik <jaroslav.bachorik@datadoghq.com> Co-authored-by: roman.kennke <roman.kennke@datadoghq.com>
What Does This Do
On JDK 21+, before the ddprof profiler is loaded, this exports
java.base/jdk.internal.miscto the classloader that loadscom.datadoghq.profiler.*.Implemented as a small
prepareDatadogProfilerContextStorage(Instrumentation)helper inAgent, invoked inInstallDatadogTracerCallbackright beforeinstallDatadogTracer(which triggers the profiler load, so the export is in place beforeJavaProfileris constructed).Motivation
The profiler exposes its OTEL context
ThreadContextas aDirectByteBufferover the carrier thread's native record — the record the (carrier-bound) sampler reads. When that storage is keyed by the virtual thread (the current default), a mounted vthread pins to whichever carrier it first ran on:ThreadsSMRSupport::free_listunder a Loom workload).The java-profiler fix (PROF-15271) adds carrier-scoped storage via
CarrierThreadLocal, so a mounted vthread always resolves to its current carrier's live record.CarrierThreadLocalis in a non-exported package, hence the export.Additional Notes
ddprof.debug.context.storage.modesystem property from tracer code. Per review, that's an internal ddprof knob that may still change and shouldn't be entangled with the genericAgentclass; the profiler's own version bump (already queued) is what activates carrier mode. This PR is now scoped to just the export, which is harmless to ship regardless of which ddprof version is bundled.isProfilingEnabled() && isDatadogProfilerEnabled() && !isWindows() && isJavaVersionAtLeast(21).JDK9ModuleAccess(internal-api-9) is only referenced under the 21+ guard, so it never loads on Java 8.jdk.internal.miscviaredefineModulefollows existing precedent (AdvancedAgentChecksdoes the same for CDS detection).Contributor Checklist
type:/comp:labelsclose/fixlinking keywords usedJira ticket: PROF-15271
🤖 Generated with Claude Code