Skip to content

fix(profiling): prevent lock profiler flush reentrancy - #19856

Draft
KowalskiThomas wants to merge 1 commit into
mainfrom
kowalski/fix-profiling-prevent-lock-profiler-flush-reentrancy
Draft

fix(profiling): prevent lock profiler flush reentrancy#19856
KowalskiThomas wants to merge 1 commit into
mainfrom
kowalski/fix-profiling-prevent-lock-profiler-flush-reentrancy

Conversation

@KowalskiThomas

Copy link
Copy Markdown
Collaborator

Description

This PR adds a guard in the Lock Profiler to prevent reentrancy in _flush_sample, which is causing crashes.

Some C extensions (e.g. gRPC) acquire a Lock from a __del__ that fires inside a subtype_dealloc chain which itself is triggered by a Python frame teardown (e.g. _PyFrame_ClearExceptCode on an asyncio task step). In that context, walking the caller frame with sys._getframe or reading task._coro.cr_frame can yield a PyFrameObject that has just been detached from the interpreter's frame chain, and dereferencing it (e.g. in the isinstance check inside SampleHandle.push_pyframes) causes a segmentation fault.
Skipping the sample when we detect we are already sampling on this thread avoids those crashes.

Error UnixSignal: Process terminated with SI_TKILL (SIGSEGV)
#0   0x0000ffff9453dd74 __pthread_kill 
#1   0x0000ffff94660838 __kernel_rt_sigreturn 
#2   0x0000aaaae836a904 object_recursive_isinstance.llvm.5564875042418019868 
#3   0x0000000000000000 __pyx_pf_7ddtrace_8internal_7datadog_9profiling_4ddup_5_ddup_12SampleHandle_26push_pyframes (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/build/cmake.linux-aarch64-cpython-312/ddtrace.internal.datadog.profiling.ddup._ddup/_ddup.cpp:9982)
#4   0x0000ffff938e3384 __pyx_pf_7ddtrace_8internal_7datadog_9profiling_4ddup_5_ddup_12SampleHandle_26push_pyframes (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/build/cmake.linux-aarch64-cpython-312/ddtrace.internal.datadog.profiling.ddup._ddup/_ddup.cpp:9982)
#5   0x0000ffff938e3384 __pyx_pw_7ddtrace_8internal_7datadog_9profiling_4ddup_5_ddup_12SampleHandle_27push_pyframes(_object*, _object* const*, long, _object*) (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/build/cmake.linux-aarch64-cpython-312/ddtrace.internal.datadog.profiling.ddup._ddup/_ddup.cpp:9928)
#6   0x0000aaaae836d438 _PyObject_VectorcallTstate.llvm.5539597928013386035 
#7   0x0000aaaae8466ed8 PyObject_VectorcallMethod 
#8   0x0000000000000000 __pyx_pf_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_34_flush_sample (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:8943)
#9   0x0000ffff905287e0 __pyx_pf_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_34_flush_sample (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:8943)
#10  0x0000ffff905287e0 __pyx_pw_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_35_flush_sample (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:8374)
#11  0x0000aaaae83703e0 method_vectorcall.llvm.8884419402852520965 
#12  0x0000aaaae836d438 _PyObject_VectorcallTstate.llvm.5539597928013386035 
#13  0x0000aaaae8466ed8 PyObject_VectorcallMethod 
#14  0x0000000000000000 __pyx_pf_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_24_acquire (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:7079)
#15  0x0000ffff9051f064 __pyx_pf_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_24_acquire (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:7079)
#16  0x0000ffff9051f064 __pyx_pw_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_25_acquire (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:6557)
#17  0x0000aaaae8370380 method_vectorcall.llvm.8884419402852520965 
#18  0x0000000000000000 __Pyx_PyObject_Call (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:19737)
#19  0x0000000000000000 __pyx_pf_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_20__enter__ (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:6244)
#20  0x0000ffff9051cfdc __Pyx_PyObject_Call (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:19737)
#21  0x0000ffff9051cfdc __pyx_pf_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_20__enter__ (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:6244)
#22  0x0000ffff9051cfdc __pyx_pw_7ddtrace_9profiling_9collector_5_lock_13_ProfiledLock_21__enter__ (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_lock.c:6195)
#23  0x0000ffff7136cba4  
#24  0x0000ffff7136dc58  
#25  0x0000aaaae83afd10 subtype_dealloc 
#26  0x0000aaaae8375e98 frame_dealloc 
#27  0x0000aaaae842298c tb_dealloc 
#28  0x0000aaaae842297c tb_dealloc 
#29  0x0000aaaae842297c tb_dealloc 
#30  0x0000aaaae842297c tb_dealloc 
#31  0x0000aaaae842297c tb_dealloc 
#32  0x0000aaaae842297c tb_dealloc 
#33  0x0000aaaae82a2788 FutureObj_clear 
#34  0x0000aaaae82a3e68 TaskObj_clear 
#35  0x0000aaaae82a3c6c TaskObj_dealloc 
#36  0x0000aaaae837927c list_dealloc 
#37  0x0000aaaae8412f18 _PyFrame_ClearExceptCode 
#38  0x0000aaaae83dbd74 _PyEval_EvalFrameDefault 
#39  0x0000aaaae8473468 gen_send_ex2 
#40  0x0000aaaae82a0c38 task_step_impl 
#41  0x0000aaaae82a0a20 task_step 
#42  0x0000aaaae82a1cec task_wakeup 
#43  0x0000aaaae8396804 cfunction_vectorcall_O.llvm.6434661070585453414 
#44  0x0000aaaae827ec1c _PyObject_VectorcallTstate 
#45  0x0000aaaae827eaf8 context_run 
#46  0x0000aaaae83965cc cfunction_vectorcall_FASTCALL_KEYWORDS.llvm.6434661070585453414 
#47  0x0000aaaae8402a34 _PyEval_EvalFrameDefault 
#48  0x0000aaaae8370380 method_vectorcall.llvm.8884419402852520965 
#49  0x0000aaaae8402a34 _PyEval_EvalFrameDefault 
#50  0x0000aaaae8370420 method_vectorcall.llvm.8884419402852520965 
#51  0x0000aaaae8403e08 _PyEval_EvalFrameDefault 
#52  0x0000aaaae836f318 _PyObject_Call_Prepend 
#53  0x0000aaaae84a2718 slot_tp_call 
#54  0x0000aaaae83ff1f4 _PyEval_EvalFrameDefault 
#55  0x0000aaaae84fc8f8 PyEval_EvalCode 
#56  0x0000aaaae84fa42c builtin_exec 
#57  0x0000aaaae83965cc cfunction_vectorcall_FASTCALL_KEYWORDS.llvm.6434661070585453414 
#58  0x0000aaaae83feea4 _PyEval_EvalFrameDefault 
#59  0x0000aaaae8547e6c pymain_run_module 
#60  0x0000aaaae854765c Py_RunMain 
#61  0x0000aaaae8547c7c pymain_main.llvm.11498081596425848568 
#62  0x0000aaaae84408d0 main 
#63  0x0000ffff942efdd8 __libc_start_main 

@KowalskiThomas KowalskiThomas added Profiling Continous Profling identified-by:crashtracking Identified by Crash Tracking labels Aug 25, 2026
@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

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

🚦 5 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | K8S_LIB_INJECTION_NO_AC: [dd-lib-python-init-test-django-gunicorn, ${PRIVATE_DOCKER_REGISTRY}/system-tests/dd-lib-python-init-test-django-gunicorn, 235494822917.dkr.ecr.us-east-1.amazonaws.com/ssi/dd-lib-python-init:glci133080880] — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741238-d2b8243-manylinux2014_x86_64, 1] — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [arm64, cp315-cp315, v113741357-d2b8243-manylinux2014_aarch64, 1] — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

View all 5 failed jobs.

ℹ️ 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: 20a63dd | Docs | View more details | Give us feedback!

@KowalskiThomas
KowalskiThomas force-pushed the kowalski/fix-profiling-prevent-lock-profiler-flush-reentrancy branch 3 times, most recently from a806f47 to a1dfe85 Compare August 25, 2026 14:33
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against main using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

ddtrace/profiling/collector/_lock.pyi                                   @DataDog/profiling-python
ddtrace/profiling/collector/_lock.pyx                                   @DataDog/profiling-python
releasenotes/notes/profiling-lock-profiler-reentrancy-crash-ec548ff1a0d474a9.yaml  @DataDog/apm-python
tests/profiling/collector/test_threading.py                             @DataDog/profiling-python

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 25, 2026

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

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

Show existing violations (showing 5 of 250 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=135)
ddtrace.llmobs._integrations.bedrock_agents -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)
ddtrace.llmobs._integrations.vertexai -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)
ddtrace.llmobs._integrations.langgraph -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)
ddtrace.llmobs._utils -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)

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 Aug 25, 2026

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 3 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector
ddtrace.llmobs -> ddtrace.llmobs._evaluators -> ddtrace.llmobs._evaluators.format -> ddtrace.llmobs._experiment -> ddtrace.llmobs
ddtrace.appsec._asm_request_context -> ddtrace.appsec._iast._iast_request_context_base -> ddtrace.appsec._iast._iast_env -> ddtrace.appsec._iast.reporter -> ddtrace.appsec._exploit_prevention.stack_traces -> ddtrace.appsec._asm_request_context

@KowalskiThomas
KowalskiThomas force-pushed the kowalski/fix-profiling-prevent-lock-profiler-flush-reentrancy branch from a1dfe85 to 20a63dd Compare August 25, 2026 14:36
@KowalskiThomas

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: 20a63dd9f4

ℹ️ 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 on lines +278 to +279
if tid in _FLUSH_SAMPLE_ACTIVE_THREADS:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Detect unsafe frame teardown before the first flush

When a profiled lock is first acquired from a __del__ during task/frame teardown—the scenario shown in the commit description—this thread ID is not yet in the set, so the guard admits the sample and still reaches _c_get_task()/sys._getframe() and push_pyframes() with the detached frame that caused the segmentation fault. This only suppresses a flush nested inside another _flush_sample; the added test pre-populates the set and therefore does not exercise the reported entry path. Guard or reject the frame-teardown case itself so the original gRPC/asyncio crash is actually avoided.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.14 and it does not conflict with that branch.
The command used to test backporting was

git fetch origin 4.14 && git checkout origin/4.14 && git checkout -b backport-19856-to-4.14 && git cherry-pick -x --mainline 1 d035a46583ae62a396ac8dd2042da5910b557a29

@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.11 and it does not conflict with that branch.
The command used to test backporting was

git fetch origin 4.11 && git checkout origin/4.11 && git checkout -b backport-19856-to-4.11 && git cherry-pick -x --mainline 1 d035a46583ae62a396ac8dd2042da5910b557a29

@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.12 and it does not conflict with that branch.
The command used to test backporting was

git fetch origin 4.12 && git checkout origin/4.12 && git checkout -b backport-19856-to-4.12 && git cherry-pick -x --mainline 1 d035a46583ae62a396ac8dd2042da5910b557a29

@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.13 and it does not conflict with that branch.
The command used to test backporting was

git fetch origin 4.13 && git checkout origin/4.13 && git checkout -b backport-19856-to-4.13 && git cherry-pick -x --mainline 1 d035a46583ae62a396ac8dd2042da5910b557a29

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant