Skip to content

fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-15342) - #18797

Draft
vlad-scherbich wants to merge 2 commits into
mainfrom
vlad/prof-14568-shutdown-race
Draft

fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-15342)#18797
vlad-scherbich wants to merge 2 commits into
mainfrom
vlad/prof-14568-shutdown-race

Conversation

@vlad-scherbich

@vlad-scherbich vlad-scherbich commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

< Prev PR | Next PR

Summary

This PR closes the shutdown race (Layer 2).

Profiler._stop_service runs the final scheduler.flush() before it stops the
collectors. That first flush resolves the code-provenance file, which the very
first time triggers one-time importlib.metadata cold imports
(setuptools / _distutils_hack / packaging). Those imports run while the native
stack sampler is still walking live frames during interpreter teardown, so the
sampler can read a freed PyCodeObject* and fault inside safe_memcpy.

Changes

  • Pre-warm code provenance at scheduler start (scheduler.py) so the final
    flush performs no cold imports — done on a safe thread when nothing is being
    torn down.
  • Pause the native sampler before the final flush (profiler.py +
    collector/stack.py's pause_for_shutdown_flush). Collectors are not stopped
    yet, so memalloc's snapshot still works; the stack v2 sampler pushes directly
    to ddup and has no meaningful snapshot, so pausing it is safe.
  • Py_IsFinalizing guard in the native sampling loop (sampler.cpp) so the
    sampler bails the moment finalization begins (covers teardown paths that don't
    stop the thread first, e.g. uWSGI --skip-atexit, hard exits).

Stacked with #18798 (handler-ownership hardening), which depends on this branch.

Reproduction

Repro script included: scripts/profiling/repro_prof_14568_shutdown.py. It
repeatedly starts/stops the profiler while threads churn frames, clearing the
code-provenance cache each cycle so the final flush does the cold imports while
sampling.

# crashes (intermittently) on main, survives on this branch
DD_PROFILING_STACK_FAST_COPY=1 python scripts/profiling/repro_prof_14568_shutdown.py

Test plan

  • Rebuild the stack native extension.
  • pytest tests/profiling/test_main.py -k start_stop_under_load
  • Run the repro on main (intermittent crash) vs this branch (clean) on Linux.
  • Profiling riot venvs.

Close the shutdown race where the final scheduler flush triggered one-time
code-provenance imports while the native stack sampler was still walking live
frames during interpreter teardown, reading freed objects and crashing.

- Pre-warm code provenance at scheduler start so the final flush does no cold
  imports.
- Pause the native sampler before the final flush (StackCollector exposes
  pause_for_shutdown_flush; Profiler invokes it pre-flush).
- Add a Py_IsFinalizing guard to the native sampling loop so it bails the moment
  interpreter finalization begins.

Adds a start/stop-under-load regression test and a deterministic repro script.
@cit-pr-commenter-54b7da

Copy link
Copy Markdown

Codeowners resolved as

ddtrace/internal/datadog/profiling/stack/src/sampler.cpp                @DataDog/profiling-python
ddtrace/profiling/collector/stack.py                                    @DataDog/profiling-python
ddtrace/profiling/profiler.py                                           @DataDog/profiling-python
ddtrace/profiling/scheduler.py                                          @DataDog/profiling-python
releasenotes/notes/profiling-fix-stack-sampler-shutdown-segv-9f3c1a7e54b2d806.yaml  @DataDog/apm-python
scripts/profiling/repro_prof_14568_shutdown.py                          @DataDog/apm-core-python
tests/profiling/test_main.py                                            @DataDog/profiling-python

@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented Jun 29, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 36 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741238-d2b8243-manylinux2014_x86_64, 1]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741491-d2b8243-musllinux_1_2_x86_64, 1]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [arm64, cp315-cp315, v113741357-d2b8243-manylinux2014_aarch64, 1]   View in Datadog   GitLab

View all 36 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🔄 Datadog auto-retried 1 job - 1 passed on retry View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 323a9ec | Docs | Datadog PR Page | Give us feedback!

@vlad-scherbich vlad-scherbich changed the title profiling: fix stack-profiler SIGSEGV on shutdown (PROF-14568) [1/2] fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-14568) [1/2] Jun 29, 2026
@vlad-scherbich vlad-scherbich changed the title fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-14568) [1/2] fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-14568) Jul 1, 2026
@vlad-scherbich vlad-scherbich changed the title fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-14568) fix(profiling): stack-profiler SIGSEGV on shutdown Jul 1, 2026
@vlad-scherbich vlad-scherbich changed the title fix(profiling): stack-profiler SIGSEGV on shutdown fix(profiling): stack-profiler SIGSEGV on shutdown (PROF-15342) Jul 7, 2026
@vlad-scherbich vlad-scherbich added the Profiling Continous Profling label Jul 7, 2026
@vlad-scherbich
vlad-scherbich requested a review from Copilot July 9, 2026 21: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

Fixes a stack-profiler shutdown crash (SIGSEGV) by removing cold-import work from the final flush, quiescing the native sampler before the last upload, and adding a native-side guard to stop sampling as soon as interpreter finalization begins.

Changes:

  • Pre-warm code-provenance resolution during scheduler startup so shutdown flush avoids one-time importlib.metadata imports.
  • Pause the native stack sampler prior to the final flush; add Py_IsFinalizing/_Py_IsFinalizing guard in the sampling loop.
  • Add a regression subprocess test, a standalone repro script, and a release note.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ddtrace/profiling/scheduler.py Pre-warms code provenance at scheduler start to prevent cold imports during shutdown flush.
ddtrace/profiling/profiler.py Pauses collectors (when supported) before the final scheduler flush.
ddtrace/profiling/collector/stack.py Implements pause_for_shutdown_flush() for the stack collector via stack.pause_sampling().
ddtrace/internal/datadog/profiling/stack/src/sampler.cpp Stops sampling immediately when the interpreter is finalizing.
tests/profiling/test_main.py Adds a regression subprocess test for repeated start/stop under load.
scripts/profiling/repro_prof_14568_shutdown.py Adds a standalone reproduction script for the shutdown crash scenario.
releasenotes/notes/profiling-fix-stack-sampler-shutdown-segv-9f3c1a7e54b2d806.yaml Documents the crash fix in release notes.

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

Comment on lines +223 to +230
@pytest.mark.subprocess(
env=dict(
DD_PROFILING_STACK_V2_ENABLED="true",
DD_PROFILING_STACK_FAST_COPY="1",
DD_PROFILING_ENABLE_CODE_PROVENANCE="true",
),
out="OK\n",
)
Comment on lines +260 to +261
# Force the next flush to recompute provenance (re-trigger cold imports).
code_provenance._code_provenance_file_path = None
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Jul 23, 2026
…EGV handlers (PROF-15342) (#18798)

| [Next PR](#18947)

User-reported crash: https://datadoghq.atlassian.net/browse/AIPTS-1715

## Description

`safe_memcpy`'s fault recovery only works while the profiler owns the `SIGSEGV`/`SIGBUS`
handlers. Libraries like PyTorch/CUDA (and abseil via vLLM/gRPC) install their own
handler during startup; once a foreign handler owns those signals, a fault on a stale
read is no longer recovered and the process crashes ([PROF-14568](https://datadoghq.atlassian.net/browse/PROF-14568)).

This PR makes the default-on state safe and removes the need for the `_DD_PROFILING_STACK_FAST_COPY=0`
workaround. It handles the handlers we *cannot* wrap (torch/CUDA/abseil) via detect-and-fallback.

## Changes

* Sampler **starts on the safe syscall copy** (`process_vm_readv` / `mach_vm_read_overwrite`)
  for a short warmup, so a fault during crash-prone startup can't crash the process (these
  syscalls return an error instead of faulting).
* After warmup it **upgrades to `safe_memcpy` only if we still own both `SIGSEGV` and
  `SIGBUS`** (`segv_handler_installed()`).
* It **re-checks ownership every cycle** and permanently falls back to the syscall copy if
  a handler is taken over later (e.g. lazy CUDA init). If no safe fallback exists
  (`process_vm_readv` blocked), it **stops sampling** — we degrade to dropped samples,
  never a crash.
* Warmup is a fixed 15s internal constant (not a user knob). Policy is auto-fallback, not
  reinstall-and-chain, so the foreign handler stays authoritative; `init_segv_catcher`
  stays `call_once` to avoid reintroducing handler-chaining races.

## Test plan

* New unit tests
* Manual repro (synthetic + `--torch`) on #18911):
crashes on `main`, runs OK on this branch
* Tested on another internal service in staging: `ai_gateway` ([profile link](https://ddstaging.datadoghq.com/profiling/explorer?query=service%3Aai_gateway%20env%3Astaging%20version%3Afaulthandling-1ac266be&my_code=disabled&profile_type=heap-live-size&refresh_mode=paused&viz=flame_graph&from_ts=1784830809012&to_ts=1784834409012&live=false))

* Py CPU

<img width="1004" height="1024" alt="image" src="https://github.com/user-attachments/assets/5b4311bf-ee2b-4dd0-bb1f-b23f05ca567d" />

* eBPF CPU

<img width="999" height="1024" alt="image" src="https://github.com/user-attachments/assets/ada757ba-8e35-48c0-ba97-e6972f69549b" />

* Live Heap

<img width="1009" height="1024" alt="image" src="https://github.com/user-attachments/assets/c7161fad-e922-43d6-b7c8-5cfbc0706cd0" />



### Testing in User Environment:

* User deployed a binary with this and next PR (#18797) to their service; no crashes observed in 24 hours. (cc @askardog )

[PROF-14568]: https://datadoghq.atlassian.net/browse/PROF-14568?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Co-authored-by: KowalskiThomas <thomas.kowalski@datadoghq.com>
Co-authored-by: vlad.scherbich <vlad.scherbich@datadoghq.com>
@github-actions github-actions Bot added the stale label Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Profiling Continous Profling stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants