Skip to content

fix(profiling): reduce lock contention in greenlet stack unwinding#16657

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
mainfrom
scp-1039-psql-connection-exhaustion
Mar 6, 2026
Merged

fix(profiling): reduce lock contention in greenlet stack unwinding#16657
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
mainfrom
scp-1039-psql-connection-exhaustion

Conversation

@taegyunkim

@taegyunkim taegyunkim commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Split unwind_greenlets() into two phases to reduce lock hold time on greenlet_info_map_lock
    • Phase 1 (under lock): snapshot greenlet IDs, names, frame pointers, and parent chains into lightweight GreenletSnapshot structs
    • Phase 2 (lock released): perform the expensive stack unwinding (process_vm_readv / copy_type) outside the lock
  • Add GreenletSnapshot struct in greenlets.h to hold the snapshotted state

Motivation

Before this fix, unwind_greenlets() held greenlet_info_map_lock for the entire duration of stack unwinding across all tracked greenlets. Since every greenlet switch calls update_greenlet_frame() under the same lock, applications with many tracked greenlets (e.g. gunicorn + gevent + psycopg2) experienced severe lock contention — greenlet switches stalled waiting for the sampler, leading to connection pool exhaustion and request timeouts.

Why asyncio doesn't have this problem

Asyncio task unwinding (unwind_tasks) doesn't suffer from this because it reads task state directly from CPython internals (linked lists in 3.14+, or WeakSet in older versions) via copy_type() — no profiler-owned lock is involved during discovery or unwinding. The only profiler lock (task_link_map_lock) is held briefly for link-map cleanup, then released before the expensive coroutine stack unwinding begins.

Greenlets lack CPython-native tracking, so the profiler must maintain its own greenlet_info_map with frame pointers updated on every switch via update_greenlet_frame(). This creates a lock shared between the sampler thread (reading) and the application thread (writing on every switch) — the exact contention pattern this fix addresses by making the read-side (Phase 1 snapshot) fast.

Regression test confirms the issue on main

The regression test (test_gevent_greenlet_switch_not_blocked_by_profiler) was cherry-picked to main in #16660 and fails there, confirming this branch fixes the contention.

JIRA

SCP-1039

Testing

  • Added test_gevent_greenlet_switch_not_blocked_by_profiler regression test that:
    • Measures greenlet-switch wall time with 0 vs 2000 idle tracked greenlets (each with 50-deep stacks) while the profiler samples at 5ms intervals
    • Asserts the ratio stays below 3x to catch lock-contention regressions
    • Uses gevent.joinall with a 30s timeout and try/finally cleanup to prevent CI hangs

Risks

  • Snapshotted frame pointers may become stale between Phase 1 and Phase 2, but unwind_frame() already handles invalid pointers gracefully via copy_type() which returns non-zero on failure
  • No change to public API

🤖 Generated with Claude Code

@taegyunkim taegyunkim added Profiling Continous Profling AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos labels Feb 25, 2026
@cit-pr-commenter-54b7da

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

Copy link
Copy Markdown

Codeowners resolved as

ddtrace/internal/datadog/profiling/stack/echion/echion/greenlets.h      @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/src/echion/greenlets.cc        @DataDog/profiling-python
ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc          @DataDog/profiling-python
releasenotes/notes/fix-profiler-greenlet-lock-contention-74ad94719d4343fc.yaml  @DataDog/apm-python
tests/profiling/collector/test_stack.py                                 @DataDog/profiling-python

@taegyunkim taegyunkim changed the title fix(profiling): reduce lock contention for greenlet unwinding fix(profiling): reduce lock contention in greenlet stack unwinding Mar 2, 2026
@taegyunkim
taegyunkim marked this pull request as ready for review March 2, 2026 21:52
@taegyunkim
taegyunkim requested review from a team as code owners March 2, 2026 21:52

@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: 5c5362ef90

ℹ️ 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 ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc

@emmettbutler emmettbutler left a comment

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.

Release note looks good

Comment thread releasenotes/notes/fix-profiler-greenlet-lock-contention-74ad94719d4343fc.yaml Outdated

@vlad-scherbich vlad-scherbich 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.

Image

@taegyunkim taegyunkim left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@KowalskiThomas Do you have any concern/comment for this PR?

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

LGTM, thanks for doing this.

Comment thread ddtrace/internal/datadog/profiling/stack/src/echion/greenlets.cc
Comment thread ddtrace/internal/datadog/profiling/stack/src/echion/greenlets.cc
@KowalskiThomas

Copy link
Copy Markdown
Contributor

Resolved my comments to avoid popping this from the MQ.

@taegyunkim

Copy link
Copy Markdown
Contributor Author

@KowalskiThomas The next step would be making sure that we clear greenlet task names from the string table. As far as I understand, we're guarded from the cases where we can't find string table entry given a key.

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 6613299 into main Mar 6, 2026
354 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the scp-1039-psql-connection-exhaustion branch March 6, 2026 15:51
@KowalskiThomas

Copy link
Copy Markdown
Contributor

The next step would be making sure that we clear greenlet task names from the string table.

Yes we do need to do that. I haven't gotten back to my memory leak PR yet since I was working on R&D stuff!

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

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos Profiling Continous Profling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants