1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//
5
6//
7
8#include "common.h"
9#include "simplerwlock.hpp"
10
11BOOL SimpleRWLock::TryEnterRead()
12{
13 STATIC_CONTRACT_NOTHROW;
14 STATIC_CONTRACT_CAN_TAKE_LOCK;
15
16#ifdef _DEBUG
17 PreEnter();
18#endif //_DEBUG
19
20 LONG RWLock;
21
22 do {
23 RWLock = m_RWLock;
24 if( RWLock == -1 ) return FALSE;
25 _ASSERTE (RWLock >= 0);
26 } while( RWLock != InterlockedCompareExchange( &m_RWLock, RWLock+1, RWLock ));
27
28 INCTHREADLOCKCOUNT();
29 EE_LOCK_TAKEN(this);
30
31#ifdef _DEBUG
32 PostEnter();
33#endif //_DEBUG
34
35 return TRUE;
36}
37
38//=====================================================================
39void SimpleRWLock::EnterRead()
40{
41 STATIC_CONTRACT_NOTHROW;
42 STATIC_CONTRACT_CAN_TAKE_LOCK;
43
44 // Custom contract is needed for PostEnter()'s unscoped GC_NoTrigger counter change
45#ifdef ENABLE_CONTRACTS_IMPL
46 CheckGCNoTrigger();
47#endif //ENABLE_CONTRACTS_IMPL
48
49 GCX_MAYBE_PREEMP(m_gcMode == PREEMPTIVE);
50
51#ifdef _DEBUG
52 PreEnter();
53#endif //_DEBUG
54
55 DWORD dwSwitchCount = 0;
56
57 while (TRUE)
58 {
59 // prevent writers from being starved. This assumes that writers are rare and
60 // dont hold the lock for a long time.
61 while (IsWriterWaiting())
62 {
63 int spinCount = m_spinCount;
64 while (spinCount > 0) {
65 spinCount--;
66 YieldProcessor();
67 }
68 __SwitchToThread(0, ++dwSwitchCount);
69 }
70
71 if (TryEnterRead())
72 {
73 return;
74 }
75
76 DWORD i = g_SpinConstants.dwInitialDuration;
77 do
78 {
79 if (TryEnterRead())
80 {
81 return;
82 }
83
84 if (g_SystemInfo.dwNumberOfProcessors <= 1)
85 {
86 break;
87 }
88 // Delay by approximately 2*i clock cycles (Pentium III).
89 // This is brittle code - future processors may of course execute this
90 // faster or slower, and future code generators may eliminate the loop altogether.
91 // The precise value of the delay is not critical, however, and I can't think
92 // of a better way that isn't machine-dependent.
93 for (int delayCount = i; --delayCount; )
94 {
95 YieldProcessor(); // indicate to the processor that we are spining
96 }
97
98 // exponential backoff: wait a factor longer in the next iteration
99 i *= g_SpinConstants.dwBackoffFactor;
100 }
101 while (i < g_SpinConstants.dwMaximumDuration);
102
103 __SwitchToThread(0, ++dwSwitchCount);
104 }
105}
106
107//=====================================================================
108BOOL SimpleRWLock::TryEnterWrite()
109{
110 STATIC_CONTRACT_NOTHROW;
111 STATIC_CONTRACT_CAN_TAKE_LOCK;
112
113#ifdef _DEBUG
114 PreEnter();
115#endif //_DEBUG
116
117 LONG RWLock = InterlockedCompareExchange( &m_RWLock, -1, 0 );
118
119 _ASSERTE (RWLock >= 0 || RWLock == -1);
120
121 if( RWLock ) {
122 return FALSE;
123 }
124
125 INCTHREADLOCKCOUNT();
126 EE_LOCK_TAKEN(this);
127
128#ifdef _DEBUG
129 PostEnter();
130#endif //_DEBUG
131
132 ResetWriterWaiting();
133
134 return TRUE;
135}
136
137//=====================================================================
138void SimpleRWLock::EnterWrite()
139{
140 STATIC_CONTRACT_NOTHROW;
141 STATIC_CONTRACT_CAN_TAKE_LOCK;
142
143 // Custom contract is needed for PostEnter()'s unscoped GC_NoTrigger counter change
144#ifdef ENABLE_CONTRACTS_IMPL
145 CheckGCNoTrigger();
146#endif //ENABLE_CONTRACTS_IMPL
147
148 GCX_MAYBE_PREEMP(m_gcMode == PREEMPTIVE);
149
150#ifdef _DEBUG
151 PreEnter();
152#endif //_DEBUG
153
154 BOOL set = FALSE;
155
156 DWORD dwSwitchCount = 0;
157
158 while (TRUE)
159 {
160 if (TryEnterWrite())
161 {
162 return;
163 }
164
165 // set the writer waiting word, if not already set, to notify potential
166 // readers to wait. Remember, if the word is set, so it can be reset later.
167 if (!IsWriterWaiting())
168 {
169 SetWriterWaiting();
170 set = TRUE;
171 }
172
173 DWORD i = g_SpinConstants.dwInitialDuration;
174 do
175 {
176 if (TryEnterWrite())
177 {
178 return;
179 }
180
181 if (g_SystemInfo.dwNumberOfProcessors <= 1)
182 {
183 break;
184 }
185 // Delay by approximately 2*i clock cycles (Pentium III).
186 // This is brittle code - future processors may of course execute this
187 // faster or slower, and future code generators may eliminate the loop altogether.
188 // The precise value of the delay is not critical, however, and I can't think
189 // of a better way that isn't machine-dependent.
190 for (int delayCount = i; --delayCount; )
191 {
192 YieldProcessor(); // indicate to the processor that we are spining
193 }
194
195 // exponential backoff: wait a factor longer in the next iteration
196 i *= g_SpinConstants.dwBackoffFactor;
197 }
198 while (i < g_SpinConstants.dwMaximumDuration);
199
200 __SwitchToThread(0, ++dwSwitchCount);
201 }
202}
203
204#ifdef ENABLE_CONTRACTS_IMPL
205//=========================================================================
206// Asserts if lock mode is PREEMPTIVE and thread in a GC_NOTRIGGER contract
207//=========================================================================
208void SimpleRWLock::CheckGCNoTrigger()
209{
210 STATIC_CONTRACT_NOTHROW;
211
212 // On PREEMPTIVE locks we'll toggle the GC mode, so we better not be in a GC_NOTRIGGERS region
213 if (m_gcMode == PREEMPTIVE)
214 {
215 ClrDebugState *pClrDebugState = CheckClrDebugState();
216 if (pClrDebugState)
217 {
218 if (pClrDebugState->GetGCNoTriggerCount())
219 {
220 // If we have no thread object, we won't be toggling the GC. This is the case,
221 // for example, on the debugger helper thread which is always GC_NOTRIGGERS.
222 if (GetThreadNULLOk() != NULL)
223 {
224 if (!( (GCViolation|BadDebugState) & pClrDebugState->ViolationMask()))
225 {
226 CONTRACT_ASSERT("You cannot enter a lock in a GC_NOTRIGGER region.",
227 Contract::GC_NoTrigger,
228 Contract::GC_Mask,
229 __FUNCTION__,
230 __FILE__,
231 __LINE__);
232 }
233 }
234 }
235
236 // The mode checks and enforcement of GC_NOTRIGGER during the lock are done in SimpleRWLock::PostEnter().
237 }
238 }
239}
240#endif //ENABLE_CONTRACTS_IMPL
241
242#ifdef _DEBUG
243//=====================================================================
244// GC mode assertions before acquiring a lock based on its mode.
245//=====================================================================
246void SimpleRWLock::PreEnter()
247{
248 CONTRACTL
249 {
250 NOTHROW;
251 GC_NOTRIGGER;
252 DEBUG_ONLY;
253 }
254 CONTRACTL_END;
255
256 if (m_gcMode == PREEMPTIVE)
257 _ASSERTE(!GetThread() || !GetThread()->PreemptiveGCDisabled());
258 else if (m_gcMode == COOPERATIVE)
259 _ASSERTE(!GetThread() || GetThread()->PreemptiveGCDisabled());
260}
261
262//=====================================================================
263// GC checks after lock acquisition for avoiding deadlock scenarios.
264//=====================================================================
265void SimpleRWLock::PostEnter()
266{
267 WRAPPER_NO_CONTRACT;
268
269 if ((m_gcMode == COOPERATIVE) || (m_gcMode == COOPERATIVE_OR_PREEMPTIVE))
270 {
271 Thread * pThread = GetThreadNULLOk();
272 if (pThread == NULL)
273 {
274 // Cannot set NoTrigger. This could conceivably turn into
275 // A GC hole if the thread is created and then a GC rendezvous happens
276 // while the lock is still held.
277 }
278 else
279 {
280 // Keep a count, since the thread may change from NULL to non-NULL and
281 // we don't want to have unbalanced NoTrigger calls
282 InterlockedIncrement(&m_countNoTriggerGC);
283 INCONTRACT(pThread->BeginNoTriggerGC(__FILE__, __LINE__));
284 }
285 }
286}
287
288//=====================================================================
289// GC checks before lock release for avoiding deadlock scenarios.
290//=====================================================================
291void SimpleRWLock::PreLeave()
292{
293 WRAPPER_NO_CONTRACT;
294
295 if (m_countNoTriggerGC > 0)
296 {
297 DWORD countNoTriggerGC = InterlockedDecrement(&m_countNoTriggerGC);
298 _ASSERTE(countNoTriggerGC >= 0);
299
300 Thread * pThread = GetThreadNULLOk();
301 if (pThread != NULL)
302 {
303 INCONTRACT(pThread->EndNoTriggerGC());
304 }
305 }
306}
307#endif //_DEBUG
308