| 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 | |