Repository navigation
fix(profiler): recover carrier frames without preserved frame pointers - #887
Conversation
CI Test ResultsRun: #40016862638 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Summary: Total: 32 | Passed: 32 | Failed: 0 Updated: 2026-10-23 20:37:29 UTC |
Reliability & Chaos Results✅ All reliability & chaos checks passed Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/146779382 |
…nto paul.fournillon/fix-continuation-carrier-recovery
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
The 128-word argument limit rejects legal high-arity compiled methods at continuation return barriers, causing break_cont_entry_fp and dropping carrier ancestry.
🤖 Bits Code Review · Commit d65cc18
Preserve SP-based carrier recovery with explicit continuation-boundary classification and truncation. Adapt the native walker fixtures to the required metadata, cover missing-metadata rejection, and retain vm/vmx coverage for never-parked virtual threads.
What does this PR do?:
Recover carrier ancestry at virtual-thread continuation boundaries without requiring preserved frame-pointer links.
For JVMs without exported
ContinuationEntrylayout metadata, derive the entry frame from SP, the generatedenterSpecialframe size, and stack-argument padding. Account for OSR frames having zero stack arguments and for an entry wrapper generated after profiler initialization. Accept a non-pointer saved carrier FP only when the carrier PC resolves to an nmethod; retain stack bounds and PC validation. Track the argument count before compiled prologue/epilogue shortcuts and when recovering interpreter frames directly from Java frame anchors, so those paths can also reach the return barrier with valid metadata.Debug unwind-failure diagnostics also use a multi-byte JFR event-size prefix. Long stub names captured by the high-arity regression could otherwise corrupt the event size or abort a debug build while stopping the recording.
Motivation:
The existing blocking virtual-thread carrier test fails in PR #877 CI, with captured stacks stopping at
break_cont_carrier_sp.An isolated JDK 21 reproduction also exposes
break_cont_entry_fp. Diagnostic capture on macOS aarch64 found a valid continuation entry whose saved carrier FP was1: compiled carrier code can use FP as a general-purpose register. Following that value as another frame link, or requiring it to look like a pointer before walking compiled carrier code, loses valid ancestry.OpenJDK's x86 fast-thaw link handling, entry-frame argument/alignment calculation, and generated entry-frame setup provide the basis for this recovery calculation.
Additional Notes:
How to test the change?:
Two integration regressions require a return barrier, inspect samples with callers still frozen, reject continuation-pointer errors, and require carrier ancestry. One uses register arguments; the other forces stack-passed arguments. Both fail on the unchanged profiler on macOS aarch64/JDK 21; the Linux aarch64 baseline also fails a new regression. Fully thawed
vmxcoverage and eight carrier-recovery native tests cover entry slots, argument alignment, invalid metadata, overflow, and late wrapper generation.JAVA_HOME=/path/to/build/jdk JAVA_TEST_HOME=/path/to/test/jdk \ ./.claude/commands/build-and-summarize :ddprof-test:testDebug \ '-Ptests=*VirtualThread*Test' -Pskip-gtest -PkeepJFRs JAVA_HOME=/path/to/build/jdk \ ./.claude/commands/build-and-summarize :ddprof-lib:gtestDebug_continuationCarrierFrame_ut \ :ddprof-lib:gtestDebug_unwindFailureEncoding_utThe shared
vthread-remountchaos workload keeps four virtual threads alive, alternates register-only and stack-passed arguments, and repeatedly parks under deep callers before burning CPU.VirtualThreadCarrierChaosTestruns it for six seconds per mode in the ordinary integration suite, checking CPU and wall samples in bothvmandvmx. It requires return-barrier hits and worker progress, rejects continuation errors and missing/duplicated/misplaced carrier ancestry, and verifies truncation when outer callers are absent. Unfinished walks are counted separately and must be truncated. The old library fails the newvmxcheck with 2,623 continuation errors; thevmcontrol passes.The stress test exposed rare argument-tracking omissions in compiled shortcuts and direct interpreter-anchor recovery. Initial JDK 25 and mixed-chaos recordings retained
break_cont_entry_fp; the final runs below exercise the corrected paths without those errors.Extend the test duration, and repeat with
-XX:+PreserveFramePointeras a control:The workload is also selectable by name in the existing chaos harness, including alongside
alloc-stormandclassloader-churn. A clean harness exit alone is not a stack-correctness assertion.Completed validation:
-XX:+PreserveFramePointerbefore the final interpreter-anchor tracking addition. A 20-second mixed run with remount, allocation and class-loader churn completes; independent JDK JFR inspection finds 3,965 CPU and 8,791 wall workload samples with one carrier, and no continuation-pointer errors.spotlessApplyandgit diff --checkpass.Additional regression coverage:
For Datadog employees: