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: Canary.h |
6 | // |
7 | |
8 | // |
9 | // Header file Debugger Canary |
10 | // |
11 | //***************************************************************************** |
12 | |
13 | #ifndef CANARY_H |
14 | #define CANARY_H |
15 | |
16 | //----------------------------------------------------------------------------- |
17 | // Canary. |
18 | // |
19 | // The helper thread needs to be very careful about what locks it takes. If it takes a lock |
20 | // held by a suspended thread, then the whole process deadlocks (Since the suspended thread |
21 | // is waiting for the helper to resume it). |
22 | // In general, we try to avoid having the helper take such locks, but the problem is unsolvable |
23 | // because: |
24 | // - we don't know what that set of locks are (eg, OS apis may take new locks between versions) |
25 | // - the helper may call into the EE and that takes unsafe locks. |
26 | // The most prominent dangerous lock is the heap lock, which is why we have the "InteropSafe" heap. |
27 | // Since we don't even know what locks are bad (eg, we can't actually find the Heaplock), we can't |
28 | // explicitly check if the lock is safe to take. |
29 | // So we spin up an auxiallary "Canary" thread which can sniff for locks that the helper thread will |
30 | // need to take. Thus the helper thread can find out if the locks are available without actually taking them. |
31 | // The "Canary" can call APIs that take the locks (such as regular "new" for the process heap lock). |
32 | // The helper will wait on the canary with timeout. If the canary returns, the helper knows it's |
33 | // safe to take the locks. If the canary times out, then the helper assumes it's blocked on the |
34 | // locks and thus not safe for the helper to take them. |
35 | //----------------------------------------------------------------------------- |
36 | class HelperCanary |
37 | { |
38 | public: |
39 | HelperCanary(); |
40 | ~HelperCanary(); |
41 | |
42 | void Init(); |
43 | bool AreLocksAvailable(); |
44 | void ClearCache(); |
45 | |
46 | protected: |
47 | static DWORD WINAPI ThreadProc(LPVOID param); |
48 | void ThreadProc(); |
49 | void TakeLocks(); |
50 | bool AreLocksAvailableWorker(); |
51 | |
52 | // Flag to tell Canary thread to exit. |
53 | bool m_fStop; |
54 | |
55 | // Flag to indicate Init has been run |
56 | bool m_initialized; |
57 | |
58 | // Cache the answers between stops so that we don't have to ping the canary every time. |
59 | bool m_fCachedValid; |
60 | bool m_fCachedAnswer; |
61 | |
62 | HANDLE m_hCanaryThread; // handle for canary thread |
63 | DWORD m_CanaryThreadId; // canary thread OS Thread ID |
64 | |
65 | // These counters are read + written by both helper and canary thread. |
66 | // These need to be volatile because of how they're being accessed from different threads. |
67 | // However, since each is only read from 1 thread, and written by another, and the WFSO/SetEvent |
68 | // will give us a memory barrier, and we have a flexible polling operation, volatile is |
69 | // sufficient to deal with memory barrier issues. |
70 | Volatile<DWORD> m_RequestCounter; |
71 | Volatile<DWORD> m_AnswerCounter; |
72 | HandleHolder m_hPingEvent; |
73 | |
74 | // We use a Manual wait event to replace Sleep. |
75 | HandleHolder m_hWaitEvent; |
76 | }; |
77 | |
78 | |
79 | #endif // CANARY_H |
80 | |
81 | |