Skip to content

Guard Recording::writeElement against SIGSEGV on a corrupted metadata tree - #692

Merged
jbachorik merged 8 commits into
mainfrom
sphinx/fix-jira-PROF-15075
Aug 6, 2026
Merged

Guard Recording::writeElement against SIGSEGV on a corrupted metadata tree#692
jbachorik merged 8 commits into
mainfrom
sphinx/fix-jira-PROF-15075

Conversation

@jbachorik

@jbachorikjbachorik commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?:
Adds a null check and a recursion-depth guard (10) to Recording::writeElement(), so a corrupted or dangling Element* in the JFR metadata tree produces a logged, truncated dump instead of a SIGSEGV. Each guard trip also increments a dedicated counter (metadata_tree_null_child / metadata_tree_depth_exceeded), which flows out as a datadog.ProfilerCounter JFR event through the existing counter-reporting path — a durable, queryable signal in case this recurs, rather than relying on stderr from an embedded native library (rarely captured/monitored). Also fixes a macOS x86_64 build issue in safeAccess.cpp (current_pc referenced an undefined context_rip macro instead of the DU3_PREFIX-based uc_mcontext accessor already used on aarch64), found while testing this change locally.

Motivation:
PROF-15075: a SIGSEGV crash cluster in Recording::writeElement, seen on JDK 17/21/25 with dd-trace-java 1.63.0. The production trace (writeElement x5 self-recursive <- writeMetadata <- switchChunk <- Profiler::dump) is a plain periodic chunk dump with no profiler restart involved, so what actually corrupts/nulls the Element* is not yet confirmed. This guard turns the crash into a safe, logged, telemetry-visible failure in the meantime.

Additional Notes:
The true trigger remains open. A core dump or heap-sanitizer run is what's needed to confirm it; the new counters and log lines are meant to catch and localize a recurrence in the meantime, not to explain away the underlying issue.

How to test the change?:

  • ./gradlew :ddprof-lib:buildDebug -Pskip-gtest and the flightRecorder_result_ut gtest suite pass with this change.
  • No automated test reproduces the crash itself, since the trigger isn't known yet; the guard is defense-in-depth.

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-15075

Unsure? Have a question? Request a review!

CopilotAI review requested due to automatic review settings July 28, 2026 07:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@datadog-prod-us1-6

This comment has been minimized.

@dd-octo-sts

dd-octo-stsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run:#31099759182 | Commit:59c0a67 | Duration: 17m 50s (longest job)

All 32 test jobs passed

Status Overview

JDKglibc-aarch64/debugglibc-amd64/debugmusl-aarch64/debugmusl-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

Summary: Total: 32 | Passed: 32 | Failed: 0


Updated: 2026-08-06 12:23:03 UTC

@jbachorikjbachorik changed the title Fix: SIGSEGV in Recording::writeElement during JFR chunk switchFix: SIGSEGV crash mitigation in Recording::writeElement during JFR chunk switchJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 11:40

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jbachorikjbachorik mentioned this pull request Jul 28, 2026
3 tasks
@jbachorikjbachorik changed the title Fix: SIGSEGV crash mitigation in Recording::writeElement during JFR chunk switchFix: guard against SIGSEGV in Recording::writeElement during JFR chunk switchJul 28, 2026
@jbachorikjbachorik changed the title Fix: guard against SIGSEGV in Recording::writeElement during JFR chunk switchGuard Recording::writeElement against SIGSEGV on a corrupted metadata treeJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 12:03

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 3583a36)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/127425053 Commit: 3583a36405ab89c0ee17e1543ccde56296732708

✅ Within expected boundaries

No significant runtime deltas (all within run-to-run noise) and no internal-counter outliers.

Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10345 ms (21 iters)✅ 10268 ms (21 iters)≈ -0.7% (±11.1%)— / —
akka-uct25✅ 8856 ms (24 iters)✅ 8781 ms (24 iters)≈ -0.8% (±10%)— / —
finagle-chirper21✅ 5986 ms (33 iters)✅ 5924 ms (33 iters)≈ -1% (±25.1%)⚠️ W:3 / ⚠️ W:3
finagle-chirper25✅ 5452 ms (36 iters)✅ 5465 ms (36 iters)≈ +0.2% (±23.8%)⚠️ W:4 / ⚠️ W:3
fj-kmeans21✅ 2693 ms (70 iters)✅ 2654 ms (71 iters)≈ -1.4% (±2.5%)— / —
fj-kmeans25✅ 2821 ms (66 iters)✅ 2798 ms (66 iters)≈ -0.8% (±2.7%)— / —
future-genetic21✅ 2081 ms (89 iters)✅ 2105 ms (88 iters)≈ +1.2% (±2.7%)— / —
future-genetic25✅ 2059 ms (91 iters)✅ 2098 ms (89 iters)≈ +1.9% (±2.6%)— / —
naive-bayes21✅ 1310 ms (131 iters)✅ 1249 ms (137 iters)≈ -4.7% (±31.7%)— / —
naive-bayes25✅ 1015 ms (169 iters)✅ 1009 ms (169 iters)≈ -0.6% (±31.8%)— / —
reactors21✅ 15968 ms (15 iters)✅ 16668 ms (15 iters)≈ +4.4% (±7.4%)— / —
reactors25✅ 18572 ms (15 iters)✅ 18539 ms (15 iters)≈ -0.2% (±4.1%)— / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅5 / 21952 / 1889✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅1 / 12254 / 2293✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅2 / 48656 / 8830✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅2 / 18271 / 8041✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅5 / 21256 / 1294✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅4 / 31274 / 1264✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅1 / 22980 / 2984✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅1 / ✅2907 / 2963✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅4 / 53554 / 3511✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅3 / 13491 / 3463✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅1 / ✅1609 / 1693✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅✅ / ✅1958 / 1906✅ / ✅✅ / ✅

CopilotAI review requested due to automatic review settings July 29, 2026 07:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 7b6ab83)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/127634397 Commit: 7b6ab83587dae844223407aac5b93089120ca975

⚠️ Significant outliers

  • 🟢 future-genetic (JDK 25): runtime -2.7% (2090→2033 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10334 ms (21 iters)✅ 10322 ms (21 iters)≈ -0.1% (±11.6%)— / —
akka-uct25✅ 8811 ms (24 iters)✅ 8871 ms (24 iters)≈ +0.7% (±9.7%)— / —
finagle-chirper21✅ 5932 ms (33 iters)✅ 6005 ms (33 iters)≈ +1.2% (±25.4%)⚠️ W:4 / ⚠️ W:3
finagle-chirper25✅ 5467 ms (36 iters)✅ 5423 ms (36 iters)≈ -0.8% (±23.9%)⚠️ W:3 / ⚠️ W:3
fj-kmeans21✅ 2754 ms (68 iters)✅ 2695 ms (70 iters)≈ -2.1% (±2.7%)— / —
fj-kmeans25✅ 2821 ms (66 iters)✅ 2811 ms (66 iters)≈ -0.4% (±2.6%)— / —
future-genetic21✅ 2075 ms (90 iters)✅ 2120 ms (87 iters)≈ +2.2% (±2.6%)— / —
future-genetic25✅ 2090 ms (89 iters)✅ 2033 ms (91 iters)🟢 -2.7%— / —
naive-bayes21✅ 1344 ms (128 iters)✅ 1307 ms (131 iters)≈ -2.8% (±32.2%)— / —
naive-bayes25✅ 1001 ms (171 iters)✅ 1012 ms (169 iters)≈ +1.1% (±31.8%)— / —
reactors21✅ 16074 ms (15 iters)✅ 16204 ms (15 iters)≈ +0.8% (±8%)— / —
reactors25✅ 18350 ms (15 iters)✅ 18478 ms (15 iters)≈ +0.7% (±4.9%)— / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅1 / ✅2099 / 2044✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅1 / ✅2259 / 2339✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅1 / 58410 / 8407✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅5 / 28363 / 8727✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅4 / 31298 / 1277✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅✅ / 101265 / 1270✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅✅ / 22895 / 3034✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅2 / 22913 / 2904✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅2 / 63528 / 3558✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅1 / 13500 / 3475✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅1 / 31638 / 1771✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅2 / ✅1893 / 1903✅ / ✅✅ / ✅

@jbachorik

Copy link
Copy Markdown
CollaboratorAuthor

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:7b6ab83587

ℹ️ 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".

Comment threadddprof-lib/src/main/cpp/flightRecorder.cpp Outdated
Comment threadddprof-lib/src/main/cpp/flightRecorder.cpp
Comment threadddprof-lib/src/main/cpp/flightRecorder.cpp
CopilotAI review requested due to automatic review settings July 29, 2026 12:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

CopilotAI review requested due to automatic review settings July 29, 2026 13:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 00070a8)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/127722360 Commit: 00070a827e5184836149e3b522036210fdd281ff

⚠️ Significant outliers

  • 🔴 future-genetic (JDK 25): runtime +4.1% (1986→2068 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10283 ms (21 iters)✅ 10259 ms (21 iters)≈ -0.2% (±11.6%)— / —
akka-uct25✅ 8938 ms (24 iters)✅ 8849 ms (24 iters)≈ -1% (±9.1%)— / —
finagle-chirper21✅ 6004 ms (33 iters)✅ 5945 ms (33 iters)≈ -1% (±25%)⚠️ W:3 / ⚠️ W:3
finagle-chirper25✅ 5529 ms (36 iters)✅ 5474 ms (36 iters)≈ -1% (±24.5%)⚠️ W:3 / ⚠️ W:3
fj-kmeans21✅ 2703 ms (70 iters)✅ 2704 ms (69 iters)≈ +0% (±2.7%)— / —
fj-kmeans25✅ 2818 ms (66 iters)✅ 2786 ms (67 iters)≈ -1.1% (±2.6%)— / —
future-genetic21✅ 2145 ms (87 iters)✅ 2098 ms (90 iters)≈ -2.2% (±2.6%)— / —
future-genetic25✅ 1986 ms (94 iters)✅ 2068 ms (90 iters)🔴 +4.1%— / —
naive-bayes21✅ 1269 ms (135 iters)✅ 1236 ms (138 iters)≈ -2.6% (±32%)— / —
naive-bayes25✅ 980 ms (174 iters)✅ 1017 ms (169 iters)≈ +3.8% (±32.5%)— / —
reactors21✅ 16812 ms (15 iters)✅ 16680 ms (15 iters)≈ -0.8% (±8.4%)— / —
reactors25✅ 17862 ms (15 iters)✅ 18605 ms (15 iters)≈ +4.2% (±4.4%)— / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅2 / 11939 / 2016✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅✅ / ✅2347 / 2190✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅4 / 48381 / 8397✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅✅ / 18334 / 8682✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅2 / ✅1276 / 1255✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅1 / ✅1262 / 1288✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅3 / 12919 / 2913✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅2 / 23533 / 3499✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅4 / 23484 / 3494✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅✅ / ✅1633 / 1836✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅✅ / 11778 / 1941✅ / ✅✅ / ✅

@dd-octo-sts

Copy link
Copy Markdown
Contributor

Reliability & Chaos Results

All reliability & chaos checks passed Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/127721763

@jbachorik
jbachorikforce-pushed the sphinx/fix-jira-PROF-15075 branch from 00070a8 to 6c71e41CompareAugust 4, 2026 10:51
CopilotAI review requested due to automatic review settings August 4, 2026 10:51
@dd-octo-sts

dd-octo-stsBot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Scan-Build Report

User:runner@runnervmvrwv9
Working Directory:/home/runner/work/java-profiler/java-profiler/ddprof-lib/src/test/make
Command Line:make -j4 all
Clang Version:Ubuntu clang version 18.1.3 (1ubuntu1)
Date:Thu Aug 6 12:04:58 2026

Bug Summary

Bug TypeQuantityDisplay?
All Bugs1
Logic error
Dereference of null pointer1

Reports

Bug GroupBug Type ▾FileFunction/MethodLinePath Length
Logic errorDereference of null pointerprofiler.hfindLibraryByAddress51713

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

ddprof-lib/src/main/cpp/flightRecorder.cpp:1167

  • The depth-limit ERROR log in writeElement() never fires in normal recursion because recursion is stopped earlier via truncate_children at depth==10 (so depth > 10 is unreachable from the only call site). That means depth truncation is currently counter-only with no log line, which contradicts the stated goal of a logged truncated dump when the metadata tree is unexpectedly deep/cyclic. Consider emitting a one-time ERROR when truncate_children is true (e.g., inside countSerializableChildren), while keeping the per-child counter increments as-is.
size_t Recording::countSerializableChildren(
const std::vector<const Element *> &children, int depth) {
// Children one level deeper than `depth` are what writeElement() would
// truncate on its own depth check, so exclude them here too, before being
// counted, so child_count always matches the number of children actually
// serialized below (an inflated count would make the metadata stream
// itself malformed).
bool truncate_children = depth + 1 > 10;
size_t child_count = 0;
for (size_t i = 0; i < children.size(); i++) {
if (children[i] == nullptr) {
Counters::increment(METADATA_TREE_NULL_CHILD);
fprintf(stderr, "[ddprof] [WARN] writeElement skipping null child at index %zu\n", i);
} else if (truncate_children) {
Counters::increment(METADATA_TREE_DEPTH_EXCEEDED);
} else {
child_count++;
}

docs/sphinx/specs/2026-07-27-sigsegv-in-recording-writeelement.md:18

  • This spec describes two fixes as being “shipped together” and includes detailed scope/correctness requirements for a JfrMetadata::reset() cleanup, but this PR does not implement any reset() deletion logic (reset() still just clears vectors). Please either implement the reset() cleanup in this PR, or update the spec to clearly mark the reset() leak work as out-of-scope/follow-up so the doc matches the actual changes.
What follows are two independently-real, independently-justified fixes shipped together. Fix 2 (defensive traversal) is the one that actually addresses the observed crash shape, regardless of trigger. Fix 1 (reset() leak) is a genuine bug worth closing but is not believed to be the trigger for this specific crash cluster, since it requires a restart cycle this deployment's lifecycle doesn't exercise.
1. **[Latent, not confirmed as trigger] Memory leak in JfrMetadata::reset():** The `reset()` function (jfrMetadata.cpp:32–40) calls `_root._children.clear()` but never deletes the heap-allocated Element objects allocated in `initialize()` (lines 183, 145–149, 207). If `Profiler::start()` were ever called a second time in the same process (restart), and the heap allocator reused freed memory addresses, `writeElement()` could dereference stale/corrupted pointers. Real bug, but only reachable via a restart cycle that does not occur in normal deployments — see status note above.
2. **[Addresses the observed crash shape] Unbounded recursion without cycle or null guard:** `writeElement()` recursively traverses the Element tree without a depth limit, cycle detection, or null checks on `e`/`e->_children[i]`. Whatever is producing the corrupted/dangling `Element*` in production, this is what turns it into a SIGSEGV instead of a safely-logged truncation. This is the mitigation that actually matters for PROF-15075 until the true trigger is found.

@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 655da91)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/128813465 Commit: 655da91c4a91c60ba941e5d60221bcb1f4e681f3

⚠️ Significant outliers

  • 🔴 future-genetic (JDK 25): runtime +3.3% (2056→2124 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10475 ms (21 iters)✅ 10226 ms (21 iters)≈ -2.4% (±10.4%)— / —
akka-uct25✅ 8831 ms (24 iters)✅ 8793 ms (24 iters)≈ -0.4% (±9.9%)— / —
finagle-chirper21✅ 5965 ms (33 iters)✅ 5988 ms (33 iters)≈ +0.4% (±25.3%)⚠️ W:4 / ⚠️ W:3
finagle-chirper25✅ 5440 ms (36 iters)✅ 5496 ms (36 iters)≈ +1% (±24%)⚠️ W:3 / ⚠️ W:4
fj-kmeans21✅ 2696 ms (70 iters)✅ 2696 ms (69 iters)≈ 0% (±2.7%)— / —
fj-kmeans25✅ 2848 ms (66 iters)✅ 2852 ms (66 iters)≈ +0.1% (±2.6%)— / —
future-genetic21✅ 2076 ms (89 iters)✅ 2099 ms (88 iters)≈ +1.1% (±2.6%)— / —
future-genetic25✅ 2056 ms (90 iters)✅ 2124 ms (87 iters)🔴 +3.3%— / —
naive-bayes21✅ 1305 ms (132 iters)✅ 1253 ms (136 iters)≈ -4% (±31.6%)— / —
naive-bayes25✅ 1012 ms (168 iters)✅ 975 ms (175 iters)≈ -3.7% (±31.4%)— / —
reactors21✅ 16279 ms (15 iters)✅ 16648 ms (15 iters)≈ +2.3% (±7.3%)— / —
reactors25✅ 18667 ms (15 iters)✅ 18594 ms (15 iters)≈ -0.4% (±4.5%)— / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅1 / 42286 / 2162✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅7 / 48919 / 8506✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅✅ / ✅8223 / 8159✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅3 / 41297 / 1255✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅1 / 21290 / 1270✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅✅ / 12915 / 2970✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅1 / ✅2862 / 2900✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅5 / 53537 / 3509✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅4 / 33467 / 3517✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅✅ / 11591 / 1666✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅2 / ✅1839 / 1910✅ / ✅✅ / ✅

CopilotAI review requested due to automatic review settings August 4, 2026 14:24

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

ddprof-lib/src/main/cpp/flightRecorder.cpp:1182

  • depth > 10 handling is effectively unreachable with the current recursion gate (truncate_children = depth + 1 > 10 and if (!truncate_children) around the loop). As a result, the [ERROR] writeElement depth limit exceeded... log and its counter increment will never trigger from the normal call path, and all depth-exceeded accounting comes from countSerializableChildren() instead. Consider either removing the dead depth > 10 block, or reworking the depth logic so a single, reachable place is responsible for both truncation and telemetry (ideally logging once per truncated element rather than per child).
 if (depth > 10) {
// stderr from an embedded native lib is rarely captured or monitored, and
// we don't yet know what corrupts the tree, so the counter is the durable
// signal here — an unmonitored log line would let it recur invisibly.
Counters::increment(METADATA_TREE_DEPTH_EXCEEDED);
fprintf(stderr, "[ddprof] [ERROR] writeElement depth limit exceeded, truncating output\n");
return;

ddprof-lib/src/main/cpp/counters.h:139

  • The comment claims writeElement() guards against a "dangling" metadata tree, but the added guards only handle null children and excessive recursion depth. A truly dangling (non-null invalid) Element* would still SIGSEGV on dereference, so this wording is misleading.
 /* writeElement() guards against a corrupted/dangling JfrMetadata tree. \
* Root cause is still unconfirmed, so these counters are the durable \
* signal for spotting a recurrence. */ \

@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 1fa759e)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/128867886 Commit: 1fa759ec30dde382e20f301a1973bf924e6bfd04

⚠️ Significant outliers

  • 🔴 future-genetic (JDK 25): runtime +3.6% (2001→2074 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10251 ms (21 iters)✅ 10286 ms (21 iters)≈ +0.3% (±11.3%)— / —
akka-uct25✅ 8900 ms (24 iters)✅ 8812 ms (24 iters)≈ -1% (±10.5%)— / —
finagle-chirper21✅ 5959 ms (33 iters)✅ 5950 ms (33 iters)≈ -0.2% (±25.3%)⚠️ W:3 / ⚠️ W:3
finagle-chirper25✅ 5431 ms (36 iters)✅ 5440 ms (36 iters)≈ +0.2% (±24.3%)⚠️ W:4 / ⚠️ W:3
fj-kmeans21✅ 2728 ms (68 iters)✅ 2667 ms (69 iters)≈ -2.2% (±2.6%)— / —
fj-kmeans25✅ 2834 ms (66 iters)✅ 2834 ms (66 iters)≈ 0% (±2.6%)— / —
future-genetic21✅ 2075 ms (89 iters)✅ 2095 ms (89 iters)≈ +1% (±2.7%)— / —
future-genetic25✅ 2001 ms (93 iters)✅ 2074 ms (90 iters)🔴 +3.6%— / —
naive-bayes21✅ 1273 ms (134 iters)✅ 1276 ms (135 iters)≈ +0.2% (±32.8%)— / —
naive-bayes25✅ 1009 ms (170 iters)✅ 1024 ms (167 iters)≈ +1.5% (±31.9%)— / —
reactors21✅ 16217 ms (15 iters)✅ 16346 ms (15 iters)≈ +0.8% (±7.4%)— / —
reactors25✅ 18641 ms (15 iters)✅ 18034 ms (15 iters)≈ -3.3% (±5.2%)— / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅1 / 11927 / 1959✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅2 / ✅2300 / 2231✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅2 / 28848 / 8550✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅1 / ✅8281 / 8345✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅2 / 11240 / 1232✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅✅ / 11268 / 1276✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅1 / 42945 / 3025✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅1 / 32958 / 2871✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅2 / 53493 / 3532✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅3 / 93469 / 3497✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅✅ / 31792 / 1683✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅✅ / ✅1884 / 1766✅ / ✅✅ / ✅

jbachorikand others added 6 commits August 6, 2026 13:37
Drop the JfrMetadata::reset() leak fix and its tests: that leak is
only reachable via a Profiler restart (reset() -> initialize() twice
in the same process), which doesn't happen in production. It's real
but unrelated to this crash and is fixed separately in
DataDog/java-profiler#<leak-pr>.
The PROF-15075 crash trace (dump -> switchChunk -> writeMetadata ->
writeElement, no restart involved) is addressed by the null-check and
recursion-depth guard in writeElement alone; true trigger still
unconfirmed, see updated spec.
stderr from an embedded native lib is rarely captured/monitored, and
this guard exists precisely because the tree-corruption trigger is
unconfirmed (PROF-15075) -- an unmonitored log line would let it
recur invisibly. Increment metadata_tree_null_child /
metadata_tree_depth_exceeded so a recurrence shows up as a
datadog.ProfilerCounter JFR event instead of only stderr.
Exclude depth-truncated children from the encoded child_count so the
JFR metadata stream stays structurally valid, and add gtest coverage
for the null-child and depth-boundary serialization guards.
Each TEST now uses an ElementOwner that frees its Elements via
unique_ptr on scope exit, instead of leaking raw new-allocated
Elements per test run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 6, 2026 11:40
@jbachorik
jbachorikforce-pushed the sphinx/fix-jira-PROF-15075 branch from 1fa759e to ee6f9bdCompareAugust 6, 2026 11:40
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jbachorik

Copy link
Copy Markdown
CollaboratorAuthor

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:ee6f9bda5a

ℹ️ 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".

Comment threadddprof-lib/src/main/cpp/flightRecorder.cpp
Comment threadddprof-lib/src/test/cpp/flightRecorder_metadata_ut.cpp

@rkennkerkennke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Depth-exceeded truncation is telemetrized but never logged to stderr — unlike the null-child guard.

In Recording::writeElement() (flightRecorder.cpp), the if (depth > 10) { ...; fprintf(stderr, "...depth limit exceeded...") } block at the top of the function is dead code. writeElement() is only reachable at depth 0..10: the sole recursive call site only fires when !truncate_children (i.e. depth + 1 <= 10), so depth can never exceed 10 when writeElement() is entered. That means this fprintf — and the Counters::increment(METADATA_TREE_DEPTH_EXCEEDED) alongside it — never execute.

The actual telemetry for depth-truncated children does fire, but from a different place: countSerializableChildren()'s else if (truncate_children) branch, which runs on every writeElement() call and correctly increments METADATA_TREE_DEPTH_EXCEEDED once per excluded child. It just has no accompanying fprintf, unlike the null-child branch right above it, which does both Counters::increment(METADATA_TREE_NULL_CHILD) and a fprintf(stderr, ...).

Net effect: a null-child truncation is both counted and logged; a depth-exceeded truncation is only counted, silently. That's inconsistent with the PR's own stated rationale — that stderr logging is needed as a human-visible backstop alongside the counters, precisely because the tree-corruption trigger is unconfirmed.

Suggested fix: move the fprintf into countSerializableChildren()'s truncate_children branch (next to its existing counter increment), and delete the now-unreachable if (depth > 10) block in writeElement() entirely, rather than trying to make that block reachable. Making it reachable instead (e.g. by loosening the caller's truncate_children guard so recursion continues to depth 11) would double-count the same truncation event (once in countSerializableChildren(), again in writeElement()) and would reintroduce the exact child_count/actual-serialized-children mismatch this PR's own regression tests guard against, since countSerializableChildren() would have already excluded that child from the encoded count before writeElement() serialized it anyway.

The reset()-regression suite belongs to main (merged separately in #694)
and was never part of this PR's scope; a rebase conflict resolution
deleted it by mistake.
CopilotAI review requested due to automatic review settings August 6, 2026 11:57
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

writeElement()'s depth>10 guard was dead code: the sole recursive call
site never invokes it above depth 10. Depth-exceeded truncation was
therefore only ever counted, never logged, unlike the null-child case
right above it. Move the fprintf into countSerializableChildren()'s
truncate_children branch and drop the unreachable block.
CopilotAI review requested due to automatic review settings August 6, 2026 12:02
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jbachorik

Copy link
Copy Markdown
CollaboratorAuthor

Good catch on the dead code — fixed in 8a8d4ce. Moved the fprintf into countSerializableChildren()'s truncate_children branch (next to its existing counter increment) and dropped the unreachable depth > 10 block in writeElement(). Depth-exceeded truncation is now logged the same way the null-child case already was. Both regression suites still pass (gtestDebug_flightRecorder_metadata_ut, gtestDebug_jfrMetadata_ut).

@dd-octo-sts

Copy link
Copy Markdown
Contributor

All 40 integration tests passed

📊 Dashboard · 👷 Pipeline · 📦 8a8d4ce0

@jbachorik
jbachorik merged commit bea1a9b into mainAug 6, 2026
109 checks passed
@jbachorik
jbachorik deleted the sphinx/fix-jira-PROF-15075 branch August 6, 2026 13:02
@github-actionsgithub-actionsBot added this to the 1.49.0 milestone Aug 6, 2026
Sign up for freeto 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.

3 participants

@jbachorik@rkennke