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-------------------------------------------------------------------------------
13Revision 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.
29void IncCantStopCount();
30void DecCantStopCount();
31#else
32#define IncCantStopCount()
33#define DecCantStopCount()
34#endif // !SELF_NO_HOST && _TARGET_X86_
35
36/******************************************************************************
37Definitions of the bit fields in UTSemReadWrite::m_dwFlag:
38
39Warning: The code assume that READER_MASK is in the low-order bits of the DWORD.
40******************************************************************************/
41
42const ULONG READERS_MASK = 0x000003FF; // field that counts number of readers
43const 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.)
47const ULONG WRITERS_MASK = 0x00000C00; // field that counts number of writers
48const ULONG WRITERS_INCR = 0x00000400; // amount to add to increment number of writers
49
50const ULONG READWAITERS_MASK = 0x003FF000; // field that counts number of threads waiting to read
51const ULONG READWAITERS_INCR = 0x00001000; // amount to add to increment number of read waiters
52
53const ULONG WRITEWAITERS_MASK = 0xFFC00000; // field that counts number of threads waiting to write
54const 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
65BOOL __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
72Volatile<BOOL> g_fInitializedGlobalSystemInfo = FALSE;
73
74// Global System Information
75SYSTEM_INFO g_SystemInfo;
76
77// Configurable constants used across our spin locks
78SpinConstants 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
86inline 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
94BOOL __SwitchToThread (DWORD dwSleepMSec, DWORD dwSwitchCount);
95extern SYSTEM_INFO g_SystemInfo;
96extern SpinConstants g_SpinConstants;
97
98#endif //!SELF_NO_HOST || CROSSGEN_COMPILE
99
100/******************************************************************************
101Function : UTSemReadWrite::UTSemReadWrite
102
103Abstract: Constructor.
104******************************************************************************/
105UTSemReadWrite::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/******************************************************************************
131Function : UTSemReadWrite::~UTSemReadWrite
132
133Abstract: Destructor
134******************************************************************************/
135UTSemReadWrite::~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//
157HRESULT
158UTSemReadWrite::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
189ErrExit:
190 return hr;
191} // UTSemReadWrite::Init
192
193/******************************************************************************
194Function : UTSemReadWrite::LockRead
195
196Abstract: Obtain a shared lock
197******************************************************************************/
198HRESULT 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
292ReadLockAcquired:
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/******************************************************************************
303Function : UTSemReadWrite::LockWrite
304
305Abstract: Obtain an exclusive lock
306******************************************************************************/
307HRESULT 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
398WriteLockAcquired:
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/******************************************************************************
409Function : UTSemReadWrite::UnlockRead
410
411Abstract: Release a shared lock
412******************************************************************************/
413void 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/******************************************************************************
474Function : UTSemReadWrite::UnlockWrite
475
476Abstract: Release an exclusive lock
477******************************************************************************/
478void 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//=======================================================================================
539BOOL
540UTSemReadWrite::Debug_IsLockedForRead()
541{
542 return ((m_dwFlag & READERS_MASK) != 0);
543}
544
545//=======================================================================================
546BOOL
547UTSemReadWrite::Debug_IsLockedForWrite()
548{
549 return ((m_dwFlag & WRITERS_MASK) != 0);
550}
551
552#endif //_DEBUG
553
554