@@ -772,6 +772,91 @@ BOOL CrstBase::IsSafeToTake()
772
772
773
773
#endif // _DEBUG
774
774
775
+ CrstBase::CrstAndForbidSuspendForDebuggerHolder::CrstAndForbidSuspendForDebuggerHolder (CrstBase *pCrst)
776
+ : m_pCrst(pCrst), m_pThreadForExitingForbidRegion(nullptr )
777
+ {
778
+ CONTRACTL
779
+ {
780
+ NOTHROW;
781
+ GC_TRIGGERS;
782
+ MODE_PREEMPTIVE;
783
+ }
784
+ CONTRACTL_END;
785
+
786
+ if (pCrst == nullptr )
787
+ {
788
+ return ;
789
+ }
790
+
791
+ // Reentrant locks are currently not supported
792
+ _ASSERTE ((pCrst->m_dwFlags & CRST_REENTRANCY) == 0 );
793
+
794
+ Thread *pThread = GetThread ();
795
+ _ASSERTE (pThread != nullptr );
796
+ if (pThread->IsInForbidSuspendForDebuggerRegion ())
797
+ {
798
+ AcquireLock (pCrst);
799
+ return ;
800
+ }
801
+
802
+ while (true )
803
+ {
804
+ // Enter the forbid region and make that state change visible to other threads (memory barrier) before checking for the
805
+ // TS_DebugSuspendPending state. The other interacting thread in SysStartSuspendForDebug(), sets TS_DebugSuspendPending
806
+ // and makes that state change visible to other threads (memory barrier) before checking for whether this thread is in
807
+ // the forbid region. This ensures that in race conditions where both threads update the respective state and make the
808
+ // state change visible to other threads, at least one of those threads will see the state change made by the other
809
+ // thread. If this thread sees the state change (TS_DebugSuspendPending), it will avoid entering the forbid region by
810
+ // exiting the lock and pulsing the GC mode to try suspending for the debugger. If SysStartSuspendForDebug() sees the
811
+ // state change (that this thread is in the forbid region), then it will flag this thread appropriately to sync for
812
+ // suspend, and the debugger will later identify this thread as synced after this thread leaves the forbid region.
813
+ //
814
+ // The forbid region could be entered after acquiring the lock, but an additional memory barrier would be necessary. It
815
+ // is entered before the lock just to make use of the implicit memory barrier from acquiring the lock. It is anyway a
816
+ // prerequisite for entering a lock along with entering a forbid-suspend-for-debugger region, that the lock is not held
817
+ // for too long such that the thread can suspend for the debugger in reasonable time.
818
+ pThread->EnterForbidSuspendForDebuggerRegion ();
819
+ AcquireLock (pCrst); // implicit full memory barrier on all supported platforms
820
+
821
+ // This can be an opportunistic check (instead of a volatile load), because if the GC mode change below does not suspend
822
+ // for the debugger (which is also possible with a volatile load), it will just loop around and try again if necessary
823
+ if (!pThread->HasThreadStateOpportunistic (Thread::TS_DebugSuspendPending))
824
+ {
825
+ m_pThreadForExitingForbidRegion = pThread;
826
+ return ;
827
+ }
828
+
829
+ // Cannot enter the forbid region when a suspend for the debugger is pending because there are likely to be subsequent
830
+ // changes to the GC mode inside the lock, and this thread needs to suspend for the debugger in reasonable time. Exit
831
+ // the lock and pulse the GC mode to suspend for the debugger.
832
+ ReleaseLock (pCrst);
833
+ pThread->ExitForbidSuspendForDebuggerRegion ();
834
+ GCX_COOP ();
835
+ }
836
+ }
837
+
838
+ CrstBase::CrstAndForbidSuspendForDebuggerHolder::~CrstAndForbidSuspendForDebuggerHolder ()
839
+ {
840
+ CONTRACTL
841
+ {
842
+ NOTHROW;
843
+ GC_NOTRIGGER;
844
+ MODE_PREEMPTIVE;
845
+ }
846
+ CONTRACTL_END;
847
+
848
+ if (m_pCrst == nullptr )
849
+ {
850
+ return ;
851
+ }
852
+
853
+ ReleaseLock (m_pCrst);
854
+ if (m_pThreadForExitingForbidRegion != nullptr )
855
+ {
856
+ m_pThreadForExitingForbidRegion->ExitForbidSuspendForDebuggerRegion ();
857
+ }
858
+ }
859
+
775
860
#endif // !DACCESS_COMPILE
776
861
777
862
#ifdef TEST_DATA_CONSISTENCY
0 commit comments