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 | FILE : UTSEM.CPP |
6 | |
7 | |
8 | |
9 | Purpose: Part of the utilities library for the VIPER project |
10 | |
11 | Abstract : Implements the UTSemReadWrite class. |
12 | ------------------------------------------------------------------------------- |
13 | Revision History: |
14 | |
15 | |
16 | *******************************************************************************/ |
17 | #include "stdafx.h" |
18 | #include "clrhost.h" |
19 | #include "ex.h" |
20 | |
21 | #include <utsem.h> |
22 | #include "contract.h" |
23 | |
24 | // Consider replacing this with a #ifdef INTEROP_DEBUGGING |
25 | #if !defined(SELF_NO_HOST) && defined(_TARGET_X86_) && !defined(FEATURE_PAL) |
26 | // For Interop debugging, the UTSemReadWrite class must inform the debugger |
27 | // that this thread can't be suspended currently. See vm\util.hpp for the |
28 | // implementation of these methods. |
29 | void IncCantStopCount(); |
30 | void DecCantStopCount(); |
31 | #else |
32 | #define IncCantStopCount() |
33 | #define DecCantStopCount() |
34 | #endif // !SELF_NO_HOST && _TARGET_X86_ |
35 | |
36 | /****************************************************************************** |
37 | Definitions of the bit fields in UTSemReadWrite::m_dwFlag: |
38 | |
39 | Warning: The code assume that READER_MASK is in the low-order bits of the DWORD. |
40 | ******************************************************************************/ |
41 | |
42 | const ULONG READERS_MASK = 0x000003FF; // field that counts number of readers |
43 | const ULONG READERS_INCR = 0x00000001; // amount to add to increment number of readers |
44 | |
45 | // The following field is 2 bits long to make it easier to catch errors. |
46 | // (If the number of writers ever exceeds 1, we've got problems.) |
47 | const ULONG WRITERS_MASK = 0x00000C00; // field that counts number of writers |
48 | const ULONG WRITERS_INCR = 0x00000400; // amount to add to increment number of writers |
49 | |
50 | const ULONG READWAITERS_MASK = 0x003FF000; // field that counts number of threads waiting to read |
51 | const ULONG READWAITERS_INCR = 0x00001000; // amount to add to increment number of read waiters |
52 | |
53 | const ULONG WRITEWAITERS_MASK = 0xFFC00000; // field that counts number of threads waiting to write |
54 | const ULONG WRITEWAITERS_INCR = 0x00400000; // amount to add to increment number of write waiters |
55 | |
56 | // ====================================================================================== |
57 | // Spinning support |
58 | |
59 | // Copy of definition from file:..\VM\spinlock.h |
60 | #define CALLER_LIMITS_SPINNING 0 |
61 | |
62 | #if (defined(SELF_NO_HOST) && !defined(CROSSGEN_COMPILE)) || (defined(FEATURE_PAL) && defined(DACCESS_COMPILE)) |
63 | |
64 | // When we do not have host, we just call OS - see file:..\VM\hosting.cpp#__SwitchToThread |
65 | BOOL __SwitchToThread(DWORD dwSleepMSec, DWORD dwSwitchCount) |
66 | { |
67 | // This is just simple implementation that does not support full dwSwitchCount arg |
68 | _ASSERTE(dwSwitchCount == CALLER_LIMITS_SPINNING); |
69 | return SwitchToThread(); |
70 | } |
71 | |
72 | Volatile<BOOL> g_fInitializedGlobalSystemInfo = FALSE; |
73 | |
74 | // Global System Information |
75 | SYSTEM_INFO g_SystemInfo; |
76 | |
77 | // Configurable constants used across our spin locks |
78 | SpinConstants g_SpinConstants = { |
79 | 50, // dwInitialDuration |
80 | 40000, // dwMaximumDuration - ideally (20000 * max(2, numProc)) ... updated in code:InitializeSpinConstants_NoHost |
81 | 3, // dwBackoffFactor |
82 | 10, // dwRepetitions |
83 | 0 // dwMonitorSpinCount |
84 | }; |
85 | |
86 | inline void InitializeSpinConstants_NoHost() |
87 | { |
88 | g_SpinConstants.dwMaximumDuration = max(2, g_SystemInfo.dwNumberOfProcessors) * 20000; |
89 | } |
90 | |
91 | #else //!SELF_NO_HOST || CROSSGEN_COMPILE |
92 | |
93 | // Use VM/CrossGen functions and variables |
94 | BOOL __SwitchToThread (DWORD dwSleepMSec, DWORD dwSwitchCount); |
95 | extern SYSTEM_INFO g_SystemInfo; |
96 | extern SpinConstants g_SpinConstants; |
97 | |
98 | #endif //!SELF_NO_HOST || CROSSGEN_COMPILE |
99 | |
100 | /****************************************************************************** |
101 | Function : UTSemReadWrite::UTSemReadWrite |
102 | |
103 | Abstract: Constructor. |
104 | ******************************************************************************/ |
105 | UTSemReadWrite::UTSemReadWrite() |
106 | { |
107 | CONTRACTL |
108 | { |
109 | NOTHROW; |
110 | GC_NOTRIGGER; |
111 | } |
112 | CONTRACTL_END; |
113 | |
114 | #if defined(SELF_NO_HOST) && !defined(CROSSGEN_COMPILE) |
115 | if (!g_fInitializedGlobalSystemInfo) |
116 | { |
117 | GetSystemInfo(&g_SystemInfo); |
118 | InitializeSpinConstants_NoHost(); |
119 | |
120 | g_fInitializedGlobalSystemInfo = TRUE; |
121 | } |
122 | #endif //SELF_NO_HOST && !CROSSGEN_COMPILE |
123 | |
124 | m_dwFlag = 0; |
125 | m_pReadWaiterSemaphore = NULL; |
126 | m_pWriteWaiterEvent = NULL; |
127 | } |
128 | |
129 | |
130 | /****************************************************************************** |
131 | Function : UTSemReadWrite::~UTSemReadWrite |
132 | |
133 | Abstract: Destructor |
134 | ******************************************************************************/ |
135 | UTSemReadWrite::~UTSemReadWrite() |
136 | { |
137 | CONTRACTL |
138 | { |
139 | NOTHROW; |
140 | GC_NOTRIGGER; |
141 | } |
142 | CONTRACTL_END; |
143 | |
144 | _ASSERTE_MSG((m_dwFlag == (ULONG)0), "Destroying a UTSemReadWrite while in use" ); |
145 | |
146 | if (m_pReadWaiterSemaphore != NULL) |
147 | delete m_pReadWaiterSemaphore; |
148 | |
149 | if (m_pWriteWaiterEvent != NULL) |
150 | delete m_pWriteWaiterEvent; |
151 | } |
152 | |
153 | //======================================================================================= |
154 | // |
155 | // Initialize the lock (its semaphore and event) |
156 | // |
157 | HRESULT |
158 | UTSemReadWrite::Init() |
159 | { |
160 | CONTRACTL |
161 | { |
162 | NOTHROW; |
163 | GC_NOTRIGGER; |
164 | } |
165 | CONTRACTL_END; |
166 | |
167 | HRESULT hr = S_OK; |
168 | |
169 | _ASSERTE(m_pReadWaiterSemaphore == NULL); |
170 | _ASSERTE(m_pWriteWaiterEvent == NULL); |
171 | |
172 | EX_TRY |
173 | { |
174 | CONTRACT_VIOLATION(ThrowsViolation); |
175 | |
176 | m_pReadWaiterSemaphore = new Semaphore(); |
177 | m_pReadWaiterSemaphore->Create(0, MAXLONG); |
178 | |
179 | m_pWriteWaiterEvent = new Event(); |
180 | m_pWriteWaiterEvent->CreateAutoEvent(FALSE); |
181 | } |
182 | EX_CATCH |
183 | { |
184 | hr = E_OUTOFMEMORY; |
185 | } |
186 | EX_END_CATCH(SwallowAllExceptions) |
187 | IfFailGo(hr); |
188 | |
189 | ErrExit: |
190 | return hr; |
191 | } // UTSemReadWrite::Init |
192 | |
193 | /****************************************************************************** |
194 | Function : UTSemReadWrite::LockRead |
195 | |
196 | Abstract: Obtain a shared lock |
197 | ******************************************************************************/ |
198 | HRESULT UTSemReadWrite::LockRead() |
199 | { |
200 | CONTRACTL |
201 | { |
202 | NOTHROW; |
203 | GC_NOTRIGGER; |
204 | CAN_TAKE_LOCK; |
205 | } |
206 | CONTRACTL_END; |
207 | |
208 | // Inform CLR that the debugger shouldn't suspend this thread while |
209 | // holding this lock. |
210 | IncCantStopCount(); |
211 | |
212 | // First do some spinning - copied from file:..\VM\crst.cpp#CrstBase::SpinEnter |
213 | for (DWORD iter = 0; iter < g_SpinConstants.dwRepetitions; iter++) |
214 | { |
215 | DWORD i = g_SpinConstants.dwInitialDuration; |
216 | |
217 | do |
218 | { |
219 | DWORD dwFlag = m_dwFlag; |
220 | |
221 | if (dwFlag < READERS_MASK) |
222 | { // There are just readers in the play, try to add one more |
223 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + READERS_INCR, dwFlag)) |
224 | { |
225 | goto ReadLockAcquired; |
226 | } |
227 | } |
228 | |
229 | if (g_SystemInfo.dwNumberOfProcessors <= 1) |
230 | { // We do not need to spin on a single processor |
231 | break; |
232 | } |
233 | |
234 | // Delay by approximately 2*i clock cycles (Pentium III). |
235 | // This is brittle code - future processors may of course execute this |
236 | // faster or slower, and future code generators may eliminate the loop altogether. |
237 | // The precise value of the delay is not critical, however, and I can't think |
238 | // of a better way that isn't machine-dependent. |
239 | int sum = 0; |
240 | |
241 | for (int delayCount = i; --delayCount; ) |
242 | { |
243 | sum += delayCount; |
244 | YieldProcessor(); // indicate to the processor that we are spining |
245 | } |
246 | |
247 | if (sum == 0) |
248 | { |
249 | // never executed, just to fool the compiler into thinking sum is live here, |
250 | // so that it won't optimize away the loop. |
251 | static char dummy; |
252 | dummy++; |
253 | } |
254 | // exponential backoff: wait a factor longer in the next iteration |
255 | i *= g_SpinConstants.dwBackoffFactor; |
256 | } while (i < g_SpinConstants.dwMaximumDuration); |
257 | |
258 | __SwitchToThread(0, CALLER_LIMITS_SPINNING); |
259 | } |
260 | // Stop spinning |
261 | |
262 | // Start waiting |
263 | for (;;) |
264 | { |
265 | DWORD dwFlag = m_dwFlag; |
266 | |
267 | if (dwFlag < READERS_MASK) |
268 | { // There are just readers in the play, try to add one more |
269 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + READERS_INCR, dwFlag)) |
270 | { |
271 | break; |
272 | } |
273 | } |
274 | else if ((dwFlag & READERS_MASK) == READERS_MASK) |
275 | { // The number of readers has reached the maximum (0x3ff), wait 1s |
276 | ClrSleepEx(1000, FALSE); |
277 | } |
278 | else if ((dwFlag & READWAITERS_MASK) == READWAITERS_MASK) |
279 | { // The number of readers waiting on semaphore has reached the maximum (0x3ff), wait 1s |
280 | ClrSleepEx(1000, FALSE); |
281 | } |
282 | else |
283 | { // Try to add waiting reader and then wait for signal |
284 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + READWAITERS_INCR, dwFlag)) |
285 | { |
286 | m_pReadWaiterSemaphore->Wait(INFINITE, FALSE); |
287 | break; |
288 | } |
289 | } |
290 | } |
291 | |
292 | ReadLockAcquired: |
293 | _ASSERTE ((m_dwFlag & READERS_MASK) != 0 && "reader count is zero after acquiring read lock" ); |
294 | _ASSERTE ((m_dwFlag & WRITERS_MASK) == 0 && "writer count is nonzero after acquiring write lock" ); |
295 | EE_LOCK_TAKEN(this); |
296 | |
297 | return S_OK; |
298 | } // UTSemReadWrite::LockRead |
299 | |
300 | |
301 | |
302 | /****************************************************************************** |
303 | Function : UTSemReadWrite::LockWrite |
304 | |
305 | Abstract: Obtain an exclusive lock |
306 | ******************************************************************************/ |
307 | HRESULT UTSemReadWrite::LockWrite() |
308 | { |
309 | CONTRACTL |
310 | { |
311 | NOTHROW; |
312 | GC_NOTRIGGER; |
313 | CAN_TAKE_LOCK; |
314 | } |
315 | CONTRACTL_END; |
316 | |
317 | // Inform CLR that the debugger shouldn't suspend this thread while |
318 | // holding this lock. |
319 | IncCantStopCount(); |
320 | |
321 | // First do some spinning - copied from file:..\VM\crst.cpp#CrstBase::SpinEnter |
322 | for (DWORD iter = 0; iter < g_SpinConstants.dwRepetitions; iter++) |
323 | { |
324 | DWORD i = g_SpinConstants.dwInitialDuration; |
325 | |
326 | do |
327 | { |
328 | DWORD dwFlag = m_dwFlag; |
329 | |
330 | if (dwFlag == 0) |
331 | { // No readers/writers in play, try to add a writer |
332 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, WRITERS_INCR, dwFlag)) |
333 | { |
334 | goto WriteLockAcquired; |
335 | } |
336 | } |
337 | |
338 | if (g_SystemInfo.dwNumberOfProcessors <= 1) |
339 | { // We do not need to spin on a single processor |
340 | break; |
341 | } |
342 | |
343 | // Delay by approximately 2*i clock cycles (Pentium III). |
344 | // This is brittle code - future processors may of course execute this |
345 | // faster or slower, and future code generators may eliminate the loop altogether. |
346 | // The precise value of the delay is not critical, however, and I can't think |
347 | // of a better way that isn't machine-dependent. |
348 | int sum = 0; |
349 | |
350 | for (int delayCount = i; --delayCount; ) |
351 | { |
352 | sum += delayCount; |
353 | YieldProcessor(); // indicate to the processor that we are spining |
354 | } |
355 | |
356 | if (sum == 0) |
357 | { |
358 | // never executed, just to fool the compiler into thinking sum is live here, |
359 | // so that it won't optimize away the loop. |
360 | static char dummy; |
361 | dummy++; |
362 | } |
363 | // exponential backoff: wait a factor longer in the next iteration |
364 | i *= g_SpinConstants.dwBackoffFactor; |
365 | } while (i < g_SpinConstants.dwMaximumDuration); |
366 | |
367 | __SwitchToThread(0, CALLER_LIMITS_SPINNING); |
368 | } |
369 | // Stop spinning |
370 | |
371 | // Start waiting |
372 | for (;;) |
373 | { |
374 | DWORD dwFlag = m_dwFlag; |
375 | |
376 | if (dwFlag == 0) |
377 | { // No readers/writers in play, try to add a writer |
378 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, WRITERS_INCR, dwFlag)) |
379 | { |
380 | break; |
381 | } |
382 | } |
383 | else if ((dwFlag & WRITEWAITERS_MASK) == WRITEWAITERS_MASK) |
384 | { // The number of writers waiting on semaphore has reached the maximum (0x3ff), wait 1s |
385 | ClrSleepEx(1000, FALSE); |
386 | } |
387 | else |
388 | { // Try to add waiting writer and then wait for signal |
389 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + WRITEWAITERS_INCR, dwFlag)) |
390 | { |
391 | m_pWriteWaiterEvent->Wait(INFINITE, FALSE); |
392 | break; |
393 | } |
394 | } |
395 | |
396 | } |
397 | |
398 | WriteLockAcquired: |
399 | _ASSERTE ((m_dwFlag & READERS_MASK) == 0 && "reader count is nonzero after acquiring write lock" ); |
400 | _ASSERTE ((m_dwFlag & WRITERS_MASK) == WRITERS_INCR && "writer count is not 1 after acquiring write lock" ); |
401 | EE_LOCK_TAKEN(this); |
402 | |
403 | return S_OK; |
404 | } // UTSemReadWrite::LockWrite |
405 | |
406 | |
407 | |
408 | /****************************************************************************** |
409 | Function : UTSemReadWrite::UnlockRead |
410 | |
411 | Abstract: Release a shared lock |
412 | ******************************************************************************/ |
413 | void UTSemReadWrite::UnlockRead() |
414 | { |
415 | CONTRACTL |
416 | { |
417 | NOTHROW; |
418 | GC_NOTRIGGER; |
419 | } |
420 | CONTRACTL_END; |
421 | |
422 | ULONG dwFlag; |
423 | |
424 | |
425 | _ASSERTE ((m_dwFlag & READERS_MASK) != 0 && "reader count is zero before releasing read lock" ); |
426 | _ASSERTE ((m_dwFlag & WRITERS_MASK) == 0 && "writer count is nonzero before releasing read lock" ); |
427 | |
428 | for (;;) |
429 | { |
430 | dwFlag = m_dwFlag; |
431 | |
432 | if (dwFlag == READERS_INCR) |
433 | { // we're the last reader, and nobody is waiting |
434 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, (ULONG)0, dwFlag)) |
435 | { |
436 | break; |
437 | } |
438 | } |
439 | |
440 | else if ((dwFlag & READERS_MASK) > READERS_INCR) |
441 | { // we're not the last reader |
442 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag - READERS_INCR, dwFlag)) |
443 | { |
444 | break; |
445 | } |
446 | } |
447 | |
448 | else |
449 | { |
450 | // here, there should be exactly 1 reader (us), and at least one waiting writer. |
451 | _ASSERTE ((dwFlag & READERS_MASK) == READERS_INCR && "UnlockRead consistency error 1" ); |
452 | _ASSERTE ((dwFlag & WRITEWAITERS_MASK) != 0 && "UnlockRead consistency error 2" ); |
453 | |
454 | // one or more writers is waiting, do one of them next |
455 | // (remove a reader (us), remove a write waiter, add a writer |
456 | if (dwFlag == |
457 | InterlockedCompareExchangeT( |
458 | &m_dwFlag, |
459 | dwFlag - READERS_INCR - WRITEWAITERS_INCR + WRITERS_INCR, |
460 | dwFlag)) |
461 | { |
462 | m_pWriteWaiterEvent->Set(); |
463 | break; |
464 | } |
465 | } |
466 | } |
467 | |
468 | DecCantStopCount(); |
469 | EE_LOCK_RELEASED(this); |
470 | } // UTSemReadWrite::UnlockRead |
471 | |
472 | |
473 | /****************************************************************************** |
474 | Function : UTSemReadWrite::UnlockWrite |
475 | |
476 | Abstract: Release an exclusive lock |
477 | ******************************************************************************/ |
478 | void UTSemReadWrite::UnlockWrite() |
479 | { |
480 | CONTRACTL |
481 | { |
482 | NOTHROW; |
483 | GC_NOTRIGGER; |
484 | } |
485 | CONTRACTL_END; |
486 | |
487 | ULONG dwFlag; |
488 | ULONG count; |
489 | |
490 | _ASSERTE ((m_dwFlag & READERS_MASK) == 0 && "reader count is nonzero before releasing write lock" ); |
491 | _ASSERTE ((m_dwFlag & WRITERS_MASK) == WRITERS_INCR && "writer count is not 1 before releasing write lock" ); |
492 | |
493 | for (;;) |
494 | { |
495 | dwFlag = m_dwFlag; |
496 | |
497 | if (dwFlag == WRITERS_INCR) |
498 | { // nobody is waiting |
499 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, (ULONG)0, dwFlag)) |
500 | { |
501 | break; |
502 | } |
503 | } |
504 | |
505 | else if ((dwFlag & READWAITERS_MASK) != 0) |
506 | { // one or more readers are waiting, do them all next |
507 | count = (dwFlag & READWAITERS_MASK) / READWAITERS_INCR; |
508 | // remove a writer (us), remove all read waiters, turn them into readers |
509 | if (dwFlag == |
510 | InterlockedCompareExchangeT( |
511 | &m_dwFlag, |
512 | dwFlag - WRITERS_INCR - count * READWAITERS_INCR + count * READERS_INCR, |
513 | dwFlag)) |
514 | { |
515 | m_pReadWaiterSemaphore->Release(count, NULL); |
516 | break; |
517 | } |
518 | } |
519 | |
520 | else |
521 | { // one or more writers is waiting, do one of them next |
522 | _ASSERTE ((dwFlag & WRITEWAITERS_MASK) != 0 && "UnlockWrite consistency error" ); |
523 | // (remove a writer (us), remove a write waiter, add a writer |
524 | if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag - WRITEWAITERS_INCR, dwFlag)) |
525 | { |
526 | m_pWriteWaiterEvent->Set(); |
527 | break; |
528 | } |
529 | } |
530 | } |
531 | |
532 | DecCantStopCount(); |
533 | EE_LOCK_RELEASED(this); |
534 | } // UTSemReadWrite::UnlockWrite |
535 | |
536 | #ifdef _DEBUG |
537 | |
538 | //======================================================================================= |
539 | BOOL |
540 | UTSemReadWrite::Debug_IsLockedForRead() |
541 | { |
542 | return ((m_dwFlag & READERS_MASK) != 0); |
543 | } |
544 | |
545 | //======================================================================================= |
546 | BOOL |
547 | UTSemReadWrite::Debug_IsLockedForWrite() |
548 | { |
549 | return ((m_dwFlag & WRITERS_MASK) != 0); |
550 | } |
551 | |
552 | #endif //_DEBUG |
553 | |
554 | |