Skip to content

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

Open
jbachorik wants to merge 7 commits into
mainfrom
sphinx/fix-jira-PROF-15075
Open

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

Conversation

@jbachorik

@jbachorik jbachorik 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!

@jbachorik jbachorik added the AI label Jul 28, 2026
Copilot AI review requested due to automatic review settings July 28, 2026 07:45

Copilot AI 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-sts Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #30918776776 | Commit: 8766df2 | Duration: 3h 0m 17s (longest job)

All 32 test jobs passed

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

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


Updated: 2026-08-04 17:28:59 UTC

@jbachorik jbachorik changed the title Fix: SIGSEGV in Recording::writeElement during JFR chunk switch Fix: SIGSEGV crash mitigation in Recording::writeElement during JFR chunk switch Jul 28, 2026
Copilot AI review requested due to automatic review settings July 28, 2026 11:40

Copilot AI 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 jbachorik changed the title Fix: SIGSEGV crash mitigation in Recording::writeElement during JFR chunk switch Fix: guard against SIGSEGV in Recording::writeElement during JFR chunk switch Jul 28, 2026
@jbachorik jbachorik changed the title Fix: guard against SIGSEGV in Recording::writeElement during JFR chunk switch Guard Recording::writeElement against SIGSEGV on a corrupted metadata tree Jul 28, 2026
Copilot AI review requested due to automatic review settings July 28, 2026 12:03

Copilot AI 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

dd-octo-sts Bot commented Jul 28, 2026

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)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10345 ms (21 iters) ✅ 10268 ms (21 iters) ≈ -0.7% (±11.1%) — / —
akka-uct 25 ✅ 8856 ms (24 iters) ✅ 8781 ms (24 iters) ≈ -0.8% (±10%) — / —
finagle-chirper 21 ✅ 5986 ms (33 iters) ✅ 5924 ms (33 iters) ≈ -1% (±25.1%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5452 ms (36 iters) ✅ 5465 ms (36 iters) ≈ +0.2% (±23.8%) ⚠️ W:4 / ⚠️ W:3
fj-kmeans 21 ✅ 2693 ms (70 iters) ✅ 2654 ms (71 iters) ≈ -1.4% (±2.5%) — / —
fj-kmeans 25 ✅ 2821 ms (66 iters) ✅ 2798 ms (66 iters) ≈ -0.8% (±2.7%) — / —
future-genetic 21 ✅ 2081 ms (89 iters) ✅ 2105 ms (88 iters) ≈ +1.2% (±2.7%) — / —
future-genetic 25 ✅ 2059 ms (91 iters) ✅ 2098 ms (89 iters) ≈ +1.9% (±2.6%) — / —
naive-bayes 21 ✅ 1310 ms (131 iters) ✅ 1249 ms (137 iters) ≈ -4.7% (±31.7%) — / —
naive-bayes 25 ✅ 1015 ms (169 iters) ✅ 1009 ms (169 iters) ≈ -0.6% (±31.8%) — / —
reactors 21 ✅ 15968 ms (15 iters) ✅ 16668 ms (15 iters) ≈ +4.4% (±7.4%) — / —
reactors 25 ✅ 18572 ms (15 iters) ✅ 18539 ms (15 iters) ≈ -0.2% (±4.1%) — / —
Internal counter details (ddprof)

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

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 5 / 2 1952 / 1889 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 1 / 1 2254 / 2293 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 2 / 4 8656 / 8830 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ 2 / 1 8271 / 8041 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 5 / 2 1256 / 1294 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 4 / 3 1274 / 1264 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ 1 / 2 2980 / 2984 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 2907 / 2963 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 4 / 5 3554 / 3511 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 3 / 1 3491 / 3463 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ 1 / ✅ 1609 / 1693 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ ✅ / ✅ 1958 / 1906 ✅ / ✅ ✅ / ✅

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

Copilot AI 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

dd-octo-sts Bot commented Jul 29, 2026

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)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10334 ms (21 iters) ✅ 10322 ms (21 iters) ≈ -0.1% (±11.6%) — / —
akka-uct 25 ✅ 8811 ms (24 iters) ✅ 8871 ms (24 iters) ≈ +0.7% (±9.7%) — / —
finagle-chirper 21 ✅ 5932 ms (33 iters) ✅ 6005 ms (33 iters) ≈ +1.2% (±25.4%) ⚠️ W:4 / ⚠️ W:3
finagle-chirper 25 ✅ 5467 ms (36 iters) ✅ 5423 ms (36 iters) ≈ -0.8% (±23.9%) ⚠️ W:3 / ⚠️ W:3
fj-kmeans 21 ✅ 2754 ms (68 iters) ✅ 2695 ms (70 iters) ≈ -2.1% (±2.7%) — / —
fj-kmeans 25 ✅ 2821 ms (66 iters) ✅ 2811 ms (66 iters) ≈ -0.4% (±2.6%) — / —
future-genetic 21 ✅ 2075 ms (90 iters) ✅ 2120 ms (87 iters) ≈ +2.2% (±2.6%) — / —
future-genetic 25 ✅ 2090 ms (89 iters) ✅ 2033 ms (91 iters) 🟢 -2.7% — / —
naive-bayes 21 ✅ 1344 ms (128 iters) ✅ 1307 ms (131 iters) ≈ -2.8% (±32.2%) — / —
naive-bayes 25 ✅ 1001 ms (171 iters) ✅ 1012 ms (169 iters) ≈ +1.1% (±31.8%) — / —
reactors 21 ✅ 16074 ms (15 iters) ✅ 16204 ms (15 iters) ≈ +0.8% (±8%) — / —
reactors 25 ✅ 18350 ms (15 iters) ✅ 18478 ms (15 iters) ≈ +0.7% (±4.9%) — / —
Internal counter details (ddprof)

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

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 1 / ✅ 2099 / 2044 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 2259 / 2339 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 1 / 5 8410 / 8407 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ 5 / 2 8363 / 8727 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 4 / 3 1298 / 1277 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ ✅ / 10 1265 / 1270 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ ✅ / 2 2895 / 3034 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 2 / 2 2913 / 2904 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 2 / 6 3528 / 3558 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 1 / 1 3500 / 3475 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ 1 / 3 1638 / 1771 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ 2 / ✅ 1893 / 1903 ✅ / ✅ ✅ / ✅

@jbachorik

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: 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 thread ddprof-lib/src/main/cpp/flightRecorder.cpp Outdated
Comment thread ddprof-lib/src/main/cpp/flightRecorder.cpp Outdated
Comment thread ddprof-lib/src/main/cpp/flightRecorder.cpp
Copilot AI review requested due to automatic review settings July 29, 2026 12:35

Copilot AI 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.

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

Copilot AI 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

dd-octo-sts Bot commented Jul 29, 2026

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)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10283 ms (21 iters) ✅ 10259 ms (21 iters) ≈ -0.2% (±11.6%) — / —
akka-uct 25 ✅ 8938 ms (24 iters) ✅ 8849 ms (24 iters) ≈ -1% (±9.1%) — / —
finagle-chirper 21 ✅ 6004 ms (33 iters) ✅ 5945 ms (33 iters) ≈ -1% (±25%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5529 ms (36 iters) ✅ 5474 ms (36 iters) ≈ -1% (±24.5%) ⚠️ W:3 / ⚠️ W:3
fj-kmeans 21 ✅ 2703 ms (70 iters) ✅ 2704 ms (69 iters) ≈ +0% (±2.7%) — / —
fj-kmeans 25 ✅ 2818 ms (66 iters) ✅ 2786 ms (67 iters) ≈ -1.1% (±2.6%) — / —
future-genetic 21 ✅ 2145 ms (87 iters) ✅ 2098 ms (90 iters) ≈ -2.2% (±2.6%) — / —
future-genetic 25 ✅ 1986 ms (94 iters) ✅ 2068 ms (90 iters) 🔴 +4.1% — / —
naive-bayes 21 ✅ 1269 ms (135 iters) ✅ 1236 ms (138 iters) ≈ -2.6% (±32%) — / —
naive-bayes 25 ✅ 980 ms (174 iters) ✅ 1017 ms (169 iters) ≈ +3.8% (±32.5%) — / —
reactors 21 ✅ 16812 ms (15 iters) ✅ 16680 ms (15 iters) ≈ -0.8% (±8.4%) — / —
reactors 25 ✅ 17862 ms (15 iters) ✅ 18605 ms (15 iters) ≈ +4.2% (±4.4%) — / —
Internal counter details (ddprof)

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

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 2 / 1 1939 / 2016 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ ✅ / ✅ 2347 / 2190 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 4 / 4 8381 / 8397 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ ✅ / 1 8334 / 8682 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 2 / ✅ 1276 / 1255 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 1262 / 1288 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 3 / 1 2919 / 2913 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 2 / 2 3533 / 3499 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 4 / 2 3484 / 3494 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ ✅ / ✅ 1633 / 1836 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ ✅ / 1 1778 / 1941 ✅ / ✅ ✅ / ✅

@dd-octo-sts

dd-octo-sts Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Reliability & Chaos Results

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

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

dd-octo-sts Bot 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:Tue Aug 4 14:26:52 2026

Bug Summary

Bug TypeQuantityDisplay?
All Bugs1
Logic error
Dereference of null pointer1

Reports

Bug Group Bug Type ▾ File Function/Method Line Path Length
Logic errorDereference of null pointerprofiler.hfindLibraryByAddress51713

Copilot AI 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.

Copilot AI review requested due to automatic review settings August 4, 2026 11:02

Copilot AI 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 (4)

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

  • The "Primary fixes" section lists the JfrMetadata::reset() cleanup as a primary fix, but that implementation is not part of this PR. Mark it explicitly as follow-up/out-of-scope here as well (or add the missing code change) to avoid readers assuming the leak was fixed by this PR.
1. **jfrMetadata.cpp:32–40 (JfrMetadata::reset)** — Memory leak: Implement recursive deletion of all heap-allocated Elements in `_root._children` before clearing the vector. Add a private helper function `void deleteElementTree(Element* e)` that recursively deletes all children and the element itself.

2. **flightRecorder.cpp:1140–1156 (Recording::writeElement)** — Unbounded recursion and null-pointer dereference: Add a depth parameter (default 0) with a check on entry; if `depth > 10`, log ERROR and return without recursing. Add null checks for `e`, `e->_children[i]` with defensive logging and continue-on-skip behavior.

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

  • countSerializableChildren() currently emits one stderr line per null child and does per-child atomic counter increments. On a heavily corrupted metadata tree this can flood logs during periodic chunk dumps and adds unnecessary atomic ops. Consider batching null/truncated child counts and logging once per element (and using a single counter increment with delta).
  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) {

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

  • writeElement() returns immediately on a null element without incrementing METADATA_TREE_NULL_CHILD or logging. If the entrypoint ever sees a null Element* (e.g., corrupted root), this loses the durable telemetry signal the PR description/doc calls out.
  if (e == nullptr) {
    return;
  }

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

  • This spec says two fixes are "shipped together" and details a JfrMetadata::reset() leak fix, but this PR’s code changes do not touch jfrMetadata.*. Please clarify in the spec that the reset() leak item is a follow-up (or add the corresponding code change) so the doc matches what’s actually in the PR.

This issue also appears on line 46 of the same file.

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.

jbachorik and others added 6 commits August 4, 2026 13:10
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>
Copilot AI review requested due to automatic review settings August 4, 2026 11:11
@jbachorik
jbachorik force-pushed the sphinx/fix-jira-PROF-15075 branch from 46757e3 to 655da91 Compare August 4, 2026 11:11
@jbachorik
jbachorik marked this pull request as ready for review August 4, 2026 11:13
@jbachorik
jbachorik requested a review from a team as a code owner August 4, 2026 11:13

Copilot AI 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 (1)

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

  • When truncate_children is true (depth boundary), this helper only increments METADATA_TREE_DEPTH_EXCEEDED but emits no error log. Since writeElement() also stops recursing when truncate_children is true, the depth > 10 branch (and its [ERROR] ... truncating output log) is effectively unreachable from the normal recursion path, so deep/cyclic metadata trees can be silently truncated except for the counter signal. Consider logging once per truncated node (not once per child) when depth limiting is applied, so the PR behavior matches the "logged, truncated dump" description and improves diagnosability in logs.
    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);

@dd-octo-sts

dd-octo-sts Bot commented Aug 4, 2026

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)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10475 ms (21 iters) ✅ 10226 ms (21 iters) ≈ -2.4% (±10.4%) — / —
akka-uct 25 ✅ 8831 ms (24 iters) ✅ 8793 ms (24 iters) ≈ -0.4% (±9.9%) — / —
finagle-chirper 21 ✅ 5965 ms (33 iters) ✅ 5988 ms (33 iters) ≈ +0.4% (±25.3%) ⚠️ W:4 / ⚠️ W:3
finagle-chirper 25 ✅ 5440 ms (36 iters) ✅ 5496 ms (36 iters) ≈ +1% (±24%) ⚠️ W:3 / ⚠️ W:4
fj-kmeans 21 ✅ 2696 ms (70 iters) ✅ 2696 ms (69 iters) ≈ 0% (±2.7%) — / —
fj-kmeans 25 ✅ 2848 ms (66 iters) ✅ 2852 ms (66 iters) ≈ +0.1% (±2.6%) — / —
future-genetic 21 ✅ 2076 ms (89 iters) ✅ 2099 ms (88 iters) ≈ +1.1% (±2.6%) — / —
future-genetic 25 ✅ 2056 ms (90 iters) ✅ 2124 ms (87 iters) 🔴 +3.3% — / —
naive-bayes 21 ✅ 1305 ms (132 iters) ✅ 1253 ms (136 iters) ≈ -4% (±31.6%) — / —
naive-bayes 25 ✅ 1012 ms (168 iters) ✅ 975 ms (175 iters) ≈ -3.7% (±31.4%) — / —
reactors 21 ✅ 16279 ms (15 iters) ✅ 16648 ms (15 iters) ≈ +2.3% (±7.3%) — / —
reactors 25 ✅ 18667 ms (15 iters) ✅ 18594 ms (15 iters) ≈ -0.4% (±4.5%) — / —
Internal counter details (ddprof)

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

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 1 / 4 2286 / 2162 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 7 / 4 8919 / 8506 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ ✅ / ✅ 8223 / 8159 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 3 / 4 1297 / 1255 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 1 / 2 1290 / 1270 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ ✅ / 1 2915 / 2970 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 2862 / 2900 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 5 / 5 3537 / 3509 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 4 / 3 3467 / 3517 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ ✅ / 1 1591 / 1666 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ 2 / ✅ 1839 / 1910 ✅ / ✅ ✅ / ✅

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

Copilot AI 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

dd-octo-sts Bot commented Aug 4, 2026

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)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10251 ms (21 iters) ✅ 10286 ms (21 iters) ≈ +0.3% (±11.3%) — / —
akka-uct 25 ✅ 8900 ms (24 iters) ✅ 8812 ms (24 iters) ≈ -1% (±10.5%) — / —
finagle-chirper 21 ✅ 5959 ms (33 iters) ✅ 5950 ms (33 iters) ≈ -0.2% (±25.3%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5431 ms (36 iters) ✅ 5440 ms (36 iters) ≈ +0.2% (±24.3%) ⚠️ W:4 / ⚠️ W:3
fj-kmeans 21 ✅ 2728 ms (68 iters) ✅ 2667 ms (69 iters) ≈ -2.2% (±2.6%) — / —
fj-kmeans 25 ✅ 2834 ms (66 iters) ✅ 2834 ms (66 iters) ≈ 0% (±2.6%) — / —
future-genetic 21 ✅ 2075 ms (89 iters) ✅ 2095 ms (89 iters) ≈ +1% (±2.7%) — / —
future-genetic 25 ✅ 2001 ms (93 iters) ✅ 2074 ms (90 iters) 🔴 +3.6% — / —
naive-bayes 21 ✅ 1273 ms (134 iters) ✅ 1276 ms (135 iters) ≈ +0.2% (±32.8%) — / —
naive-bayes 25 ✅ 1009 ms (170 iters) ✅ 1024 ms (167 iters) ≈ +1.5% (±31.9%) — / —
reactors 21 ✅ 16217 ms (15 iters) ✅ 16346 ms (15 iters) ≈ +0.8% (±7.4%) — / —
reactors 25 ✅ 18641 ms (15 iters) ✅ 18034 ms (15 iters) ≈ -3.3% (±5.2%) — / —
Internal counter details (ddprof)

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

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 1 / 1 1927 / 1959 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 2 / ✅ 2300 / 2231 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 2 / 2 8848 / 8550 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 8281 / 8345 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 2 / 1 1240 / 1232 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ ✅ / 1 1268 / 1276 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ 1 / 4 2945 / 3025 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 1 / 3 2958 / 2871 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 2 / 5 3493 / 3532 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 3 / 9 3469 / 3497 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ ✅ / 3 1792 / 1683 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ ✅ / ✅ 1884 / 1766 ✅ / ✅ ✅ / ✅

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