feat(context): all-native OTEL context write API — Phase 1 (PROF-15271) - #631
Conversation
Captures the UAF problem, options A-E, the JMH benchmark campaign and its governing principle, the combined-API design, the consumer audit, and the expand -> migrate -> contract migration plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add native setTraceContext0/clearTraceContext0 (combined per-scope activate/deactivate) and setContextValue0/clearContextValue0 (single attribute), plus attrs_data compaction and LRS helpers, in javaApi.cpp, with matching private native declarations on JavaProfiler. Each resolves the current carrier's OtelThreadContextRecord via ProfiledThread::current(). A JNI native frame pins a mounted virtual thread to its carrier for the duration of the call, so the resolved record is guaranteed live and cannot migrate mid-write -- there is no cached per-thread DirectByteBuffer to dangle. Writes follow the same detach -> mutate -> attach valid-flag protocol as ThreadContext, keeping the async-signal-handler sampler (ContextApi::get) coherent. Native layer only; the public Java API and value cache built on top follow in a later commit. The DirectByteBuffer path is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-15271) Add the public all-native context API on JavaProfiler (provisional names, pending dd-trace-java coordination): setTraceContext/clearTraceContext (combined per-scope activate/deactivate) and setContextValue/clearContextValue (single attribute), each backed by the native primitives. Relocate the value->(encoding,utf8) memoization out of the per-thread ThreadContext into a new process-wide ContextValueCache. Because the encoding is a process-global immutable ID (registerConstant0), a shared lock-free cache is correct: immutable entries published via a single AtomicReferenceArray slot store (no torn reads), a miss re-registers idempotently, and collisions self-heal. This avoids duplicating the table across every carrier/virtual thread and needs no CarrierThreadLocal. registerConstant0 is made package-private for reuse (relocates in phase 3). Coexists with the DirectByteBuffer path (both write the same native record); the DBB path is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…5271) Mark the DBB write/read surface `@Deprecated` pointing at the all-native API: JavaProfiler.setContext/clearContext/setContextAttribute/clearContextAttribute/ setContextAttributesByIdAndBytes/copyTags/getThreadContext, and the DBB-path helper classes ThreadContext, ScopeStack, and ContextSetter. Non-breaking (annotations only) -- signals the migration for dd-trace-java (phase 2) ahead of removal (phase 3). Java has no -Werror, so the deprecation notes are informational; the DBB path still functions unchanged during coexistence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…PROF-15271) setTraceContext0/clearTraceContext0 now write the fixed LRS entry header (key_index=0, length=16) in addition to its hex value, instead of relying on the ThreadContext constructor to have initialized it. This makes the all-native path correct on a record that only saw the ProfiledThread zero-init -- i.e. when no DirectByteBuffer/ThreadContext was ever created (the phase-2 pure-native case), where attrs_data[1] would otherwise be 0 and mis-frame the LRS entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AllNativeContextTest exercises setTraceContext/clearTraceContext/setContextValue/ clearContextValue: round-trip readback, equivalence with the DirectByteBuffer path, clear semantics, span-transition attribute reset, single-attribute set/clear, attrs_data overflow, rejected (null/oversized) values, DBB<->native coexistence on one thread, and coherence of native writes from many mounted virtual threads (the migration-safe path). Read-back uses the ThreadContext DBB read methods as an oracle -- a view over the same native record the all-native path writes. All 9 pass on JDK 21 (debug); the existing context suite (Otel/Carrier/Tag/ProcessContext) is unregressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ROF-15271) Point the design note appendix at branch context-storage-benchmark-experiment (pushed to origin), which preserves the full JMH campaign + native prototypes for reproducibility, rather than describing scaffolding that lives only on that branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le (PROF-15271) ContextCombinedBenchmark compares the production per-scope activate+deactivate cycle: the combined native calls (setTraceContext + clearTraceContext) vs the deprecated DirectByteBuffer sequence as dd-trace-java actually drives it (profiler.setContext + 2x setContextAttribute + clear...), on platform + mounted virtual threads, both storage modes. Measured (JDK 21, debug, carrier): native ~136 ns vs DBB ~189 ns -- native ~28% faster, mode-independent, and a win on vthreads too (~147 vs ~193 ns). The DBB baseline here is the faithful production cost: each of its 6 calls pays a currentContext()/ CarrierThreadLocal lookup, which the 2-call combined native cycle avoids. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI Test ResultsRun: #29021930572 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Summary: Total: 32 | Passed: 32 | Failed: 0 Updated: 2026-07-09 13:56:02 UTC |
Fixes check-javadoc "no comment" warnings for setTraceContext, setContextValue, and clearContextValue introduced in this PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
- Convert `// @deprecated` line comments into proper `@deprecated` javadoc tags on the deprecated JavaProfiler methods and the ThreadContext / ScopeStack / ContextSetter classes, so the rationale renders in javadoc and `-Xlint` stops warning about the missing tag. - Reject out-of-range slots (`slot >= MAX_CONTEXT_SLOTS`) in setContextValue and resolveContextValue before resolving, so an invalid slot no longer registers an orphan entry in the permanent native Dictionary (matches the DBB ThreadContext.setContextAttribute guard and the existing javadoc). - Reconcile the perf figures in the design note: explain that ~7-10% is the prototype microbench baseline and ~28% is the production ContextCombinedBenchmark baseline (six real currentContext()-resolving calls), so they no longer read as contradictory. - Clarify the otelWriteLrsEntry comment: only setTraceContext0/clearTraceContext0 establish the LRS entry; setContextValue0 does not, which is harmless. - Add an assert(len <= 255) in otelReadUtf8 documenting the caller contract, keeping the clamp as release-build defense. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa4e7cbd1d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- P1 (Codex/jbachorik): the all-native write entry points never initialized OTEL TLS, so a thread that used only setTraceContext/setContextValue wrote a record the sampler (ContextApi::get / wallClock) ignored and eBPF could not discover. setTraceContext0 / setContextValue0 now call ContextApi::initializeContextTLS(thrd) on first use, matching the DBB path. Added AllNativeContextSamplingTest: a wall-clock JFR test on a thread that never calls getThreadContext(), asserting a sample carries the natively written context (fails if the record is not published to the sampler). - jbachorik: ContextValueCache assumed process-lifetime-immutable encodings, but a fresh start resets the native Dictionary (clearAll), reassigning IDs. Added ContextValueCache.clear(), invoked from execute() on a 'start' action, and corrected the class javadoc invariant. - jbachorik: MAX_CONTEXT_SLOTS duplicated native DD_TAGS_CAPACITY with only a comment binding them. Added native accessor maxContextSlots0() and MaxContextSlotsTest to catch drift from DD_TAGS_CAPACITY / ThreadContext.MAX_CUSTOM_SLOTS at test time. - jbachorik nits: added tests for the native null byte[] guard in otelReadUtf8, the otelCompactAttr 2-byte (zero-length value) boundary, and the setContextValue slot == MAX_CONTEXT_SLOTS boundary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kaahos
left a comment
There was a problem hiding this comment.
really nice changes, thanks! I only have few minor questions you can find below.
- #1/#2 (ContextValueCache invalidation): replace the Java command-string parsing (isStartAction) — which didn't match arguments.cpp's tokenization — with a native, authoritative signal. Profiler::start sets a _context_value_dict_reset flag in the reset block where _context_value_map.clearAll() runs (reached only for ACTION_START), and JavaProfiler.execute consumes it via consumeContextDictionaryReset0() to drop the cache. No command re-parsing in Java. This does not fully close the concurrent race window (resolve() during the reset), which is no worse than the deprecated DirectByteBuffer path; fully closing it is tracked as a follow-up (see PR discussion / Jira). - #3 (clearContextValue0 validity): capture and restore the prior valid flag instead of unconditionally re-attaching with valid=1, so clearing one attribute cannot resurrect a record that clearTraceContext0 intentionally left deactivated. Added AllNativeContextTest.clearContextValuePreservesInvalidState. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reliability & Chaos Results❌ 1 failure(s) detected Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/123758963 ❌ chaos: profiler tcmalloc amd64 21 0 3 temXchaos |
Apply the same guard as clearContextValue0: capture the record's valid flag up
front and restore it instead of hardcoding valid=1. Writing a single attribute
must not resurrect a record that clearTraceContext0 intentionally deactivated —
republishing it would expose a span-less record (zero trace/span) as valid.
Mirrors the DBB path's guard in ThreadContext.setContextAttributesByIdAndBytes
("never resurrect a cleared, span-less record").
Added AllNativeContextTest.setContextValuePreservesInvalidState.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kaahos
left a comment
There was a problem hiding this comment.
Thanks for the modifications! it looks good to me.
… API Distinguish caller programming errors (fail loud) from runtime/data conditions (soft signal), instead of silently swallowing the former: - setTraceContext rejects spanId==0 with IllegalArgumentException — it is the activation path and requires a real span; clearing is clearTraceContext. This drops the DBB path's conflation of setContext(0,0,0,0) with a clear and never publishes a span-less record. The native setTraceContext0 primitive asserts spanId!=0 as debug-only defense (the wrapper validates first). - Out-of-range custom-attribute slots now throw IllegalArgumentException in setTraceContext (non-negative slot >= MAX_CONTEXT_SLOTS; negative stays the documented skip sentinel), setContextValue, and clearContextValue — instead of being silently skipped / returning false / no-op'ing. Data conditions stay soft (not exceptions), matching normal control flow: a null or oversized value, or a full Dictionary, still returns false (setContextValue) or skips (setTraceContext). Native keeps its hard slot range guards (load-bearing for memory safety and to defend direct-JNI calls). Tests: added setTraceContextRejectsZeroSpanId; expanded slotBoundaryIsRejected to assert IllegalArgumentException across setContextValue/clearContextValue/ setTraceContext and that a negative activation slot still skips without throwing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverse the earlier "preserve prior valid" change on setContextValue0. Setting a value means "make it visible": an attribute must be observable to the sampler even when no span is active (zero trace/span). This is dd-trace-java's app-context-independent-of-span model (PR #11646) — app-owned attributes such as http.route, and the reapply-after-deactivation path, must stay visible between spans. It mirrors the DBB single-attribute setter (ThreadContext.setContextAttributeDirect), which always re-attaches; the earlier change had wrongly modeled it on the *bulk* setter's valid==0 guard. clearContextValue0 keeps preserving the prior valid flag: removing a value must not resurrect a deactivated record. So the asymmetry is intentional and matches the DBB single-vs-bulk behavior the tracer's reapply logic was built around: set publishes, clear does not. Test renamed/rewritten: setContextValuePublishesAppContextWithoutSpan asserts a value set after clearTraceContext republishes (valid=1, span=0, value observable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jbachorik
left a comment
There was a problem hiding this comment.
Looks really good.
Could you, for peace of my mind, run the benchmarks also on Java 8 and 11 and add the numbers to the PR?
I expect maybe slight degradation, but we won't try and optimize for old runtimes anyway ...
Add JavaProfiler.copyContextTags(int[]) + native copyContextTags0: reads the current thread's custom-attribute sidecar tag encodings directly from the record (ProfiledThread::current()), with no ThreadContext / DirectByteBuffer. Unlike the deprecated DBB copyTags — which goes through ThreadContext, whose ctor resets the record — this observes encodings written via the all-native setContextValue path and does not clobber them. This is the read the dd-trace-java Phase 2 migration needs for its (test-only) snapshot() oracle once writes go all-native. Also adds the Phase 2 plan doc (dd-trace-java migration onto the all-native API). Test: AllNativeContextTest.copyContextTagsReadsNativeEncodings (never calls getThreadContext, proving the native read observes native writes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dd-trace-java Phase 2 bridge sizes its app-offset arrays from the context attribute count. It previously read that as snapshotTags().length — a DBB read that creates a ThreadContext. Expose size() (attributes.size(), pure Java) so the bridge can size without touching the DBB path; offsetOf + size are the only ContextSetter methods it still uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@jbachorik ran the Only the
thread mode, ns/op (± 99.9% CI), 9 measurements each:
So the combined 2-JNI-call cycle beats the 6-call DBB sequence by ~2.3–2.5× on both — consistent with the JDK 21 result, no slight degradation observed. (Caveat: the reliable signal is the native-vs-dbb ratio within each JVM; the absolute 8-vs-11 gap is cross-JVM and noisier, as JMH itself warns.) |
|
@rkennke Cool, thanks! |
Switch the profiler context bridge off the deprecated DirectByteBuffer (DBB) context API onto java-profiler's all-native API (setTraceContext / clearTraceContext / setContextValue / clearContextValue). This eliminates the virtual-thread use-after-free (the DBB cached buffer that dangled on carrier migration) and folds the per-activation sequence (setContext + two setContextValue) into a single native call. - DatadogProfilingIntegration.activate: one setTraceContext(...) carrying trace/span context + operation and resource attributes (3 JNI calls -> 1); close/clearContext: clearTraceContext() (wipes op/resource slots too). - DatadogProfiler: setContextValue/clearContextValue/reapplyAppContext/ syncNativeAppContext are now all-native. reapplyAppContext uses a per-slot native setContextValue loop (native setContextValue publishes valid=1, so app context stays visible without an active span — preserves PR #11646). A native batch reapply is deferred to a measured follow-up (java-profiler PROF-15361). - AppContextSnapshot simplified to strings-only (the native path resolves each value's encoding via the process-wide cache; no cached id/utf8/snapshotTags). - snapshot() uses the new native copyContextTags read (no ThreadContext/DBB, so it observes native writes without resetting the record). - ContextSetter kept only for offsetOf + size (pure Java); no DBB usage remains. Requires java-profiler with the all-native API (ddprof >= the phase-1 release; DataDog/java-profiler#631). Build/test with -PddprofUseSnapshot=true against a local publishToMavenLocal 1.47.0-SNAPSHOT until that ships. ddprof suite is Linux-gated (assumeTrue(isLinux)) — verify on Linux/CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the profiler context bridge off the deprecated DirectByteBuffer (DBB) context API onto java-profiler's all-native API (setTraceContext / clearTraceContext / setContextValue / clearContextValue). This eliminates the virtual-thread use-after-free (the DBB cached buffer that dangled on carrier migration) and folds the per-activation sequence (setContext + two setContextValue) into a single native call. - DatadogProfilingIntegration.activate: one setTraceContext(...) carrying trace/span context + operation and resource attributes (3 JNI calls -> 1); close/clearContext: clearTraceContext() (wipes op/resource slots too). - DatadogProfiler: setContextValue/clearContextValue/reapplyAppContext/ syncNativeAppContext are now all-native. reapplyAppContext uses a per-slot native setContextValue loop (native setContextValue publishes valid=1, so app context stays visible without an active span — preserves PR #11646). A native batch reapply is deferred to a measured follow-up (java-profiler PROF-15361). - AppContextSnapshot simplified to strings-only (the native path resolves each value's encoding via the process-wide cache; no cached id/utf8/snapshotTags). - snapshot() uses the new native copyContextTags read (no ThreadContext/DBB, so it observes native writes without resetting the record). - ContextSetter kept only for offsetOf + size (pure Java); no DBB usage remains. Requires java-profiler with the all-native API (ddprof >= the phase-1 release; DataDog/java-profiler#631). Build/test with -PddprofUseSnapshot=true against a local publishToMavenLocal 1.47.0-SNAPSHOT until that ships. ddprof suite is Linux-gated (assumeTrue(isLinux)) — verify on Linux/CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e 2) (#11899) profiling(ddprof): migrate context bridge to the all-native API Switch the profiler context bridge off the deprecated DirectByteBuffer (DBB) context API onto java-profiler's all-native API (setTraceContext / clearTraceContext / setContextValue / clearContextValue). This eliminates the virtual-thread use-after-free (the DBB cached buffer that dangled on carrier migration) and folds the per-activation sequence (setContext + two setContextValue) into a single native call. - DatadogProfilingIntegration.activate: one setTraceContext(...) carrying trace/span context + operation and resource attributes (3 JNI calls -> 1); close/clearContext: clearTraceContext() (wipes op/resource slots too). - DatadogProfiler: setContextValue/clearContextValue/reapplyAppContext/ syncNativeAppContext are now all-native. reapplyAppContext uses a per-slot native setContextValue loop (native setContextValue publishes valid=1, so app context stays visible without an active span — preserves PR #11646). A native batch reapply is deferred to a measured follow-up (java-profiler PROF-15361). - AppContextSnapshot simplified to strings-only (the native path resolves each value's encoding via the process-wide cache; no cached id/utf8/snapshotTags). - snapshot() uses the new native copyContextTags read (no ThreadContext/DBB, so it observes native writes without resetting the record). - ContextSetter kept only for offsetOf + size (pure Java); no DBB usage remains. Requires java-profiler with the all-native API (ddprof >= the phase-1 release; DataDog/java-profiler#631). Build/test with -PddprofUseSnapshot=true against a local publishToMavenLocal 1.47.0-SNAPSHOT until that ships. ddprof suite is Linux-gated (assumeTrue(isLinux)) — verify on Linux/CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> profiling(ddprof): guard zero span in setTraceContext; document clearContextValue The native setTraceContext rejects spanId==0 with IllegalArgumentException (it is the activation path; clearing is clearTraceContext). The bridge's catch(Throwable) would swallow that throw and leave the previous span's context stale on the thread. Span ids are non-zero by construction (IdGenerationStrategy never yields 0, DDSpanId.ZERO means "no span", and DDSpanContext is the only ProfilerContext), so this is defensive: route a zero span to a clean clearTraceContext instead of a silently-swallowed throw over stale state. Also document clearContextValue(int)'s return contract (@param/@return) and add a testContextRegistration scenario asserting a zero-span activation does not throw and still reapplies app context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> profiling(ddprof): describe current behavior in comments; drop historical references Rewrite or remove comments that documented the superseded DirectByteBuffer context API and the ddprof-version history of the bridge (e.g. "Replaces the previous setContext...", "no DBB read", the 1.41.0/1.45.0 no-op history in DatadogProfilingScope). Those explain how the code got here, not what it does now; the commit history and PRs carry that evolution. Comment-only, behavior unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> profiling(ddprof): fix JMH benchmark for strings-only AppContextSnapshot AppContextSnapshotBenchmark.setup() still called the old record(int, int, byte[], String) signature; AppContextSnapshot.record is now record(int, String) (strings-only, per the all-native context migration). compileJmhJava was failing in CI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> profiling(ddprof): fix two reapply ordering bugs found by PR review bots - setContextValue: a rejected native write (e.g. >255-byte UTF-8) clears the native slot but left the Java snapshot untouched, so the attribute read as unset until the next span boundary silently resurrected the stale prior value. Reapply immediately on rejection so the prior value stays visible continuously, matching pre-migration DBB behavior. - setTraceContext: reapplyAppContext() ran unconditionally after the native call, so when profiling.context.attributes also names _dd.trace.operation/resource (with span-name/resource-name context enabled), the trailing reapply clobbered the span-derived value that setTraceContext just wrote to the same offset with a stale app-recorded one. reapplyAppContext now takes the operation/resource offsets to skip. Both were flagged independently by Codex and Datadog Autotest PR review bots on #11899, with a concrete repro for the first. Added regression coverage to DatadogProfilerTest#testContextRegistration for both (verified each new assertion fails without its corresponding fix). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Merge branch 'master' into rkennke/profiler-all-native-context-phase2 Rename reapplyAppContext skip-offset params to operationOffset/resourceOffset Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Merge branch 'master' into rkennke/profiler-all-native-context-phase2 Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
Summary
Phase 1 of eliminating the virtual-thread use-after-free in OTEL trace-context storage
(PROF-15271), by moving context writes to an all-native path that resolves the current
carrier's OTEP record inside the JNI call — where the mounted virtual thread is pinned to its
carrier by the native frame, so the write is race-free by construction with no cached
DirectByteBufferto dangle.This PR is purely additive: it adds the new API alongside the existing
DirectByteBuffer(DBB) path (now
@Deprecated) and changes no behavior. The crash fix itself lands in Phase 2,when dd-trace-java switches to the new API; until then carrier-scoping (#625 /
94686da) remainsthe mitigation.
Full rationale, options considered (A–E), the JMH campaign, the consumer audit, and the
expand → migrate → contract plan are in the design note:
doc/plans/2026-07-02-all-native-context-storage-design.md.What's in this PR
javaApi.cpp):setTraceContext0/clearTraceContext0(combinedper-scope activate/deactivate) and
setContextValue0/clearContextValue0(single attribute),using the same
detach → mutate → attachvalid-flag protocol asThreadContextso theasync-signal-handler sampler (
ContextApi::get) stays coherent. The native path self-initializesthe LRS
attrs_dataentry (no dependency on theThreadContextctor).JavaProfiler,ContextValueCache): provisional namessetTraceContext/clearTraceContext/setContextValue/clearContextValue. Thevalue → (encoding, utf8)memoization moves out of the per-threadThreadContextinto aprocess-wide lock-free cache (safe because the encoding is a process-global immutable ID).
ThreadContext/ScopeStack/ContextSetterhelper classes are marked
@Deprecated(non-breaking) pointing at the new API.AllNativeContextTest, 9): round-trip, DBB-equivalence, clear semantics,span-transition attribute reset, single-attr set/clear, overflow, rejected values,
DBB↔native coexistence, and coherence of native writes from 512 mounted virtual threads.
ContextCombinedBenchmark).Results
Tests: all 9 new tests pass on JDK 21 (debug); the existing context suite (Otel / Carrier /
Tag / ProcessContext) is unregressed.
Perf (JDK 21, production per-scope activate+deactivate cycle, carrier mode): all-native
~136 ns vs DBB ~189 ns — ~28% faster (~24% thread mode), a win on virtual threads too, and
mode-independent. The DBB baseline is the faithful production cost (each of its 6 calls pays a
currentContext()/CarrierThreadLocallookup the 2-call native cycle avoids).Perf on old runtimes (Java 8 & 11): same
ContextCombinedBenchmarkand JMH config as theJDK 21 campaign (3 forks × 3×1s warmup × 3×3s measurement,
AverageTime, one op = one fullactivate+deactivate cycle). Only
platform_* / threadapplies on 8/11 —carriermode and thevthread_*benchmarks are JDK 21+ only (they needjdk.internal.misc.CarrierThreadLocal/Thread.startVirtualThread).setTraceContext+clearTraceContext)setContext+2×attr, ×2)1.8.0_492)11.0.31)No degradation on old runtimes — the all-native cycle wins by ~2.3–2.5×, consistent with JDK 21.
(Reliable signal is the native-vs-dbb ratio within each JVM; the cross-JVM absolute gap is
noisier, as JMH itself warns.)
Migration (expand → migrate → contract)
DatadogProfilingIntegrationto the combined API. Crashfix + perf win land here.
CarrierThreadLocal/OtelContextStoragesubsystem. Breaking, gated on all-consumers-migrated.
Reviewer notes
team so Phase 2 is a clean swap.
detach/attach+ release-fence protocol vsthe sampler's acquire load of
valid(ContextApi::get). Verify on aarch64 (trivial on x86).context-storage-benchmark-experiment.else is test/benchmark. Open item: confirm
ddprof-libstandalone Maven publication (externalconsumers) before the Phase-3 removal.
🤖 Generated with Claude Code