Skip to content

Use sigsetjmp/siglongjmp instead of setjmp/longjmp - #686

Merged
kaahos merged 10 commits into
mainfrom
zgu/setjmp
Jul 27, 2026
Merged

Use sigsetjmp/siglongjmp instead of setjmp/longjmp#686
kaahos merged 10 commits into
mainfrom
zgu/setjmp

Conversation

@zhengyu123

@zhengyu123zhengyu123 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?:
To perform a non-local jump out of a signal handler in POSIX C, you should use sigsetjmp() and siglongjmp() rather than standard setjmp() and longjmp(). [1, 2]

While longjmp() safely unwinds the execution stack, it does not consistently restore the process's signal mask. This means the signal that triggered your handler could remain blocked indefinitely. Using siglongjmp() ensures that your signal mask returns to its exact state prior to the signal delivery. [1, 2, 3, 4, 5]

Motivation:
Improve stability

Additional Notes:
-Regular CI tests

How to test the change?:

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

Unsure? Have a question? Request a review!

CopilotAI review requested due to automatic review settings July 24, 2026 21:09

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

This PR aims to make HotSpot crash-protection jumps signal-safe by migrating from setjmp/longjmp to sigsetjmp/siglongjmp in the native stack-walking fault-recovery path.

Changes:

  • Switched the per-thread stored jump context type from jmp_buf* to sigjmp_buf*.
  • Updated HotSpot fault recovery to use siglongjmp() when unwinding from the SEGV handler.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
ddprof-lib/src/main/cpp/threadLocalData.hStores the crash-protection jump context as sigjmp_buf* instead of jmp_buf*.
ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cppUses sigjmp_buf for the crash-protection context and siglongjmp() for recovery.
Comments suppressed due to low confidence (1)

ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp:258

  • This comment references setjmp/longjmp, but the code now uses sigjmp_buf/siglongjmp. Update the wording to match the new control-flow mechanism.
 // Should be preserved across setjmp/longjmp

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp
@dd-octo-sts

dd-octo-stsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run:#30256437751 | Commit:72e5659 | Duration: 14m 15s (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-07-27 10:42:44 UTC

CopilotAI review requested due to automatic review settings July 24, 2026 21:56

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 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:17

  • The header comment still refers to jmp_buf* and longjmp() even though the implementation has been switched to sigsetjmp/siglongjmp. This is misleading (and suggests the wrong buffer type is stored on ProfiledThread).

This issue also appears in the following locations of the same file:

  • line 283
  • line 294
 * Crash recovery inside walkVM relies on sigsetjmp/siglongjmp:
* 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx),
* chaining it with whatever context was already installed so a
* signal-based sampler interrupting a non-signal-based sampler's own
* in-flight walkVM() call doesn't clobber the outer call's context.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:298

  • This test now uses sigsetjmp/siglongjmp, but the jump buffers are still declared as jmp_buf/jmp_buf*. Use sigjmp_buf consistently to avoid relying on platform typedef quirks and to match the production API (ProfiledThread::setJmpCtx(sigjmp_buf*)). Also update the nearby comments/FAIL message to mention siglongjmp.
 if (sigsetjmp(outer_ctx, 1) != 0) {
outer_landed++;
} else {
_pt->setJmpCtx(&outer_ctx);

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:150

  • The test still uses jmp_buf + setjmp, but the crash-protection path has moved to sigsetjmp/siglongjmp. Mixing setjmp with siglongjmp is undefined/implementation-specific. Update the buffer type and call to sigjmp_buf/sigsetjmp, and rename the test to avoid implying the non-signal variant is used.
// (c2) walkVM path: a raw dereference of an injected poison pointer must be
// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp.
TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) {
ProfiledThread* t = ProfiledThread::current();
ASSERT_NE(t, nullptr);

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:286

  • Comment still says checkFault() "longjmps" even though the code path is now siglongjmp. Update wording to match the behavior being tested.
// End-to-end with real sigsetjmp/siglongjmp: a fault inside the inner frame must
// land in the inner frame's own recovery branch — checkFault() always
// longjmps through whatever is currently installed — and once the inner
// frame has recovered and restored the outer's context, the outer frame must

Comment threadddprof-lib/src/test/cpp/faultInjection_ut.cpp Outdated
CopilotAI review requested due to automatic review settings July 24, 2026 22:08

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 11 out of 11 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (5)

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:17

  • The documentation here still references jmp_buf/longjmp, but ProfiledThread::setJmpCtx/getJmpCtx now use sigjmp_buf and recovery uses siglongjmp. This is misleading and also risks type mismatches on platforms where jmp_buf and sigjmp_buf differ.

This issue also appears in the following locations of the same file:

  • line 14
  • line 283
  • line 309
 * Crash recovery inside walkVM relies on sigsetjmp/siglongjmp:
* 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx),
* chaining it with whatever context was already installed so a
* signal-based sampler interrupting a non-signal-based sampler's own
* in-flight walkVM() call doesn't clobber the outer call's context.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:302

  • jmp_buf/jmp_buf* are still used here with sigsetjmp/siglongjmp. Since ProfiledThread::setJmpCtx/getJmpCtx now use sigjmp_buf, this can break compilation/portability on platforms where jmp_buf and sigjmp_buf are distinct types.
// End-to-end with real sigsetjmp/siglongjmp: a fault inside the inner frame must
// land in the inner frame's own recovery branch — checkFault() always
// longjmps through whatever is currently installed — and once the inner
// frame has recovered and restored the outer's context, the outer frame must
// be left exactly as it was, never having been unwound itself.
TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) {
jmp_buf outer_ctx;
jmp_buf* outer_prev = _pt->getJmpCtx();
int outer_landed = 0;
int inner_landed = 0;
if (sigsetjmp(outer_ctx, 1) != 0) {
outer_landed++;
} else {
_pt->setJmpCtx(&outer_ctx);
// --- inner "walkVM" call, interrupted mid-flight by a fault ---
jmp_buf inner_ctx;
jmp_buf* inner_prev = _pt->getJmpCtx();
ASSERT_EQ(&outer_ctx, inner_prev);

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:312

  • This block still refers to longjmp in comments/error text, but the code path uses siglongjmp. Keeping wording consistent makes failures easier to interpret.
 // Simulate checkFault(): longjmp through whatever is currently
// installed — this must hit the inner frame, not the outer.
siglongjmp(*_pt->getJmpCtx(), 1);
FAIL() << "unreachable: longjmp does not return";

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:162

  • Later in this test (outside the diff hunk) the comments and assertion message still say setjmp/longjmp. Since the code path now uses sigsetjmp/siglongjmp, update that wording to avoid confusion when diagnosing failures.
 sigjmp_buf ctx;
if (sigsetjmp(ctx) != 0) {
recovered = true; // returned here via checkFault -> siglongjmp
faults++;
}

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:16

  • ProfiledThread::setJmpCtx/getJmpCtx now take/return sigjmp_buf*, but several tests earlier in this file still declare jmp_buf and pass jmp_buf* to these APIs (e.g., SetAndGetRoundTrip / SingleFrameRestoresPreviousOnExit / NestedFrames…). That can fail to compile on platforms where jmp_buf and sigjmp_buf are distinct types; consider switching those remaining jmp_buf declarations/comments to sigjmp_buf for consistency/portability.
 * 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx),
* chaining it with whatever context was already installed so a
* signal-based sampler interrupting a non-signal-based sampler's own

Comment threadddprof-lib/src/main/cpp/threadLocalData.h
Comment threadddprof-lib/src/test/cpp/faultInjection_ut.cpp
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 24, 2026 22:15

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 11 out of 11 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (3)

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:162

  • sigsetjmp requires the savesigs argument (typically 1). As written, this call will not compile (or will call the wrong overload/macro) on most platforms.
 sigjmp_buf ctx;
if (sigsetjmp(ctx, 1) != 0) {
recovered = true; // returned here via checkFault -> siglongjmp
faults++;
}

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:149

  • The test name and comment still refer to setjmp, but the implementation now uses sigsetjmp/siglongjmp. Renaming keeps intent and failure output accurate.
// (c2) walkVM path: a raw dereference of an injected poison pointer must be
// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp.
TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) {
ProfiledThread* t = ProfiledThread::current();

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:302

  • Same issue for the inner frame: use sigjmp_buf consistently so sigsetjmp/siglongjmp and setJmpCtx/getJmpCtx all agree on the buffer type.
 jmp_buf inner_ctx;
jmp_buf* inner_prev = _pt->getJmpCtx();
ASSERT_EQ(&outer_ctx, inner_prev);

Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp
CopilotAI review requested due to automatic review settings July 24, 2026 22:19

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 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (5)

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:20

  • The header comment was updated to mention sigsetjmp/siglongjmp, but it still refers to storing a jmp_buf* and calling longjmp(). This is now inconsistent with the implementation (ProfiledThread uses sigjmp_buf* and checkFault uses siglongjmp), which makes the documentation misleading.
 * Crash recovery inside walkVM relies on sigsetjmp/siglongjmp:
* 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx),
* chaining it with whatever context was already installed so a
* signal-based sampler interrupting a non-signal-based sampler's own
* in-flight walkVM() call doesn't clobber the outer call's context.
* 2. If a fault fires during the walk, checkFault() detects the live
* context via ProfiledThread::isProtected() and calls longjmp() to
* unwind through whatever context is currently installed.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:312

  • This comment and failure message still refer to longjmp, but the code now uses siglongjmp(). Updating them keeps the test intent clear and consistent with the new crash-protection mechanism.
 // Simulate checkFault(): longjmp through whatever is currently
// installed — this must hit the inner frame, not the outer.
siglongjmp(*_pt->getJmpCtx(), 1);
FAIL() << "unreachable: longjmp does not return";

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:147

  • The comment says control returns to setjmp, but this test now uses sigsetjmp/siglongjmp. Update wording to match the actual mechanism being tested.
// (c2) walkVM path: a raw dereference of an injected poison pointer must be
// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:294

  • outer_ctx/outer_prev are declared as jmp_buf/jmp_buf*, but this path now uses sigsetjmp(…, 1) and ProfiledThread::setJmpCtx/getJmpCtx are sigjmp_buf*-based. On libcs where jmp_buf and sigjmp_buf differ, passing the wrong buffer type can be incorrect (and could even corrupt the stack if sigsetjmp writes a larger env). Prefer using sigjmp_buf consistently here.
 if (sigsetjmp(outer_ctx, 1) != 0) {

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:304

  • inner_ctx/inner_prev are declared as jmp_buf/jmp_buf* but are used with sigsetjmp(…, 1) and stored via ProfiledThread::setJmpCtx(sigjmp_buf*). Use sigjmp_buf consistently to match the sigsetjmp/siglongjmp mechanism and avoid libc-dependent type mismatches.
 if (sigsetjmp(inner_ctx, 1) != 0) {

Comment threadddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp Outdated
@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 75d514d)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/126927848 Commit: 75d514df63aa3dab872ca4056bf4a6f41762f1dc

✅ 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✅ 10354 ms (21 iters)✅ 10274 ms (21 iters)≈ -0.8% (±11.6%)— / —
akka-uct25✅ 8864 ms (24 iters)✅ 8897 ms (24 iters)≈ +0.4% (±10.2%)— / —
finagle-chirper21✅ 6012 ms (33 iters)✅ 6012 ms (33 iters)≈ 0% (±25.4%)⚠️ W:4 / ⚠️ W:3
finagle-chirper25✅ 5458 ms (36 iters)✅ 5501 ms (36 iters)≈ +0.8% (±24.7%)⚠️ W:3 / ⚠️ W:3
fj-kmeans21✅ 2794 ms (67 iters)✅ 2801 ms (67 iters)≈ +0.3% (±2.7%)— / —
fj-kmeans25✅ 2821 ms (66 iters)✅ 2757 ms (67 iters)≈ -2.3% (±2.6%)— / —
future-genetic21✅ 2065 ms (90 iters)✅ 2106 ms (88 iters)≈ +2% (±2.9%)— / —
future-genetic25✅ 2012 ms (93 iters)✅ 2048 ms (91 iters)≈ +1.8% (±2.7%)— / —
naive-bayes21✅ 1266 ms (135 iters)✅ 1264 ms (135 iters)≈ -0.2% (±32.9%)— / —
naive-bayes25✅ 1026 ms (166 iters)✅ 999 ms (171 iters)≈ -2.6% (±31.4%)— / —
reactors21✅ 16138 ms (15 iters)✅ 15993 ms (15 iters)≈ -0.9% (±9.4%)— / —
reactors25✅ 18295 ms (15 iters)✅ 18564 ms (15 iters)≈ +1.5% (±5.5%)— / —
Internal counter details (ddprof)

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

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅2 / 31933 / 1972✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅✅ / 32315 / 2396✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅3 / 18943 / 8589✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅1 / 28109 / 8671✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅3 / 11274 / 1252✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅4 / 22996 / 2999✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅✅ / 12974 / 2898✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅4 / 43505 / 3470✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅3 / ✅3479 / 3471✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅1 / ✅1683 / 1785✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅2 / ✅1874 / 1943✅ / ✅✅ / ✅

CopilotAI review requested due to automatic review settings July 25, 2026 00:56

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 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (6)

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:17

  • The file header comment was partially updated to sigsetjmp/siglongjmp but still refers to jmp_buf* and longjmp(). This is now inconsistent with the code/API (sigjmp_buf* + siglongjmp()) and may confuse future maintenance.
 * Crash recovery inside walkVM relies on sigsetjmp/siglongjmp:
* 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx),
* chaining it with whatever context was already installed so a
* signal-based sampler interrupting a non-signal-based sampler's own
* in-flight walkVM() call doesn't clobber the outer call's context.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:287

  • This comment still says checkFault() "longjmps" even though the recovery mechanism is now siglongjmp(). Update the wording to match the implementation.
// End-to-end with real sigsetjmp/siglongjmp: a fault inside the inner frame must
// land in the inner frame's own recovery branch — checkFault() always
// longjmps through whatever is currently installed — and once the inner
// frame has recovered and restored the outer's context, the outer frame must
// be left exactly as it was, never having been unwound itself.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:310

  • The inline comment describes the simulated jump as longjmp, but the code now calls siglongjmp(). Keeping the comment aligned helps avoid confusion when debugging these tests.
 // Simulate checkFault(): longjmp through whatever is currently
// installed — this must hit the inner frame, not the outer.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:292

  • ProfiledThread::getJmpCtx() now returns sigjmp_buf*, but this test file still contains earlier tests using jmp_buf/jmp_buf* with setJmpCtx/getJmpCtx (e.g., around the SetAndGetRoundTrip and NestedFramesChainAndUnwindInOrder tests). Those will no longer compile cleanly and should be updated to sigjmp_buf/sigjmp_buf* consistently throughout the file.
 sigjmp_buf outer_ctx;
sigjmp_buf* outer_prev = _pt->getJmpCtx();
int outer_landed = 0;
int inner_landed = 0;

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:147

  • This comment still says control returns to setjmp, but the test now uses sigsetjmp/siglongjmp. Update the wording for accuracy.
// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp.

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:163

  • The test body now uses sigsetjmp/siglongjmp, but the later comment and assertion message still say setjmp/longjmp. Since this shows up directly in test output, it should be kept consistent with the updated recovery mechanism.
 sigjmp_buf ctx;
if (sigsetjmp(ctx, 1) != 0) {
recovered = true; // returned here via checkFault -> siglongjmp
faults++;
}
t->setJmpCtx(&ctx);

Comment threadddprof-lib/src/main/cpp/threadLocalData.h
@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit d2773a4)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/126937456 Commit: d2773a4fa0dd5c43ebd9975a2409a9c63d8bb57e

⚠️ Significant outliers

  • 🟢 future-genetic (JDK 21): runtime -2.6% (2133→2077 ms)
  • 🟢 future-genetic (JDK 25): runtime -5.2% (2080→1972 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10522 ms (21 iters)✅ 10247 ms (21 iters)≈ -2.6% (±11.3%)— / —
akka-uct25✅ 9035 ms (24 iters)✅ 8862 ms (24 iters)≈ -1.9% (±10.7%)— / —
finagle-chirper21✅ 5998 ms (33 iters)✅ 5954 ms (33 iters)≈ -0.7% (±24.5%)⚠️ W:3 / ⚠️ W:3
finagle-chirper25✅ 5490 ms (36 iters)✅ 5511 ms (36 iters)≈ +0.4% (±24.7%)⚠️ W:3 / ⚠️ W:3
fj-kmeans21✅ 2787 ms (67 iters)✅ 2853 ms (66 iters)≈ +2.4% (±2.6%)— / —
fj-kmeans25✅ 2813 ms (66 iters)✅ 2807 ms (66 iters)≈ -0.2% (±2.6%)— / —
future-genetic21✅ 2133 ms (87 iters)✅ 2077 ms (89 iters)🟢 -2.6%— / —
future-genetic25✅ 2080 ms (90 iters)✅ 1972 ms (95 iters)🟢 -5.2%— / —
naive-bayes21✅ 1245 ms (137 iters)✅ 1275 ms (134 iters)≈ +2.4% (±33.2%)— / —
naive-bayes25✅ 979 ms (174 iters)✅ 1013 ms (169 iters)≈ +3.5% (±32.2%)— / —
reactors21✅ 16261 ms (15 iters)✅ 16277 ms (15 iters)≈ +0.1% (±8.2%)— / —
reactors25✅ 18391 ms (15 iters)✅ 18398 ms (15 iters)≈ +0% (±3%)— / —
Internal counter details (ddprof)

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

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅✅ / 21977 / 2008✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅✅ / 12312 / 2189✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅3 / 28725 / 8783✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅2 / 28724 / 8493✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅✅ / 21277 / 1278✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅1 / 11274 / 1274✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅2 / 22916 / 2861✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅3 / 12887 / 2863✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅1 / 13465 / 3536✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅5 / 13492 / 3486✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅2 / 21826 / 1586✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅✅ / 21859 / 1858✅ / ✅✅ / ✅

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 25, 2026 21:08

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 11 out of 11 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

ddprof-lib/src/main/cpp/threadLocalData.h:251

  • _jmp_buf is a std::atomic, but these helpers rely on implicit atomic conversions/assignments. Using explicit load/store with acquire/release makes the publish/observe intent clearer (and avoids depending on implicit operator overloads), which is particularly important since this is read from an async signal handler path.
 inline void setJmpCtx(sigjmp_buf* buf) {
_jmp_buf = buf;
}
inline sigjmp_buf* getJmpCtx() const {
return _jmp_buf;
}
inline bool isProtected() const {
return _jmp_buf != nullptr;
}

ddprof-lib/src/test/cpp/faultInjection_ut.cpp:148

  • This comment still says control returns to setjmp, but the test now uses sigsetjmp/siglongjmp. Updating the wording avoids confusion when grepping for the intended mechanism.
// (c2) walkVM path: a raw dereference of an injected poison pointer must be
// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp.
TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) {

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:287

  • These comments still describe the recovery as longjmp even though the test has been switched to sigsetjmp/siglongjmp. Keeping terminology consistent will make the chaining behavior easier to follow.
// End-to-end with real sigsetjmp/siglongjmp: a fault inside the inner frame must
// land in the inner frame's own recovery branch — checkFault() always
// longjmps through whatever is currently installed — and once the inner
// frame has recovered and restored the outer's context, the outer frame must
// be left exactly as it was, never having been unwound itself.

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp:312

  • The inline comment says "Simulate checkFault(): longjmp" but the code calls siglongjmp now.
 _pt->setJmpCtx(&inner_ctx);
// Simulate checkFault(): longjmp through whatever is currently
// installed — this must hit the inner frame, not the outer.
siglongjmp(*_pt->getJmpCtx(), 1);
FAIL() << "unreachable: siglongjmp does not return";

@zhengyu123
zhengyu123 marked this pull request as ready for review July 25, 2026 21:15
@zhengyu123
zhengyu123 requested a review from a team as a code ownerJuly 25, 2026 21:15
@dd-octo-sts

Copy link
Copy Markdown
Contributor

Benchmark Results (commit adad103)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/126983712 Commit: adad103c67ddfce3591070dd4855c0fb88df75fb

⚠️ Significant outliers

  • 🔴 fj-kmeans (JDK 21): runtime +3.9% (2721→2826 ms)
  • 🔴 future-genetic (JDK 21): runtime +2.6% (2055→2109 ms)
  • 🔴 future-genetic (JDK 25): runtime +4.4% (2021→2110 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10337 ms (21 iters)✅ 10307 ms (21 iters)≈ -0.3% (±11.6%)— / —
akka-uct25✅ 8961 ms (24 iters)✅ 8800 ms (24 iters)≈ -1.8% (±10.8%)— / —
finagle-chirper21✅ 6006 ms (33 iters)✅ 5958 ms (33 iters)≈ -0.8% (±26.1%)⚠️ W:3 / ⚠️ W:3
finagle-chirper25✅ 5464 ms (36 iters)✅ 5489 ms (36 iters)≈ +0.5% (±24.6%)⚠️ W:3 / ⚠️ W:4
fj-kmeans21✅ 2721 ms (68 iters)✅ 2826 ms (66 iters)🔴 +3.9%— / —
fj-kmeans25✅ 2812 ms (66 iters)✅ 2795 ms (67 iters)≈ -0.6% (±2.5%)— / —
future-genetic21✅ 2055 ms (90 iters)✅ 2109 ms (88 iters)🔴 +2.6%— / —
future-genetic25✅ 2021 ms (92 iters)✅ 2110 ms (88 iters)🔴 +4.4%— / —
naive-bayes21✅ 1251 ms (137 iters)✅ 1313 ms (131 iters)≈ +5% (±33.5%)— / —
naive-bayes25✅ 1032 ms (166 iters)✅ 1013 ms (168 iters)≈ -1.8% (±31.2%)— / —
reactors21✅ 16767 ms (15 iters)✅ 15964 ms (15 iters)≈ -4.8% (±7%)— / —
reactors25✅ 18801 ms (15 iters)✅ 18428 ms (15 iters)≈ -2% (±5%)— / —
Internal counter details (ddprof)

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

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅✅ / 51966 / 2025✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅1 / 32400 / 2261✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅7 / 38658 / 8467✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅✅ / 18288 / 8713✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅1 / 41278 / 1251✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅1 / 31295 / 1247✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅1 / 13067 / 2997✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅✅ / 22913 / 2848✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅5 / 13505 / 3571✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅3 / 93509 / 3461✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅1 / 11932 / 1816✅ / ✅✅ / ✅

@kaahoskaahos 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.

Thanks for the work! Overall this looks good to me. I've made some suggestions to make some tests and comments more consistent with this switch.

Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
Comment threadddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp Outdated
CopilotAI review requested due to automatic review settings July 27, 2026 09:54

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 27, 2026 10:02

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 b4a0284)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/127106698 Commit: b4a0284218a2d682fa7deb11d9dc35d3accc56a3

⚠️ Significant outliers

  • 🔴 future-genetic (JDK 21): runtime +3.6% (2033→2106 ms)
  • 🟢 future-genetic (JDK 25): runtime -3.4% (2075→2005 ms)
Runtime details (per benchmark × JDK)
BenchmarkJDKLatestDevΔ (dev vs latest)Issues L/D
akka-uct21✅ 10199 ms (21 iters)✅ 10390 ms (21 iters)≈ +1.9% (±11.4%)— / —
akka-uct25✅ 8878 ms (24 iters)✅ 8830 ms (24 iters)≈ -0.5% (±10.3%)— / —
finagle-chirper21✅ 5992 ms (33 iters)✅ 5986 ms (33 iters)≈ -0.1% (±25.2%)⚠️ W:3 / ⚠️ W:3
finagle-chirper25✅ 5505 ms (36 iters)✅ 5456 ms (36 iters)≈ -0.9% (±25%)⚠️ W:3 / ⚠️ W:3
fj-kmeans21✅ 2704 ms (70 iters)✅ 2749 ms (68 iters)≈ +1.7% (±2.7%)— / —
fj-kmeans25✅ 2819 ms (66 iters)✅ 2811 ms (66 iters)≈ -0.3% (±2.6%)— / —
future-genetic21✅ 2033 ms (92 iters)✅ 2106 ms (88 iters)🔴 +3.6%— / —
future-genetic25✅ 2075 ms (89 iters)✅ 2005 ms (93 iters)🟢 -3.4%— / —
naive-bayes21✅ 1340 ms (128 iters)✅ 1273 ms (134 iters)≈ -5% (±32.2%)— / —
naive-bayes25✅ 1007 ms (169 iters)✅ 983 ms (173 iters)≈ -2.4% (±31.3%)— / —
reactors21✅ 16201 ms (15 iters)✅ 16155 ms (15 iters)≈ -0.3% (±7.9%)— / —
reactors25✅ 18074 ms (15 iters)✅ 18767 ms (15 iters)≈ +3.8% (±5.2%)— / —
Internal counter details (ddprof)

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

BenchmarkJDKDropped recDropped jvmtiDropped traceSkipped WCAGCT failUnwind fail
akka-uct21✅ / ✅✅ / ✅1 / 21910 / 2009✅ / ✅✅ / ✅
akka-uct25✅ / ✅✅ / ✅1 / 32492 / 2064✅ / ✅✅ / ✅
finagle-chirper21✅ / ✅✅ / ✅6 / 58735 / 8297✅ / ✅✅ / ✅
finagle-chirper25✅ / ✅✅ / ✅1 / ✅8462 / 8466✅ / ✅✅ / ✅
fj-kmeans21✅ / ✅✅ / ✅3 / ✅1286 / 1280✅ / ✅✅ / ✅
fj-kmeans25✅ / ✅✅ / ✅2 / 31262 / 1259✅ / ✅✅ / ✅
future-genetic21✅ / ✅✅ / ✅2 / 13016 / 2973✅ / ✅✅ / ✅
future-genetic25✅ / ✅✅ / ✅2 / 62886 / 2947✅ / ✅✅ / ✅
naive-bayes21✅ / ✅✅ / ✅4 / 43496 / 3485✅ / ✅✅ / ✅
naive-bayes25✅ / ✅✅ / ✅4 / 13482 / 3478✅ / ✅✅ / ✅
reactors21✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅✅ / ✅
reactors25✅ / ✅✅ / ✅1 / 11817 / 1887✅ / ✅✅ / ✅

@rkennke

Copy link
Copy Markdown
Contributor

A couple of things worth addressing before merging:

Performance

sigsetjmp(ctx, 1) in ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp (the sigsetjmp(crash_protection_ctx, 1) call in walkVM) saves the signal mask on every call, which typically costs a rt_sigprocmask syscall — unlike plain setjmp(), which is pure userspace register save. walkVM is on the hot path of a sampling profiler and can run very frequently, so this adds a syscall-class cost to every stack walk, not just the fault-recovery path.

Could you run a before/after comparison on a stack-walk-heavy benchmark to quantify the overhead, and add a note in the PR description on the correctness-vs-perf tradeoff (and why it's acceptable)?

Test coverage

None of the updated tests (ddprof-lib/src/test/cpp/faultInjection_ut.cpp, ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp) assert on the actual defect this PR fixes — that the signal mask is restored after a siglongjmp out of a handler. They cover functional recovery (control returns, depth/nesting counters), but not mask restoration. Could you add a regression test that blocks a signal, triggers a fault that siglongjmps out of a handler, and asserts the mask afterward matches what it was before signal delivery?

Nit

The "How to test the change?" section in the PR description is empty — worth filling in given this is a signal-safety fix that regular CI likely won't exercise end-to-end.


🤖 Generated with Claude Code

@kaahos
kaahos merged commit 587a45c into mainJul 27, 2026
180 of 182 checks passed
@kaahos
kaahos deleted the zgu/setjmp branch July 27, 2026 11:41
@github-actionsgithub-actionsBot added this to the 1.48.0 milestone Jul 27, 2026
@kaahos
kaahos restored the zgu/setjmp branch July 27, 2026 11:43
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@zhengyu123@rkennke@jbachorik@kaahos