File: ReadWriteSpinLock.cs

package info (click to toggle)
mono 6.8.0.105%2Bdfsg-3.3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,284,512 kB
  • sloc: cs: 11,172,132; xml: 2,850,069; ansic: 671,653; cpp: 122,091; perl: 59,366; javascript: 30,841; asm: 22,168; makefile: 20,093; sh: 15,020; python: 4,827; pascal: 925; sql: 859; sed: 16; php: 1
file content (381 lines) | stat: -rw-r--r-- 14,555 bytes parent folder | download | duplicates (7)
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
//------------------------------------------------------------------------------
// <copyright file="ReadWriteSpinLock.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------


namespace System.Web.Util {

using System.Threading;
using System.Collections;
using System.Globalization;
using Microsoft.Win32;

struct ReadWriteSpinLock {
    // 
    // Fields
    // 

    //  _bits is layed out as follows:
    //
    //   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
    //   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    //  +-+-+---------------------------+--------------------------------+
    //  |S|W|          WriteLockCount   |           ReadLockCount        |
    //  +-+-+---------------------------+--------------------------------+
    //  where
    //
    //      S - sign bit (always zero) - By having a sign bit, operations 
    //      on the ReadLockCount can use InterlockedIncrement/Decrement
    //
    //      W - writer waiting bit - set by threads attempting write lock, preventing
    //          any further threads from acquiring read locks.  This attempts to hint
    //          that updates have priority, but doesn't guarantee priority.
    //
    //      WriteLockCount - Write lock recursion count
    //
    //      ReadLockCount - Read lock recursion count
    //
    int     _bits;
    int     _id;

    //
    // Statics
    //

    static bool s_disableBusyWaiting = (SystemInfo.GetNumProcessCPUs() == 1);

    // 
    // Constants
    // 

    const int BACK_OFF_FACTORS_LENGTH = 13;
    static readonly double [] s_backOffFactors = new double [BACK_OFF_FACTORS_LENGTH] {
        1.020, 0.965,  0.890, 1.065,
        1.025, 1.115,  0.940, 0.995,
        1.050, 1.080,  0.915, 0.980,
        1.010
    };

    const int WRITER_WAITING_MASK   = (int) 0x40000000;
    const int WRITE_COUNT_MASK      = (int) 0x3FFF0000;
    const int READ_COUNT_MASK       = (int) 0x0000FFFF;
    const int WRITER_WAITING_SHIFT  = 30;           
    const int WRITE_COUNT_SHIFT     = 16;           

    static bool WriterWaiting(int bits)             {return ((bits & WRITER_WAITING_MASK) != 0);}
    static int  WriteLockCount(int bits)            {return ((bits & WRITE_COUNT_MASK) >> WRITE_COUNT_SHIFT);}
    static int  ReadLockCount(int bits)             {return (bits & READ_COUNT_MASK);}
    static bool NoWriters(int bits)                 {return ((bits & WRITE_COUNT_MASK) == 0);}
    static bool NoWritersOrWaitingWriters(int bits) {return ((bits & (WRITE_COUNT_MASK | WRITER_WAITING_MASK)) == 0);}
    static bool NoLocks(int bits)                   {return ((bits & ~WRITER_WAITING_MASK) == 0);}

    bool        WriterWaiting()                     {return WriterWaiting(_bits);}             
    int         WriteLockCount()                    {return WriteLockCount(_bits);}            
    int         ReadLockCount()                     {return ReadLockCount(_bits);}             
    bool        NoWriters()                         {return NoWriters(_bits);}                 
    bool        NoWritersOrWaitingWriters()         {return NoWritersOrWaitingWriters(_bits);} 
    bool        NoLocks()                           {return NoLocks(_bits);}                   

    int CreateNewBits(bool writerWaiting, int writeCount, int readCount) {
        int bits = ((writeCount << WRITE_COUNT_SHIFT) | readCount);
        if (writerWaiting) {
            bits |= WRITER_WAITING_MASK;
        }

        return bits;
    }


    internal /*public*/ void AcquireReaderLock() {

        // This lock supports Writelock then Readlock 
        // from the same thread (possibly from different functions).
        int threadId = Thread.CurrentThread.GetHashCode();

        // Optimize for the common case by 
        if (_TryAcquireReaderLock(threadId))
            return;

        _Spin(true, threadId);
        Debug.Trace("Spinlock", "AcquireReaderLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
                    + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
    }


    internal /*public*/ void AcquireWriterLock() {

        int threadId = Thread.CurrentThread.GetHashCode();

        // Optimize for the common case by 
        if (_TryAcquireWriterLock(threadId))
            return;

        _Spin(false, threadId);

        Debug.Trace("Spinlock", "AcquireWriterLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
                    + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
    }


    internal /*public*/ void ReleaseReaderLock() {
#if DBG 
        int id = _id;
        Debug.Assert(id == 0 || id == Thread.CurrentThread.GetHashCode(), "id == 0 || id == Thread.CurrentThread.GetHashCode()");
#endif

        int n = Interlocked.Decrement(ref _bits);

        Debug.Assert(n >= 0, "n >= 0");
        Debug.Trace("Spinlock", "ReleaseReaderLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
                    + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
    }


    void AlterWriteCountHoldingWriterLock(int oldBits, int delta) {
        int readLockCount = ReadLockCount(oldBits);
        int oldWriteLockCount = WriteLockCount(oldBits);
        int newWriteLockCount = oldWriteLockCount + delta;
        Debug.Assert(newWriteLockCount >= 0, "newWriteLockCount >= 0");
        int newBits;
        int test;
    
        for (;;) {
            //
            // Since we own the lock, the only change that can be 
            // made by another thread to _bits is to add the writer-waiting bit.
            //
            Debug.Assert(WriteLockCount(oldBits) == oldWriteLockCount, "WriteLockCount(oldBits) == oldWriteLockCount");
            Debug.Assert(ReadLockCount(oldBits) == readLockCount, "ReadLockCount(oldBits) == readLockCount");
            newBits = CreateNewBits(WriterWaiting(oldBits), newWriteLockCount, readLockCount);
            test = Interlocked.CompareExchange(ref _bits, newBits, oldBits);
            if (test == oldBits) {
                break;
            }
    
            oldBits = test;
        }
    }
    
    internal /*public*/ void ReleaseWriterLock() {
#if DBG
        int id = _id;
        Debug.Assert(id == Thread.CurrentThread.GetHashCode(), "id == Thread.CurrentThread.GetHashCode()");
#endif
    
        int oldBits = _bits;
        int writeLockCount = WriteLockCount(oldBits);
        Debug.Assert(writeLockCount > 0, "writeLockCount > 0");
        if (writeLockCount == 1) {
            // Reset the id before releasing count so that
            // AcquireRead works correctly.
            _id = 0;
        }
    
        AlterWriteCountHoldingWriterLock(oldBits, -1);
        Debug.Trace("Spinlock", "ReleaseWriterLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture) 
                    + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
    }


    bool _TryAcquireWriterLock(int threadId) {
        int id = _id;
        int oldBits = _bits;
        int newBits;
        int test;

        if (id == threadId) {
            // we can just pound in the correct value
            AlterWriteCountHoldingWriterLock(oldBits, +1);
            return true;
        }

        if (id == 0 && NoLocks(oldBits)) {
            newBits = CreateNewBits(false, 1, 0);
            test = Interlocked.CompareExchange(ref _bits, newBits, oldBits);
            if (test == oldBits) {
                id = _id;
                Debug.Assert(id == 0);
                _id = threadId;

                return true;
            }

            oldBits = test;
        }

        // If there is contention, make sure the WRITER_WAITING bit is set.
        // Note: this blocks readers from using a value that is about to be changed
        if (!WriterWaiting(oldBits)) {
            // hammer on _bits until the bit is set
            for (;;) {
                newBits = (oldBits | WRITER_WAITING_MASK);
                test = Interlocked.CompareExchange(ref _bits, newBits, oldBits);
                if (test == oldBits)
                    break;

                oldBits = test;
            }
        }

        return false;
    }

    bool _TryAcquireReaderLock(int threadId) {
        int oldBits = _bits;
        int id = _id;

        if (id == 0) {
            if (!NoWriters(oldBits)) {
                return false;
            }
        }
        else if (id != threadId) {
            return false;
        }

        if (Interlocked.CompareExchange(ref _bits, oldBits + 1, oldBits) == oldBits) {
            return true;
        }

        return false;
    }



    /// <internalonly/>
    void _Spin(bool isReaderLock, int threadId) {

        const int LOCK_MAXIMUM_SPINS =      10000;    // maximum allowable spin count
        const int LOCK_DEFAULT_SPINS =       4000;    // default spin count
        const int LOCK_MINIMUM_SPINS =        100;    // minimum allowable spin count

        int sleepTime = 0;
        int baseSpins;

        {   // limit scope of temp. stack vars to calculation of baseSpin2

            // Alternatives for threadId include a static counter
            // or the low DWORD of QueryPerformanceCounter().
            double randomBackoffFactor = s_backOffFactors[Math.Abs(threadId) % BACK_OFF_FACTORS_LENGTH];
            baseSpins = (int)(LOCK_DEFAULT_SPINS * randomBackoffFactor);
            baseSpins = Math.Min(LOCK_MAXIMUM_SPINS, baseSpins);
            baseSpins = Math.Max(baseSpins, LOCK_MINIMUM_SPINS);
        }

        DateTime utcSpinStartTime = DateTime.UtcNow; // error if struct not initialized

        // hand-optimize loop: Increase locality by copying static variables 
        // onto the stack (this will reduce cache misses after a contact 
        // switch induced by Sleep()).
        bool disableBusyWaiting = s_disableBusyWaiting;

        for (;;) {
            if (isReaderLock) {
                if (_TryAcquireReaderLock(threadId)) {
                    break;
                }
            }
            else {
                if (_TryAcquireWriterLock(threadId)) {
                    break;
                }
            }

            // if 1 cpu, or cpu affinity is set to 1, spinning is a waste of time
            if (disableBusyWaiting) {

                Thread.Sleep(sleepTime);

                // Avoid priority inversion: 0, 1, 0, 1,...
                sleepTime ^= 1;
            }
            else {
                int spinCount = baseSpins;

                // Check no more than baseSpins times then yield.
                // It is important not to use the InterlockedExchange in the
                // inner loop in order to minimize system memory bus traffic.
                for(;;) {
                    //
                    // If the lock is available break spinning and 
                    // try to obtain it.
                    //
                    if (isReaderLock) {
                        if (NoWritersOrWaitingWriters()) {
                            break;
                        }
                    }
                    else {
                        if (NoLocks()) {
                            break;
                        }
                    }

                    if (--spinCount < 0) {

                        Thread.Sleep(sleepTime);

                        // Backoff algorithm: reduce (or increase) busy wait time
                        baseSpins /= 2;
                        // LOCK_MINIMUM_SPINS <= baseSpins <= LOCK_MAXIMUM_SPINS
                        //baseSpins = Math.Min(LOCK_MAXIMUM_SPINS, baseSpins); //= min(LOCK_MAXIMUM_SPINS, baseSpins)
                        baseSpins = Math.Max(baseSpins, LOCK_MINIMUM_SPINS); //= max(baseSpins, LOCK_MINIMUM_SPINS);
                        spinCount = baseSpins;

                        // Using Sleep(0) leads to the possibility of priority
                        // inversion.  Sleep(0) only yields the processor if
                        // there's another thread of the same priority that's
                        // ready to run.  If a high-priority thread is trying to
                        // acquire the lock, which is held by a low-priority
                        // thread, then the low-priority thread may never get
                        // scheduled and hence never free the lock.  NT attempts
                        // to avoid priority inversions by temporarily boosting
                        // the priority of low-priority runnable threads, but the
                        // problem can still occur if there's a medium-priority
                        // thread that's always runnable.  If Sleep(1) is used,
                        // then the thread unconditionally yields the CPU.  We
                        // only do this for the second and subsequent even
                        // iterations, since a millisecond is a long time to wait
                        // if the thread can be scheduled in again sooner
                        // (~100,000 instructions).
                        // Avoid priority inversion: 0, 1, 0, 1,...
                        sleepTime ^= 1;
                    }
                    else {
                        // kill about 20 clock cycles on this proc
                        Thread.SpinWait(10);
                    }
                }

            }
        }// while

    }// _Spin

} // ReadWriteSpinLock

} // namespace System.Web.Util

// NOTES:
//
// This ReaderWriterSpinlock is a combination of the 
// original lightweight (4 byte) System.Web.Util.ReadWriteSpinLock
// and the lightweight (4 byte) exclusive lock (SmallSpinLock) used 
// in the George Reilly's LKRHash (see http://georgere/work/lkrhash).
//
// In an effort to support reentrancy during writes we are squirreling 
// away the thread id of the thread holding the write lock into the upper 
// 16 bits of the lock count.  This is possible as long as thread ids stay
// smaller than 7FFF.  Anything higher than that would flip the sign bit
// and we'd no longer be able to do signed comparisons to check 
// for read vs. write.  
//
//          read            write
// lower    #read locks     #write locks (from same thread)
// higher   0x0000          thread id of thread holding lock
//
// Adapted from LKRHash's lock.cpp, from GeorgeRe
// The original implementation is due to PALarson.