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 | |
11 | BOOL 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 | //===================================================================== |
39 | void 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 | //===================================================================== |
108 | BOOL 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 | //===================================================================== |
138 | void 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 | //========================================================================= |
208 | void 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 | //===================================================================== |
246 | void 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 | //===================================================================== |
265 | void 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 | //===================================================================== |
291 | void 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 | |