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: ListLock.h |
6 | // |
7 | |
8 | // |
9 | // =========================================================================== |
10 | // This file decribes the list lock and deadlock aware list lock. |
11 | // =========================================================================== |
12 | |
13 | #ifndef LISTLOCK_H |
14 | #define LISTLOCK_H |
15 | |
16 | #include "vars.hpp" |
17 | #include "threads.h" |
18 | #include "crst.h" |
19 | |
20 | template < typename ELEMENT > |
21 | class ListLockBase; |
22 | // This structure is used for running class init methods or JITing methods |
23 | // (m_pData points to a FunctionDesc). This class cannot have a destructor since it is used |
24 | // in function that also have EX_TRY's and the VC compiler doesn't allow classes with destructors |
25 | // to be allocated in a function that used SEH. |
26 | // <TODO>@FUTURE Keep a pool of these (e.g. an array), so we don't have to allocate on the fly</TODO> |
27 | // m_hInitException contains a handle to the exception thrown by the class init. This |
28 | // allows us to throw this information to the caller on subsequent class init attempts. |
29 | template < typename ELEMENT > |
30 | class ListLockEntryBase |
31 | { |
32 | friend class ListLockBase<ELEMENT>; |
33 | typedef ListLockEntryBase<ELEMENT> Entry_t; |
34 | typedef ListLockBase<ELEMENT> List_t; |
35 | typedef typename List_t::LockHolder ListLockHolder; |
36 | |
37 | |
38 | public: |
39 | #ifdef _DEBUG |
40 | bool Check() |
41 | { |
42 | WRAPPER_NO_CONTRACT; |
43 | |
44 | return m_dwRefCount != (DWORD)-1; |
45 | } |
46 | #endif // DEBUG |
47 | |
48 | DeadlockAwareLock m_deadlock; |
49 | List_t * m_pList; |
50 | ELEMENT m_data; |
51 | Crst m_Crst; |
52 | const char * m_pszDescription; |
53 | Entry_t * m_pNext; |
54 | DWORD m_dwRefCount; |
55 | HRESULT m_hrResultCode; |
56 | LOADERHANDLE m_hInitException; |
57 | PTR_LoaderAllocator m_pLoaderAllocator; |
58 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
59 | // Field to maintain the corruption severity of the exception |
60 | CorruptionSeverity m_CorruptionSeverity; |
61 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
62 | |
63 | ListLockEntryBase(List_t *pList, ELEMENT data, const char *description = NULL) |
64 | : m_deadlock(description), |
65 | m_pList(pList), |
66 | m_data(data), |
67 | m_Crst(CrstListLock, |
68 | (CrstFlags)(CRST_REENTRANCY | (pList->IsHostBreakable() ? CRST_HOST_BREAKABLE : 0))), |
69 | m_pszDescription(description), |
70 | m_pNext(NULL), |
71 | m_dwRefCount(1), |
72 | m_hrResultCode(S_FALSE), |
73 | m_hInitException(NULL), |
74 | m_pLoaderAllocator(dac_cast<PTR_LoaderAllocator>(nullptr)) |
75 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
76 | , |
77 | m_CorruptionSeverity(NotCorrupting) |
78 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
79 | { |
80 | WRAPPER_NO_CONTRACT; |
81 | } |
82 | |
83 | virtual ~ListLockEntryBase() |
84 | { |
85 | } |
86 | |
87 | DEBUG_NOINLINE void Enter() |
88 | { |
89 | WRAPPER_NO_CONTRACT; |
90 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
91 | |
92 | m_deadlock.BeginEnterLock(); |
93 | DeadlockAwareLock::BlockingLockHolder dlLock; |
94 | m_Crst.Enter(); |
95 | m_deadlock.EndEnterLock(); |
96 | } |
97 | |
98 | BOOL CanDeadlockAwareEnter() |
99 | { |
100 | WRAPPER_NO_CONTRACT; |
101 | |
102 | return m_deadlock.CanEnterLock(); |
103 | } |
104 | |
105 | DEBUG_NOINLINE BOOL DeadlockAwareEnter() |
106 | { |
107 | WRAPPER_NO_CONTRACT; |
108 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
109 | |
110 | if (!m_deadlock.TryBeginEnterLock()) |
111 | return FALSE; |
112 | |
113 | DeadlockAwareLock::BlockingLockHolder dlLock; |
114 | m_Crst.Enter(); |
115 | m_deadlock.EndEnterLock(); |
116 | |
117 | return TRUE; |
118 | } |
119 | |
120 | DEBUG_NOINLINE void Leave() |
121 | { |
122 | WRAPPER_NO_CONTRACT; |
123 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
124 | |
125 | m_deadlock.LeaveLock(); |
126 | m_Crst.Leave(); |
127 | } |
128 | |
129 | static Entry_t *Find(List_t* pLock, ELEMENT data, const char *description = NULL) |
130 | { |
131 | CONTRACTL |
132 | { |
133 | THROWS; |
134 | GC_NOTRIGGER; |
135 | MODE_ANY; |
136 | } |
137 | CONTRACTL_END; |
138 | |
139 | _ASSERTE(pLock->HasLock()); |
140 | |
141 | Entry_t *pEntry = pLock->Find(data); |
142 | if (pEntry == NULL) |
143 | { |
144 | pEntry = new Entry_t(pLock, data, description); |
145 | pLock->AddElement(pEntry); |
146 | } |
147 | else |
148 | pEntry->AddRef(); |
149 | |
150 | return pEntry; |
151 | }; |
152 | |
153 | |
154 | void AddRef() |
155 | { |
156 | CONTRACTL |
157 | { |
158 | NOTHROW; |
159 | GC_NOTRIGGER; |
160 | MODE_ANY; |
161 | PRECONDITION(CheckPointer(this)); |
162 | } |
163 | CONTRACTL_END; |
164 | |
165 | FastInterlockIncrement((LONG*)&m_dwRefCount); |
166 | } |
167 | |
168 | void Release() |
169 | { |
170 | CONTRACTL |
171 | { |
172 | NOTHROW; |
173 | GC_TRIGGERS; |
174 | MODE_ANY; |
175 | PRECONDITION(CheckPointer(this)); |
176 | } |
177 | CONTRACTL_END; |
178 | |
179 | ListLockHolder lock(m_pList); |
180 | |
181 | if (FastInterlockDecrement((LONG*)&m_dwRefCount) == 0) |
182 | { |
183 | // Remove from list |
184 | m_pList->Unlink(this); |
185 | delete this; |
186 | } |
187 | }; |
188 | |
189 | #ifdef _DEBUG |
190 | BOOL HasLock() |
191 | { |
192 | WRAPPER_NO_CONTRACT; |
193 | return(m_Crst.OwnedByCurrentThread()); |
194 | } |
195 | #endif |
196 | |
197 | // LockHolder holds the lock of the element, not the element itself |
198 | |
199 | DEBUG_NOINLINE static void LockHolderEnter(Entry_t *pThis) PUB |
200 | { |
201 | WRAPPER_NO_CONTRACT; |
202 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
203 | pThis->Enter(); |
204 | } |
205 | |
206 | DEBUG_NOINLINE static void LockHolderLeave(Entry_t *pThis) PUB |
207 | { |
208 | WRAPPER_NO_CONTRACT; |
209 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
210 | pThis->Leave(); |
211 | } |
212 | |
213 | DEBUG_NOINLINE void FinishDeadlockAwareEnter() |
214 | { |
215 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
216 | DeadlockAwareLock::BlockingLockHolder dlLock; |
217 | m_Crst.Enter(); |
218 | m_deadlock.EndEnterLock(); |
219 | } |
220 | |
221 | typedef Wrapper<Entry_t *, LockHolderEnter, LockHolderLeave> LockHolderBase; |
222 | |
223 | class LockHolder : public LockHolderBase |
224 | { |
225 | public: |
226 | |
227 | LockHolder() |
228 | : LockHolderBase(NULL, FALSE) |
229 | { |
230 | } |
231 | |
232 | LockHolder(Entry_t *value, BOOL take = TRUE) |
233 | : LockHolderBase(value, take) |
234 | { |
235 | } |
236 | |
237 | BOOL DeadlockAwareAcquire() |
238 | { |
239 | if (!this->m_acquired && this->m_value != NULL) |
240 | { |
241 | if (!this->m_value->m_deadlock.TryBeginEnterLock()) |
242 | return FALSE; |
243 | this->m_value->FinishDeadlockAwareEnter(); |
244 | this->m_acquired = TRUE; |
245 | } |
246 | return TRUE; |
247 | } |
248 | }; |
249 | }; |
250 | |
251 | template < typename ELEMENT > |
252 | class ListLockBase |
253 | { |
254 | typedef ListLockBase<ELEMENT> List_t; |
255 | typedef ListLockEntryBase<ELEMENT> Entry_t; |
256 | |
257 | protected: |
258 | CrstStatic m_Crst; |
259 | BOOL m_fInited; |
260 | BOOL m_fHostBreakable; // Lock can be broken by a host for deadlock detection |
261 | Entry_t * m_pHead; |
262 | |
263 | public: |
264 | |
265 | BOOL IsInitialized() |
266 | { |
267 | LIMITED_METHOD_CONTRACT; |
268 | return m_fInited; |
269 | } |
270 | inline void PreInit() |
271 | { |
272 | LIMITED_METHOD_CONTRACT; |
273 | memset(this, 0, sizeof(*this)); |
274 | } |
275 | |
276 | // DO NOT MAKE A CONSTRUCTOR FOR THIS CLASS - There are global instances |
277 | void Init(CrstType crstType, CrstFlags flags, BOOL fHostBreakable = FALSE) |
278 | { |
279 | WRAPPER_NO_CONTRACT; |
280 | PreInit(); |
281 | m_Crst.Init(crstType, flags); |
282 | m_fInited = TRUE; |
283 | m_fHostBreakable = fHostBreakable; |
284 | } |
285 | |
286 | void Destroy() |
287 | { |
288 | WRAPPER_NO_CONTRACT; |
289 | // There should not be any of these around |
290 | _ASSERTE(m_pHead == NULL || dbg_fDrasticShutdown || g_fInControlC); |
291 | |
292 | if (m_fInited) |
293 | { |
294 | m_fInited = FALSE; |
295 | m_Crst.Destroy(); |
296 | } |
297 | } |
298 | |
299 | BOOL IsHostBreakable () const |
300 | { |
301 | LIMITED_METHOD_CONTRACT; |
302 | return m_fHostBreakable; |
303 | } |
304 | |
305 | void AddElement(Entry_t* pElement) |
306 | { |
307 | WRAPPER_NO_CONTRACT; |
308 | pElement->m_pNext = m_pHead; |
309 | m_pHead = pElement; |
310 | } |
311 | |
312 | |
313 | DEBUG_NOINLINE void Enter() |
314 | { |
315 | CANNOT_HAVE_CONTRACT; // See below |
316 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
317 | #if 0 // The cleanup logic contract will cause any forbid GC state from the Crst to |
318 | // get deleted. This causes asserts from Leave. We probably should make the contract |
319 | // implementation tolerant of this pattern, or else ensure that the state the contract |
320 | // modifies is not used by any other code. |
321 | CONTRACTL |
322 | { |
323 | NOTHROW; |
324 | UNCHECKED(GC_TRIGGERS); // May trigger or not based on Crst's type |
325 | MODE_ANY; |
326 | PRECONDITION(CheckPointer(this)); |
327 | } |
328 | CONTRACTL_END; |
329 | #endif |
330 | |
331 | m_Crst.Enter(); |
332 | } |
333 | |
334 | DEBUG_NOINLINE void Leave() |
335 | { |
336 | WRAPPER_NO_CONTRACT; |
337 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
338 | m_Crst.Leave(); |
339 | } |
340 | |
341 | // Must own the lock before calling this or is ok if the debugger has |
342 | // all threads stopped |
343 | inline Entry_t *Find(ELEMENT data) |
344 | { |
345 | CONTRACTL |
346 | { |
347 | NOTHROW; |
348 | GC_NOTRIGGER; |
349 | PRECONDITION(CheckPointer(this)); |
350 | #ifdef DEBUGGING_SUPPORTED |
351 | PRECONDITION(m_Crst.OwnedByCurrentThread() || |
352 | CORDebuggerAttached() |
353 | // This condition should be true, but it is awkward to assert it because adding dbginterface.h creates lots of cycles in the includes |
354 | // It didn't seem valuable enough to refactor out a wrapper just to preserve it |
355 | /* && g_pDebugInterface->IsStopped() */); |
356 | #else |
357 | PRECONDITION(m_Crst.OwnedByCurrentThread()); |
358 | #endif // DEBUGGING_SUPPORTED |
359 | |
360 | } |
361 | CONTRACTL_END; |
362 | |
363 | Entry_t *pSearch; |
364 | |
365 | for (pSearch = m_pHead; pSearch != NULL; pSearch = pSearch->m_pNext) |
366 | { |
367 | if (pSearch->m_data == data) |
368 | return pSearch; |
369 | } |
370 | |
371 | return NULL; |
372 | } |
373 | |
374 | // Must own the lock before calling this! |
375 | Entry_t* Pop(BOOL unloading = FALSE) |
376 | { |
377 | LIMITED_METHOD_CONTRACT; |
378 | #ifdef _DEBUG |
379 | if(unloading == FALSE) |
380 | _ASSERTE(m_Crst.OwnedByCurrentThread()); |
381 | #endif |
382 | |
383 | if(m_pHead == NULL) return NULL; |
384 | Entry_t* pEntry = m_pHead; |
385 | m_pHead = m_pHead->m_pNext; |
386 | return pEntry; |
387 | } |
388 | |
389 | // Must own the lock before calling this! |
390 | Entry_t* Peek() |
391 | { |
392 | LIMITED_METHOD_CONTRACT; |
393 | _ASSERTE(m_Crst.OwnedByCurrentThread()); |
394 | return m_pHead; |
395 | } |
396 | |
397 | // Must own the lock before calling this! |
398 | BOOL Unlink(Entry_t *pItem) |
399 | { |
400 | LIMITED_METHOD_CONTRACT; |
401 | _ASSERTE(m_Crst.OwnedByCurrentThread()); |
402 | Entry_t *pSearch; |
403 | Entry_t *pPrev; |
404 | |
405 | pPrev = NULL; |
406 | |
407 | for (pSearch = m_pHead; pSearch != NULL; pSearch = pSearch->m_pNext) |
408 | { |
409 | if (pSearch == pItem) |
410 | { |
411 | if (pPrev == NULL) |
412 | m_pHead = pSearch->m_pNext; |
413 | else |
414 | pPrev->m_pNext = pSearch->m_pNext; |
415 | |
416 | return TRUE; |
417 | } |
418 | |
419 | pPrev = pSearch; |
420 | } |
421 | |
422 | // Not found |
423 | return FALSE; |
424 | } |
425 | |
426 | #ifdef _DEBUG |
427 | BOOL HasLock() |
428 | { |
429 | WRAPPER_NO_CONTRACT; |
430 | STATIC_CONTRACT_SO_TOLERANT; |
431 | return(m_Crst.OwnedByCurrentThread()); |
432 | } |
433 | #endif |
434 | |
435 | DEBUG_NOINLINE static void HolderEnter(List_t *pThis) |
436 | { |
437 | WRAPPER_NO_CONTRACT; |
438 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
439 | pThis->Enter(); |
440 | } |
441 | |
442 | DEBUG_NOINLINE static void HolderLeave(List_t *pThis) |
443 | { |
444 | WRAPPER_NO_CONTRACT; |
445 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
446 | pThis->Leave(); |
447 | } |
448 | |
449 | typedef Wrapper<List_t*, List_t::HolderEnter, List_t::HolderLeave> LockHolder; |
450 | }; |
451 | |
452 | class WaitingThreadListElement |
453 | { |
454 | public: |
455 | Thread * m_pThread; |
456 | WaitingThreadListElement * m_pNext; |
457 | }; |
458 | |
459 | typedef class ListLockBase<void*> ListLock; |
460 | typedef class ListLockEntryBase<void*> ListLockEntry; |
461 | |
462 | // Holds the lock of the ListLock |
463 | typedef ListLock::LockHolder ListLockHolder; |
464 | |
465 | // Holds the ownership of the lock element |
466 | typedef ReleaseHolder<ListLockEntry> ListLockEntryHolder; |
467 | |
468 | // Holds the lock of the lock element |
469 | typedef ListLockEntry::LockHolder ListLockEntryLockHolder; |
470 | |
471 | |
472 | #endif // LISTLOCK_H |
473 | |