summaryrefslogtreecommitdiff
path: root/src/vm/simplerwlock.hpp
blob: 7800b8d2a03ab0290f30443e617e474190b8dfc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//

// 

#ifndef _SimpleRWLock_hpp_
#define _SimpleRWLock_hpp_

#include "threads.h"

class SimpleRWLock;

//-------------------------------------------------------------------------------------------
// GC_MODE defines custom CONTRACTs for TryEnterRead and TryEnterWrite.
//
// Contract differs when acquiring the lock depending on its lock mode.
//
// GC/MODE
//     A SimpleRWLock can be one of the following modes. We only want to see the "PREEMPTIVE" 
//     type used in new code. Other types, kept for legacy reasons, are listed in
//     order from least objectionable to most objectionable.
//
//         PREEMPTIVE (equivalent to CRST's "normal")
//            This is the preferred type of crst. Enter() will force-switch your thread
//            into preemptive mode if it isn't already. Thus, the effective contract is:
//
//            MODE_ANY
//            GC_TRIGGERS
//
//
//
//         COOPERATIVE (equivalent to CRST_UNSAFE_COOPGC) 
//            You can only attempt to acquire this crst if you're already in coop mode. It is 
//            guaranteed no GC will occur while waiting to acquire the lock.  While you hold 
//            the lock, your thread is in a GCFORBID state.
//
//            MODE_COOP
//            GC_NOTRIGGER
//
//
//
//         COOPERATIVE_OR_PREEMPTIVE (equivalent to CRST_UNSAFE_ANYMODE) 
//            You can attempt to acquire this in either mode. Entering the crst will not change 
//            your thread mode but it will increment the GCNoTrigger count.
//
//            MODE_ANY
//            GC_NOTRIGGER
//------------------------------------------------------------------------------------------------
enum GC_MODE {
    COOPERATIVE,
    PREEMPTIVE,
    COOPERATIVE_OR_PREEMPTIVE} ;

class SimpleRWLock
{
    // Allow Module access so we can use Offsetof on this class's private members during native image creation (determinism)
    friend class Module;
private:
    BOOL IsWriterWaiting()
    {
        LIMITED_METHOD_CONTRACT;
        return m_WriterWaiting != 0;
    }

    void SetWriterWaiting()
    {
        LIMITED_METHOD_CONTRACT;
        m_WriterWaiting = 1;
    }

    void ResetWriterWaiting()
    {
        LIMITED_METHOD_CONTRACT;
        m_WriterWaiting = 0;
    }

    BOOL TryEnterRead();

    BOOL TryEnterWrite();

#ifdef ENABLE_CONTRACTS_IMPL
    void CheckGCNoTrigger();
#endif  //ENABLE_CONTRACTS_IMPL

    // lock used for R/W synchronization
    Volatile<LONG>                m_RWLock;     

    // Does this lock require to be taken in PreemptiveGC mode?
    const GC_MODE          m_gcMode;

    // spin count for a reader waiting for a writer to release the lock
    LONG                m_spinCount;

    // used to prevent writers from being starved by readers
    // we currently do not prevent writers from starving readers since writers 
    // are supposed to be rare.
    BOOL                m_WriterWaiting;

#ifdef _DEBUG
    // Check for dead lock situation.
    Volatile<LONG>      m_countNoTriggerGC;

#ifdef _WIN64
    // ensures that we are a multiple of 8-bytes
    UINT32 pad;      
#endif

    void                PostEnter ();
    void                PreEnter ();
    void                PreLeave ();
#endif //_DEBUG

#ifndef DACCESS_COMPILE
    static void AcquireReadLock(SimpleRWLock *s) { LIMITED_METHOD_CONTRACT; s->EnterRead(); }
    static void ReleaseReadLock(SimpleRWLock *s) { LIMITED_METHOD_CONTRACT; s->LeaveRead(); }

    static void AcquireWriteLock(SimpleRWLock *s) { LIMITED_METHOD_CONTRACT; s->EnterWrite(); }
    static void ReleaseWriteLock(SimpleRWLock *s) { LIMITED_METHOD_CONTRACT; s->LeaveWrite(); }
#else // DACCESS_COMPILE
    // in DAC builds, we don't actually acquire the lock, we just determine whether the LS
    // already holds it. If so, we assume the data is inconsistent and throw an exception. 
    // Argument: 
    //     input: s - the lock to be checked. 
    // Note: Throws
    static void AcquireReadLock(SimpleRWLock *s) 
    {
        SUPPORTS_DAC;
        if (s->IsWriterLock()) 
        {
            ThrowHR(CORDBG_E_PROCESS_NOT_SYNCHRONIZED); 
        }
    };
    static void ReleaseReadLock(SimpleRWLock *s) { };

    static void AcquireWriteLock(SimpleRWLock *s) { SUPPORTS_DAC; ThrowHR(CORDBG_E_TARGET_READONLY); };
    static void ReleaseWriteLock(SimpleRWLock *s) { };
#endif // DACCESS_COMPILE

public:
    SimpleRWLock (GC_MODE gcMode, LOCK_TYPE locktype)
        : m_gcMode (gcMode)
    {
        CONTRACTL {
            NOTHROW;
            GC_NOTRIGGER;
        } CONTRACTL_END;

        m_RWLock = 0;
        m_spinCount = (GetCurrentProcessCpuCount() == 1) ? 0 : 4000;
        m_WriterWaiting = FALSE;

#ifdef _DEBUG
        m_countNoTriggerGC = 0;
#endif
    }

    // Special empty CTOR for DAC. We still need to assign to const fields, but they won't actually be used.
    SimpleRWLock()  
        : m_gcMode(COOPERATIVE_OR_PREEMPTIVE)
    {
        LIMITED_METHOD_CONTRACT;

#ifdef _DEBUG
        m_countNoTriggerGC = 0;
#endif //_DEBUG
    }
    
#ifndef DACCESS_COMPILE
    // Acquire the reader lock.
    void EnterRead();

    // Acquire the writer lock.
    void EnterWrite();

    // Leave the reader lock.
    void LeaveRead()
    {
        LIMITED_METHOD_CONTRACT;
#ifdef _DEBUG
        PreLeave ();
#endif //_DEBUG
        LONG RWLock;
        RWLock = InterlockedDecrement(&m_RWLock);
        _ASSERTE (RWLock >= 0);
        DECTHREADLOCKCOUNT();
        EE_LOCK_RELEASED(this);
    }

    // Leave the writer lock.
    void LeaveWrite()
    {
        LIMITED_METHOD_CONTRACT;        
#ifdef _DEBUG
        PreLeave ();
#endif //_DEBUG
        LONG RWLock;
        RWLock = InterlockedExchange (&m_RWLock, 0);
        _ASSERTE(RWLock == -1);
        DECTHREADLOCKCOUNT();
        EE_LOCK_RELEASED(this);
    }

#endif // DACCESS_COMPILE

    typedef DacHolder<SimpleRWLock *, SimpleRWLock::AcquireReadLock, SimpleRWLock::ReleaseReadLock> SimpleReadLockHolder;
    typedef DacHolder<SimpleRWLock *, SimpleRWLock::AcquireWriteLock, SimpleRWLock::ReleaseWriteLock> SimpleWriteLockHolder;

#ifdef _DEBUG
    BOOL LockTaken ()
    {
        LIMITED_METHOD_CONTRACT;
        return m_RWLock != 0;
    }

    BOOL IsReaderLock ()
    {
        LIMITED_METHOD_CONTRACT;
        return m_RWLock > 0;
    }

#endif  

    BOOL IsWriterLock ()
    {
        LIMITED_METHOD_DAC_CONTRACT;
        return m_RWLock < 0;
    }
    
};

typedef SimpleRWLock::SimpleReadLockHolder SimpleReadLockHolder;
typedef SimpleRWLock::SimpleWriteLockHolder SimpleWriteLockHolder;
typedef DPTR(SimpleRWLock) PTR_SimpleRWLock;

#ifdef TEST_DATA_CONSISTENCY
// used for test purposes. Determines if a crst is held. 
// Arguments:
//     input: pLock - the lock to test
// Note: Throws if the lock is held    

FORCEINLINE void DebugTryRWLock(SimpleRWLock * pLock)
{
    SUPPORTS_DAC;

    SimpleReadLockHolder rwLock(pLock);
}
#endif // TEST_DATA_CONSISTENCY
#endif