Skip to content

fix(profiling): drop leftover memcpy after no-syscall foreign takeover - #21233

Draft
vlad-scherbich wants to merge 2 commits into
vlad/fastcopy-3-sticky-foreign-takeoverfrom
vlad/fastcopy-4-no-syscall-leftover
Draft

vlad-scherbich wants to merge 2 commits into
vlad/fastcopy-3-sticky-foreign-takeoverfrom
vlad/fastcopy-4-no-syscall-leftover

Conversation

@vlad-scherbich

@vlad-scherbich vlad-scherbich commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

If this PR merges alone on top of PR3: when sticky foreign takeover leaves no process_vm_readv fallback, stop sampling and clear leftover safe_memcpy so a forked child cannot sample under a foreign handler. Adds the test-only _set_process_vm_readv_available override used by the regression.

Layer Meaning Which PR in the stack?
Compiled into artifact drop_fast_copy_after_foreign_takeover this PR
Armed at runtime start/sampling_thread refuse leftover memcpy this PR
Observable in product/Python metadata reseeds enabled=false after failed disable this PR

Testing

Risks

Additional Notes

Stack: 4/4 (base PR3). Tree matches preserved #19972 tip refs/preserved/pr-19972-tip-20261023.

Pre-review checklist

  • The changes have been tested in real services in staging
  • The PR does not result in new crashes

https://datadoghq.atlassian.net/browse/PROF-16020

@vlad-scherbich vlad-scherbich added the changelog/no-changelog A changelog entry is not required for this PR. label Oct 23, 2026
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 116 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 116 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=121)
ddtrace.aiguard._api_client -×-> ddtrace.trace  (product:aiguard -> product:tracing, score=119)
ddtrace.llmobs._integrations.llama_index -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=119)
ddtrace.llmobs._integrations.typesafe -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=119)
ddtrace.llmobs._integrations.base -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=119)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against vlad/fastcopy-3-sticky-foreign-takeover using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

ddtrace/internal/datadog/profiling/stack/__init__.pyi                   @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/_stack.pyi                     @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/echion/echion/vm.h             @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/src/echion/vm.cc               @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/src/sampler.cpp                @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/src/stack.cpp                  @DataDog/profiling-python
releasenotes/notes/profiling-fast-copy-leftover-memcpy-06cae0690e831700.yaml  @DataDog/apm-python
tests/profiling/collector/test_copy_memory_stats.py                     @DataDog/profiling-python

@vlad-scherbich
vlad-scherbich requested a balanced review from Copilot October 23, 2026 18:18
@vlad-scherbich vlad-scherbich added the Profiling Continous Profling label Oct 23, 2026
@vlad-scherbich vlad-scherbich removed the changelog/no-changelog A changelog entry is not required for this PR. label Oct 23, 2026

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.

🔵 Needs a closer look

Native signal-handler and fork safety need human review backed by compiled regression results, including deterministic coverage of the restart guards.

1 open issue
What changed in this PR

This PR completes the profiling safety stack by refusing sampling after foreign signal-handler takeover leaves no safe syscall fallback.

Changes:

  • Adds startup guards and clears fast-copy state when fallback fails.
  • Restores recovery state when a usable copy method becomes available.
  • Adds a Linux-only test override and regressions for takeover, fork, and re-enable behavior.
File Description
tests/​profiling/​collector/​test_copy_memory_stats.py Adds no-fallback takeover, fork, and recovery regressions.
ddtrace/​internal/​datadog/​profiling/​stack/​src/​stack.cpp Exposes the test-only syscall availability override.
ddtrace/​internal/​datadog/​profiling/​stack/​src/​sampler.cpp Guards startup after takeover and refreshes failure metadata.
ddtrace/​internal/​datadog/​profiling/​stack/​src/​echion/​vm.cc Updates active and failure flags when copy methods change.
ddtrace/​internal/​datadog/​profiling/​stack/​echion/​echion/​vm.h Documents the availability override in a comment.
ddtrace/​internal/​datadog/​profiling/​stack/​_stack.pyi Declares the test-only override.
ddtrace/​internal/​datadog/​profiling/​stack/​__init__.pyi Clarifies test-helper access in comments.

Review effort: Balanced

@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-3-sticky-foreign-takeover branch from 9186291 to 2f3e02f Compare October 23, 2026 18:45
@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-4-no-syscall-leftover branch from 0591002 to 476f9b5 Compare October 23, 2026 18:46
@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-3-sticky-foreign-takeover branch 2 times, most recently from dd8fe47 to 0a920dd Compare October 26, 2026 00:49
@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-4-no-syscall-leftover branch from 476f9b5 to 2ec0060 Compare October 26, 2026 00:49
@vlad-scherbich
vlad-scherbich requested review from a team and gyuheon0h and a balanced review from Copilot October 26, 2026 00:50
@vlad-scherbich
vlad-scherbich requested a balanced review from Copilot and removed request for Copilot October 26, 2026 00:51

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.

🔵 Needs a closer look

Native signal-handler ownership and fork-safety changes require human review backed by platform-specific runtime validation.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-4-no-syscall-leftover branch from 2ec0060 to d4975e8 Compare October 26, 2026 00:57
@vlad-scherbich
vlad-scherbich requested a balanced review from Copilot October 26, 2026 00:57
@vlad-scherbich
vlad-scherbich requested a balanced review from Copilot and removed request for Copilot October 26, 2026 00:59

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.

🔵 Needs a closer look

Native signal-handler ownership and fork-time recovery require human validation supported by regression results from built wheels.

No open issues 🎉

Review effort: Balanced

@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-3-sticky-foreign-takeover branch from bdca954 to d316274 Compare October 27, 2026 22:55
When sticky takeover leaves no process_vm_readv fallback, stop sampling and
clear leftover safe_memcpy so a forked child cannot sample under a foreign
handler. Adds the test-only process_vm_readv override used by the regression.
Add test-only hooks so start() refuses sticky takeover with leftover
safe_memcpy before failed_safe_copy is latched, and ship the PR4 reno.
@vlad-scherbich
vlad-scherbich force-pushed the vlad/fastcopy-4-no-syscall-leftover branch from d4975e8 to f01b8f0 Compare October 27, 2026 22:55
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Pipelines  Tests

✨ Unblock PR with BitsAI

❌ Errors

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

🚦 1 Pipeline job failed

DataDog/apm-reliability/dd-trace-py | core/crashtracker — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

Useful? React with 👍 / 👎

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

@pr-commenter

pr-commenter Bot commented Oct 27, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-10-27 23:30:42

Comparing candidate commit f01b8f0 in PR branch vlad/fastcopy-4-no-syscall-leftover with baseline commit d316274 in branch vlad/fastcopy-3-sticky-foreign-takeover.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 582 metrics, 10 unstable metrics, 1 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-719.560ns; +759.630ns] or [-6.724%; +7.098%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-40.486ns; +37.919ns] or [-5.980%; +5.601%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-2366.223ns; +1472.630ns] or [-11.748%; +7.312%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1894.204ns; +1716.246ns] or [-9.991%; +9.052%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-452.622ns; +306.930ns] or [-10.522%; +7.135%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-287.049ns; +169.983ns] or [-10.565%; +6.256%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-102.316ns; +87.821ns] or [-7.224%; +6.200%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-5207.369ns; +4459.178ns] or [-10.314%; +8.832%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-945.604ns; +1059.989ns] or [-8.877%; +9.951%]

scenario:packagesupdateimporteddependencies-import_many_stdlib_cached

  • unstable execution_time [-49.090µs; +61.016µs] or [-8.549%; +10.626%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

Known flaky benchmarks without significant changes:

  • scenario:packagespackageforrootmodulemapping-cache_off

// Returns true if the caller may continue sampling; false if there is no safe
// fallback left and the caller must stop the sampler.
static bool
drop_fast_copy_after_foreign_takeover()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
drop_fast_copy_after_foreign_takeover()
can_safely_continue_sampling()

Let's rename that for clarity?

}

static PyObject*
stack_set_process_vm_readv_available(PyObject* Py_UNUSED(self), PyObject* args)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need to expose that?

Comment on lines +1219 to +1233
// Test-only: set sticky foreign takeover without changing the active copy method
// or latching failed_safe_copy. Pins start()/sampling_thread() leftover-memcpy
// guards independently of the failed-disable path.
static PyObject*
stack_mark_fast_copy_foreign_takeover(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(args))
{
mark_fast_copy_foreign_takeover();
Py_RETURN_NONE;
}

// Test-only: set fast_copy_active without changing safe_copy. Reproduces the
// inconsistent active==false / leftover-memcpy state a concurrent fork can leave.
static PyObject*
stack_force_fast_copy_active(PyObject* Py_UNUSED(self), PyObject* args)
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If that is purely in order to get test coverage... I don't think we should. Having getters for internal native state isn't great but it's acceptable, having setters that emulate a different internal state just feels wrong...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

when sticky foreign takeover leaves no process_vm_readv fallback

I don't understand what that means so I'm having a hard time understanding what I'm reviewing to be honest. Why would a foreign handler takeover "leave" or "not leave" a fallback? Isn't the fallback a property of whether it's supported by the platform?

stop sampling and clear leftover safe_memcpy so a forked child cannot sample under a foreign handler

IIUC, previously not having a fallback would put us into a state where we stayed in "safe memcpy is active" (because we can't fall back to anything) and we would thus keep sampling with it despite not having the handler?

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

Labels

Profiling Continous Profling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants