Skip to content

probeJFRRequestStackTrace: mixed atomic ordering and params null-safety - #919

Draft
jbachorik wants to merge 3 commits into
mainfrom
quirky-amber-bunny
Draft

jbachorik wants to merge 3 commits into
mainfrom
quirky-amber-bunny

Conversation

@jbachorik

@jbachorik jbachorik commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?:

Fixes three defects in VM::probeJFRRequestStackTrace() (PROF-14757):

  1. Mixed atomic ordering — _init_request_stack_trace was stored with a plain store while _request_stack_trace was published with __ATOMIC_RELEASE, so a cross-thread reader of canRequestStackTrace() had no happens-before guarantee for the init pointer (latent on arm64).
  2. Premature publication — _request_stack_trace was published during the extension scan, even when jvmtistacks was not requested and before InitializeRequestStackTrace ran, so canRequestStackTrace() reported "ready" for an uninitialized extension.
  3. NULL-safety — the extension-params deallocation loop dereferenced params[j] without guarding against a NULL params array.

_request_stack_trace is now published only after a successful InitializeRequestStackTrace under jvmtistacks=true; the _init pointer is stored (RELEASE) before it, making canRequestStackTrace() mean initialized, not merely present.

Motivation:
Review findings from PR #537

Behavior change (intended): with jvmtistacks=false, or when InitializeRequestStackTrace fails, canRequestStackTrace() now returns false. All consumers were audited: engine selection gates on _jvmtistacks && canRequestStackTrace(); Profiler::recordSampleDelegated() is reachable only from the JVMTI engines; the dump-time diagnostic fires only when requests were counted. On init failure the JVMTI engines fall back to ASGCT.

Additional Notes:

  • Memory ordering is verified by review, not by a runtime test (single-threaded tests cannot exercise acquire/release) — per the issue's test plan.
  • initializeRequestStackTrace()'s guard now checks only _init_request_stack_trace; the caller owns the presence check for the request function before publishing.

How to test the change?:

New mock-JVMTI gtest test_vmEntryRequestStackTrace.cpp (8 cases) drives probeJFRRequestStackTrace() against a fake jvmtiEnv:

  • NULL params with param_count > 0 → no crash (issue 3)
  • jvmtistacks=false → nothing published, init never invoked (issue 2)
  • published only after successful init; unpublished on init failure, missing request/init extension, or GetExtensionFunctions failure; failure counter increments
  • exact Deallocate call counts pinned (with a null sentinel slot that catches the off-by-one loop-bound mutation); initializeRequestStackTrace() guard with the init pointer unset

Verified:

  • :ddprof-lib:gtestDebug — 1020 tests passed, 0 failed
  • :ddprof-test:testDebug -Ptests=JvmtiBasedPrecheckTest — SKIPPED identically with and without the change (pre-existing: JDK 27-ea on macOS arm64 does not satisfy the jvmti_stacks_init_ok > 0 assumption)
  • Agentic review (/sphinx review + fix round): 0 CRITICAL/HIGH; 4 findings fixed, 4 dismissed with recorded reasons; fix-delta review clean

For Datadog employees:

  • If this PR touches code that signs or publishes builds or packages, or handles
    credentials of any kind, I've requested a security review (run the dd:platform-security-review
    skill, or file a request via the PSEC review form).
    bewaire also runs automatically on every PR.
  • This PR doesn't touch any of that.
  • JIRA: PROF-14757

Publish _request_stack_trace only after InitializeRequestStackTrace
succeeds and store _init_request_stack_trace atomically (RELEASE/ACQUIRE),
so canRequestStackTrace() reports 'initialized', not merely 'present'.
Guard the extension params deallocation loop against a NULL params array.
Fix the synchronization-edge comment, drop the dead rollback store, pin
exact Deallocate counts (with a null sentinel slot catching the off-by-one
loop bound), and cover initializeRequestStackTrace()'s unset-init guard.
@jbachorik jbachorik added the AI label Oct 28, 2026
@jbachorik
jbachorik marked this pull request as ready for review October 28, 2026 16:50
@jbachorik
jbachorik requested a review from a team as a code owner October 28, 2026 16:50
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T16:54:05.085378Z 17aa9d2 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@datadog-prod-us1-5 datadog-prod-us1-5 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

Publishing the request function only after successful initialization is consistent with the reviewed callers and fallback paths. The null params guard also preserves extension cleanup without dereferencing a missing array.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 17aa9d2

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17aa9d2588

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -419,28 +430,42 @@ void VM::probeJFRRequestStackTrace() {
}

if (_agent_args._jvmtistacks) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve activation from per-recording jvmtistacks options

When the profiler is loaded through the normal JNI path, JavaProfiler_init0() calls initProfilerBridge() before any recording arguments are parsed, so _agent_args._jvmtistacks retains its default false value. This condition now prevents _request_stack_trace from ever being published, and the bridge is not probed again when a later start,jvmtistacks=true command reaches Profiler::selectCpuEngine(). Consequently, JNI-loaded users requesting JVMTI stacks at recording start always see canRequestStackTrace() == false and silently fall back to ASGCT; initialization/publication needs to be deferred to the recording option path or remain available after the initial probe.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — the JNI load path (JavaProfiler_init0 → initProfilerBridge) probes before any recording options are parsed, so _agent_args._jvmtistacks was still false and a per-recording start,jvmtistacks=true could never publish the extension.

Fixed in f0085fe: the probe now records the discovered extension function pointers unconditionally, and a new VM::enableRequestStackTrace() (idempotent, quiet when the extension is absent) initializes and publishes lazily. Profiler::start() and Profiler::check() call it when args._jvmtistacks is set, so per-recording options enable the extension at recording time. Covered by new gtest cases (LazyEnableAfterJvmtistacksDisabledProbe, LazyEnableQuietWhenExtensionAbsent, LazyEnableIdempotentAfterPublication); full :ddprof-lib:gtestDebug 1024 passed / 0 failed.

@dd-octo-sts

dd-octo-sts Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #40896903180 | Commit: 7ed43d9 | Duration: 19m 25s (longest job)

❌ 1 of 32 test jobs failed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - ✅ - -
8-ibm - ✅ - -
8-j9 ✅ ✅ - -
8-librca - - ✅ ✅
8-orcl - ❌ - -
11 - ✅ - -
11-j9 ✅ ✅ - -
11-librca - - ✅ ✅
17 ✅ ✅ - -
17-graal ✅ ✅ - -
17-j9 ✅ ✅ - -
17-librca - - ✅ ✅
21 ✅ ✅ - -
21-graal ✅ ✅ - -
21-librca - - ✅ ✅
25 ✅ ✅ - -
25-graal ✅ ✅ - -
25-librca - - ✅ ✅

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Failed Jobs

Summary: Total: 32 | Passed: 31 | Failed: 1


Updated: 2026-10-29 15:20:43 UTC

@dd-octo-sts

dd-octo-sts Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

❌ 39 passed, 1 failed out of 40 expected configurations

Test Matrix

Platform JDK 8 JDK 11 JDK 17 JDK 21 JDK 25
glibc-x64-hotspot ✅ ✅ ✅ ✅ ✅
glibc-x64-openj9 ✅ ✅ ✅ ✅ ✅
glibc-arm64-hotspot ✅ ✅ ✅ ✅ ✅
glibc-arm64-openj9 ✅ ✅ ✅ ✅ ✅
musl-x64-hotspot ✅ ✅ ✅ ✅ ✅
musl-x64-openj9 ✅ ✅ ✅ ✅ ❌
musl-arm64-hotspot ✅ ✅ ✅ ✅ ✅
musl-arm64-openj9 ✅ ✅ ✅ ✅ ✅

Failure Details

musl-x64-openj9-jdk25

Profiler-only:

"  Skipped (tracer not expected in this configuration)"
""
"[7/8] Checking for unexpected events..."
"  ✓ No unexpected events found"
""
"[8/8] Scenario-specific validation (ddprof_only)..."
"  Validating profiler-only scenario..."
"  ✓ Profiler-only scenario checks passed"
""
"=== Validation Summary ==="
"ExecutionSample:            0.0 events (datadog.ExecutionSample)"
"Stack traces:               0.0 samples"
"Thread diversity:           0.0 threads"
"Allocation samples:         0 events (jdk.ObjectAllocationSample)"
"ThreadAllocationStatistics: 43 events"
""
"VALIDATION_FAILED: One or more checks did not pass"

Script executed successfully: 425 commands
VALIDATION_FAILED: Validation checks did not pass

Links

The JNI load path probes before recording options are parsed, so
jvmtistacks was never requested at probe time and per-recording
start,jvmtistacks=true silently fell back to ASGCT. Record discovered
extension pointers unconditionally and add VM::enableRequestStackTrace(),
called from Profiler::start()/check() when jvmtistacks is requested.
@rkennke

rkennke commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Not sure if it should be part of this change, but one thing to note is that the upstream proposal has changed significantly since our side of it has been written:

  1. The function is now called RequestJFRStackTrace.
  2. There is no more enabling/intializing function anymore, and instead the machinery is enabled/initialized by enabling the JFR event jdk.StackTraceRequest.

See also: JEP draft: Native Profiler Hook for Unbiased Stack Traces (Experimental)

@jbachorik

Copy link
Copy Markdown
Collaborator Author

@rkennke That's actually a good call. Should this be fixed now or better wait till the upstream is final?

@datadog-prod-us1-5

Copy link
Copy Markdown

Pipelines

✨ Unblock PR with BitsAI

❌ Errors

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 3 Pipeline jobs failed

CI Run | test-matrix / test-linux-glibc-amd64 (8-orcl, debug, regular) — 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

DataDog/java-profiler | integration-test-x64-musl: [openj9, 25] — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

DataDog/java-profiler | post-pr-comment — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: f0085fe | Docs | View more details | Give us feedback!

@jbachorik
jbachorik marked this pull request as draft October 29, 2026 16:19
@rkennke

rkennke commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

@rkennke That's actually a good call. Should this be fixed now or better wait till the upstream is final?

Not sure. On one hand, the code as it currently is would not work/detect the JVMTI extension at all, with the current implementation. OTOH, now that the JEP is (hopefully) moving forward, I would expect increased activity on the implementation, which would be a chase. It's probably better to just wait it out until it's final.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants