Skip to content

Add debug assert to detect CRST_DEFAULT locks taken under ANYMODE locks#128912

Open
davidwrighton wants to merge 1 commit into
dotnet:mainfrom
davidwrighton:debug-anymode-lock-check
Open

Add debug assert to detect CRST_DEFAULT locks taken under ANYMODE locks#128912
davidwrighton wants to merge 1 commit into
dotnet:mainfrom
davidwrighton:debug-anymode-lock-check

Conversation

@davidwrighton
Copy link
Copy Markdown
Member

Note

This PR description was generated with the assistance of GitHub Copilot.

Summary

Adds a debug-only check that detects when a CRST_DEFAULT lock (or PREEMPTIVE SimpleRWLock) is acquired while a CRST_UNSAFE_ANYMODE lock (or COOPERATIVE_OR_PREEMPTIVE SimpleRWLock) is already held on the same thread.

Motivation

Taking a GC_TRIGGERS lock while holding an ANYMODE lock can cause a three-way deadlock:

  1. Thread A holds the ANYMODE lock and attempts to acquire a DEFAULT lock, which transitions GC modes.
  2. Thread B attempts to acquire the ANYMODE lock from cooperative mode.
  3. Thread C triggers a GC, suspending cooperative threads.

All three threads can deadlock waiting on each other.

Changes

  • Added a thread_local int t_unsafeAnyModeHeldCount counter (debug-only) that tracks how many ANYMODE-style locks are held on the current thread.
  • CrstBase::Enter() increments the counter when acquiring a CRST_UNSAFE_ANYMODE lock and asserts it is zero when acquiring a default lock.
  • CrstBase::Leave() decrements the counter when releasing a CRST_UNSAFE_ANYMODE lock.
  • SimpleRWLock::PostEnter() increments the counter for COOPERATIVE_OR_PREEMPTIVE locks.
  • SimpleRWLock::PreLeave() decrements the counter for COOPERATIVE_OR_PREEMPTIVE locks.
  • SimpleRWLock::EnterRead() and EnterWrite() assert the counter is zero for PREEMPTIVE locks.

All changes are guarded by #ifdef _DEBUG and have no effect on release builds.

Add a thread_local counter that tracks how many CRST_UNSAFE_ANYMODE (and
SimpleRWLock COOPERATIVE_OR_PREEMPTIVE) locks are held on the current thread.

When acquiring a CRST_DEFAULT lock or a PREEMPTIVE SimpleRWLock, assert that
the counter is zero. Taking a GC_TRIGGERS lock while holding an ANYMODE lock
is illegal because it may cause a deadlock: if another thread attempts to
acquire the ANYMODE lock from cooperative mode while the DEFAULT lock is
transitioning between GC modes, and a third thread triggers a GC, all three
threads can deadlock.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 adds new debug-only invariants in CoreCLR’s VM locking infrastructure to detect a dangerous lock-ordering pattern: acquiring a GC_TRIGGERS-style lock (CRST default / PREEMPTIVE SimpleRWLock) while an ANYMODE-style lock is already held on the same thread.

Changes:

  • Introduces a debug-only thread_local per-thread counter to track “anymode/no-trigger” locks held.
  • Adds debug asserts in CrstBase::Enter() and SimpleRWLock::EnterRead/EnterWrite() to fail fast when taking GC_TRIGGERS-style locks under those locks.
  • Updates CrstBase and SimpleRWLock enter/leave paths to increment/decrement the counter.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/coreclr/vm/simplerwlock.hpp Declares the debug-only t_unsafeAnyModeHeldCount for SimpleRWLock codepaths.
src/coreclr/vm/simplerwlock.cpp Adds PREEMPTIVE acquisition asserts and updates the counter for COOPERATIVE_OR_PREEMPTIVE lock enter/leave.
src/coreclr/vm/crst.h Declares the debug-only t_unsafeAnyModeHeldCount for CRST codepaths.
src/coreclr/vm/crst.cpp Defines the counter and adds the CRST default-lock assert plus counter maintenance for CRST_UNSAFE_ANYMODE locks.

Comment thread src/coreclr/vm/crst.cpp
Comment thread src/coreclr/vm/crst.cpp
Comment on lines +304 to +307
if (m_dwFlags & CRST_UNSAFE_ANYMODE)
{
t_unsafeAnyModeHeldCount--;
}
Comment on lines +304 to +307
if (m_gcMode == COOPERATIVE_OR_PREEMPTIVE)
{
t_unsafeAnyModeHeldCount--;
}
Comment thread src/coreclr/vm/crst.h
Comment on lines +99 to +101
// Per-thread count of CRST_UNSAFE_ANYMODE (and equivalent) locks currently held.
// Used to detect illegal acquisition of GC_TRIGGERS locks under ANYMODE locks.
extern thread_local int t_unsafeAnyModeHeldCount;
Comment on lines +50 to +53
_ASSERTE_MSG(t_unsafeAnyModeHeldCount == 0,
"Taking a PREEMPTIVE SimpleRWLock while a COOPERATIVE_OR_PREEMPTIVE lock or "
"CRST_UNSAFE_ANYMODE lock is held is illegal. "
"The PREEMPTIVE lock may trigger a GC, which is not allowed under ANYMODE locks.");
Comment on lines +151 to +154
_ASSERTE_MSG(t_unsafeAnyModeHeldCount == 0,
"Taking a PREEMPTIVE SimpleRWLock while a COOPERATIVE_OR_PREEMPTIVE lock or "
"CRST_UNSAFE_ANYMODE lock is held is illegal. "
"The PREEMPTIVE lock may trigger a GC, which is not allowed under ANYMODE locks.");
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.

2 participants