{"action":"created","comment":{"url":"https://api.github.com/repos/dotnet/corefx/pulls/comments/86109165","pull_request_review_id":6783125,"id":86109165,"diff_hunk":"@@ -0,0 +1,1339 @@\n+// Licensed to the .NET Foundation under one or more agreements.\n+// The .NET Foundation licenses this file to you under the MIT license.\n+// See the LICENSE file in the project root for more information.\n+\n+using System.Diagnostics;\n+using System.Runtime.ConstrainedExecution;\n+\n+namespace System.Threading\n+{\n+    public sealed class ReaderWriterLock : CriticalFinalizerObject\n+    {\n+        private const int InvalidThreadID = -1;\n+        private const ushort MaxAcquireCount = ushort.MaxValue;\n+        private static readonly int DefaultSpinCount = Environment.ProcessorCount != 1 ? 500 : 0;\n+\n+        private static long s_mostRecentLockID;\n+\n+        private ManualResetEvent _readerEvent;\n+        private AutoResetEvent _writerEvent;\n+        private long _lockID;\n+        private volatile int _state;\n+        private int _writerID = InvalidThreadID;\n+        private int _writerSeqNum;\n+        private ushort _writerLevel;\n+\n+#if RWLOCK_STATISTICS\n+        private int _readerEntryCount;\n+        private int _readerContentionCount;\n+        private int _writerEntryCount;\n+        private int _writerContentionCount;\n+        private int _eventsReleasedCount;\n+#endif // RWLOCK_STATISTICS\n+\n+        public ReaderWriterLock()\n+        {\n+            _lockID = Interlocked.Increment(ref s_mostRecentLockID);\n+            GC.SuppressFinalize(this);\n+        }\n+\n+        ~ReaderWriterLock()\n+        {\n+        }\n+\n+        public bool IsReaderLockHeld\n+        {\n+            get\n+            {\n+                LockEntry lockEntry = LockEntry.GetCurrent(_lockID);\n+                if (lockEntry != null)\n+                {\n+                    return lockEntry._readerLevel != 0;\n+                }\n+                return false;\n+            }\n+        }\n+\n+        public bool IsWriterLockHeld\n+        {\n+            get\n+            {\n+                return _writerID == GetCurrentThreadID();\n+            }\n+        }\n+\n+        public int WriterSeqNum\n+        {\n+            get\n+            {\n+                return _writerSeqNum;\n+            }\n+        }\n+\n+        public bool AnyWritersSince(int seqNum)\n+        {\n+            if (_writerID == GetCurrentThreadID())\n+            {\n+                ++seqNum;\n+            }\n+            return (uint)_writerSeqNum > (uint)seqNum;\n+        }\n+\n+        public void AcquireReaderLock(int millisecondsTimeout)\n+        {\n+            if (millisecondsTimeout < -1)\n+            {\n+                throw GetInvalidTimeoutException(nameof(millisecondsTimeout));\n+            }\n+\n+            LockEntry lockEntry = LockEntry.GetOrCreateCurrent(_lockID);\n+\n+            // Check for the fast path\n+            if (Interlocked.CompareExchange(ref _state, LockStates.Reader, 0) == 0)\n+            {\n+                Debug.Assert(lockEntry._readerLevel == 0);\n+            }\n+            // Check for nested reader\n+            else if (lockEntry._readerLevel != 0)\n+            {\n+                Debug.Assert((_state & LockStates.ReadersMask) != 0);\n+\n+                if (lockEntry._readerLevel == MaxAcquireCount)\n+                {\n+                    throw new OverflowException(SR.Overflow_UInt16);\n+                }\n+                ++lockEntry._readerLevel;\n+                return;\n+            }\n+            // Check if the thread already has writer lock\n+            else if (_writerID == GetCurrentThreadID())\n+            {\n+                AcquireWriterLock(millisecondsTimeout);\n+                Debug.Assert(lockEntry.IsEmpty);\n+                return;\n+            }\n+            else\n+            {\n+                int spinCount = 0;\n+                int currentState = _state;\n+                do\n+                {\n+                    int knownState = currentState;\n+\n+                    // Reader need not wait if there are only readers and no writer\n+                    if (knownState < LockStates.ReadersMask ||\n+                        (\n+                            (knownState & LockStates.ReaderSignaled) != 0 &&\n+                            (knownState & LockStates.Writer) == 0 &&\n+                            (\n+                                // A waiting reader, after successfully completing the wait, expects that it can become a\n+                                // reader, so ensure that there is enough room for waiting readers and this potential reader.\n+                                (\n+                                    (knownState & LockStates.ReadersMask) +\n+                                    ((knownState & LockStates.WaitingReadersMask) >> LockStates.WaitingReadersShift)\n+                                ) <= LockStates.ReadersMask - 2\n+                            )\n+                        ))\n+                    {\n+                        // Add to readers\n+                        currentState = Interlocked.CompareExchange(ref _state, knownState + LockStates.Reader, knownState);\n+                        if (currentState == knownState)\n+                        {\n+                            // One more reader\n+                            break;\n+                        }\n+                        continue;\n+                    }\n+\n+                    // Check for too many readers or waiting readers, or if signaling is in progress\n+                    if ((knownState & LockStates.ReadersMask) == LockStates.ReadersMask ||\n+                        (knownState & LockStates.WaitingReadersMask) == LockStates.WaitingReadersMask ||\n+                        (knownState & LockStates.CachingEvents) == LockStates.ReaderSignaled)\n+                    {\n+                        // Sleep for a while, then update to the latest state and try again\n+                        Helpers.Sleep(1000);\n+                        spinCount = 0;\n+                        currentState = _state;\n+                        continue;\n+                    }\n+\n+                    ++spinCount;\n+\n+                    // Check if events are being cached. The purpose of this check is that \"caching\" events could involve\n+                    // disposing one or both of {_readerEvent, _writerEvent}. This check prevents the waiting code below from\n+                    // trying to use these events during this dangerous time, and instead causes the loop to spin until the\n+                    // caching state is cleared and events can be recreated. See ReleaseEvents() and callers.\n+                    if ((knownState & LockStates.CachingEvents) == LockStates.CachingEvents)\n+                    {\n+                        if (spinCount > DefaultSpinCount)\n+                        {\n+                            Helpers.Sleep(1);\n+                            spinCount = 0;\n+                        }\n+                        currentState = _state;\n+                        continue;\n+                    }\n+\n+                    // Check spin count\n+                    if (spinCount <= DefaultSpinCount)\n+                    {\n+                        currentState = _state;\n+                        continue;\n+                    }\n+\n+                    // Add to waiting readers\n+                    currentState = Interlocked.CompareExchange(ref _state, knownState + LockStates.WaitingReader, knownState);\n+                    if (currentState != knownState)\n+                    {\n+                        continue;\n+                    }\n+\n+#if RWLOCK_STATISTICS\n+                    Interlocked.Increment(ref _readerContentionCount);\n+#endif\n+\n+                    int modifyState = -LockStates.WaitingReader;\n+                    ManualResetEvent readerEvent = null;\n+                    bool waitSucceeded = false;\n+                    try\n+                    {\n+                        readerEvent = GetOrCreateReaderEvent();\n+                        waitSucceeded = readerEvent.WaitOne(millisecondsTimeout);\n+\n+                        // AcquireReaderLock cannot have reentry via pumping while waiting for readerEvent, so\n+                        // lockEntry's state should not change from underneath us\n+                        Debug.Assert(lockEntry.HasLockID(_lockID));\n+\n+                        if (waitSucceeded)\n+                        {\n+                            // Become a reader\n+                            Debug.Assert((_state & LockStates.ReaderSignaled) != 0);\n+                            Debug.Assert((_state & LockStates.ReadersMask) < LockStates.ReadersMask);\n+                            modifyState += LockStates.Reader;\n+                        }\n+                    }\n+                    finally\n+                    {\n+                        // Make the state changes determined above\n+                        knownState = Interlocked.Add(ref _state, modifyState) - modifyState;\n+\n+                        if (!waitSucceeded)\n+                        {\n+                            // Check for last signaled waiting reader\n+                            if ((knownState & LockStates.ReaderSignaled) != 0 &&\n+                                (knownState & LockStates.WaitingReadersMask) == LockStates.WaitingReader)\n+                            {\n+                                if (readerEvent == null)\n+                                {\n+                                    readerEvent = _readerEvent;\n+                                    Debug.Assert(readerEvent != null);\n+                                }\n+\n+                                // Ensure the event is signalled before resetting it, since the ReaderSignaled state is set\n+                                // before the event is set.\n+                                readerEvent.WaitOne();\n+                                Debug.Assert((_state & LockStates.ReadersMask) < LockStates.ReadersMask);\n+\n+                                // Reset the event and lower reader signaled flag\n+                                readerEvent.Reset();\n+                                Interlocked.Add(ref _state, LockStates.Reader - LockStates.ReaderSignaled);\n+\n+                                // Honor the orginal status\n+                                ++lockEntry._readerLevel;\n+                                ReleaseReaderLock();\n+                            }\n+\n+                            Debug.Assert(lockEntry.IsEmpty);\n+                        }\n+                    }\n+\n+                    if (!waitSucceeded)\n+                    {\n+                        throw GetTimeoutException();\n+                    }\n+\n+                    // Check for last signaled waiting reader\n+                    Debug.Assert((knownState & LockStates.ReaderSignaled) != 0);\n+                    Debug.Assert((knownState & LockStates.ReadersMask) < LockStates.ReadersMask);\n+                    if ((knownState & LockStates.WaitingReadersMask) == LockStates.WaitingReader)\n+                    {\n+                        // Reset the event and the reader signaled flag\n+                        readerEvent.Reset();\n+                        Interlocked.Add(ref _state, -LockStates.ReaderSignaled);\n+                    }\n+\n+                    break;\n+                } while (YieldProcessor());\n+            }\n+\n+            // Success\n+            Debug.Assert((_state & LockStates.Writer) == 0);\n+            Debug.Assert((_state & LockStates.ReadersMask) != 0);\n+            ++lockEntry._readerLevel;\n+#if RWLOCK_STATISTICS\n+            Interlocked.Increment(ref _readerEntryCount);\n+#endif\n+        }\n+\n+        public void AcquireReaderLock(TimeSpan timeout) => AcquireReaderLock(ToTimeoutMilliseconds(timeout));\n+\n+        public void AcquireWriterLock(int millisecondsTimeout)\n+        {\n+            if (millisecondsTimeout < -1)\n+            {\n+                throw GetInvalidTimeoutException(nameof(millisecondsTimeout));\n+            }\n+\n+            int threadID = GetCurrentThreadID();\n+\n+            // Check for the fast path\n+            if (Interlocked.CompareExchange(ref _state, LockStates.Writer, 0) == 0)\n+            {\n+                Debug.Assert((_state & LockStates.ReadersMask) == 0);\n+            }\n+            // Check if the thread already has writer lock\n+            else if (_writerID == threadID)\n+            {\n+                if (_writerLevel == MaxAcquireCount)\n+                {\n+                    throw new OverflowException(SR.Overflow_UInt16);\n+                }\n+                ++_writerLevel;\n+                return;\n+            }\n+            else\n+            {\n+                int spinCount = 0;\n+                int currentState = _state;\n+                do\n+                {\n+                    int knownState = currentState;\n+\n+                    // Writer need not wait if there are no readers and writer\n+                    if (knownState == 0 || knownState == LockStates.CachingEvents)\n+                    {\n+                        // Can be a writer\n+                        currentState = Interlocked.CompareExchange(ref _state, knownState + LockStates.Writer, knownState);\n+                        if (currentState == knownState)\n+                        {\n+                            // Only writer\n+                            break;\n+                        }\n+                        continue;\n+                    }\n+\n+                    // Check for too many waiting writers\n+                    if ((knownState & LockStates.WaitingWritersMask) == LockStates.WaitingWritersMask)\n+                    {\n+                        Helpers.Sleep(1000);\n+                        spinCount = 0;\n+                        currentState = _state;\n+                        continue;\n+                    }\n+\n+                    ++spinCount;\n+\n+                    // Check if events are being cached. The purpose of this check is that \"caching\" events could involve\n+                    // disposing one or both of {_readerEvent, _writerEvent}. This check prevents the waiting code below from\n+                    // trying to use these events during this dangerous time, and instead causes the loop to spin until the\n+                    // caching state is cleared and events can be recreated. See ReleaseEvents() and callers.\n+                    if ((knownState & LockStates.CachingEvents) == LockStates.CachingEvents)\n+                    {\n+                        if (spinCount > DefaultSpinCount)\n+                        {\n+                            Helpers.Sleep(1);\n+                            spinCount = 0;\n+                        }\n+                        currentState = _state;\n+                        continue;\n+                    }\n+\n+                    // Check spin count\n+                    if (spinCount <= DefaultSpinCount)\n+                    {\n+                        currentState = _state;\n+                        continue;\n+                    }\n+\n+                    // Add to waiting writers\n+                    currentState = Interlocked.CompareExchange(ref _state, knownState + LockStates.WaitingWriter, knownState);\n+                    if (currentState != knownState)\n+                    {\n+                        continue;\n+                    }\n+\n+#if RWLOCK_STATISTICS\n+                    Interlocked.Increment(ref _writerContentionCount);\n+#endif\n+\n+                    int modifyState = -LockStates.WaitingWriter;\n+                    AutoResetEvent writerEvent = null;\n+                    bool waitSucceeded = false;\n+                    try\n+                    {\n+                        writerEvent = GetOrCreateWriterEvent();\n+                        waitSucceeded = writerEvent.WaitOne(millisecondsTimeout);\n+\n+                        if (waitSucceeded)\n+                        {\n+                            // Become a writer and remove the writer-signaled state\n+                            Debug.Assert((_state & LockStates.WriterSignaled) != 0);\n+                            modifyState += LockStates.Writer - LockStates.WriterSignaled;\n+                        }\n+                    }\n+                    finally\n+                    {\n+                        // Make the state changes determined above\n+                        knownState = Interlocked.Add(ref _state, modifyState) - modifyState;\n+\n+                        if (!waitSucceeded &&\n+                            (knownState & LockStates.WriterSignaled) != 0 &&\n+                            (knownState & LockStates.WaitingWritersMask) == LockStates.WaitingWriter)\n+                        {\n+                            if (writerEvent == null)\n+                            {\n+                                writerEvent = _writerEvent;\n+                                Debug.Assert(writerEvent != null);\n+                            }\n+\n+                            while (true)\n+                            {\n+                                knownState = _state;\n+                                if ((knownState & LockStates.WriterSignaled) == 0 ||\n+                                    (knownState & LockStates.WaitingWritersMask) != 0)\n+                                {\n+                                    break;\n+                                }\n+\n+                                if (!writerEvent.WaitOne(10))\n+                                {\n+                                    continue;\n+                                }\n+\n+                                modifyState = LockStates.Writer - LockStates.WriterSignaled;\n+                                knownState = Interlocked.Add(ref _state, modifyState) - modifyState;\n+                                Debug.Assert((knownState & LockStates.WriterSignaled) != 0);\n+                                Debug.Assert((knownState & LockStates.Writer) == 0);\n+\n+                                // Honor the orginal status\n+                                _writerID = threadID;\n+                                Debug.Assert(_writerLevel == 0);\n+                                _writerLevel = 1;\n+                                ReleaseWriterLock();\n+                                break;\n+                            }\n+                        }\n+                    }\n+\n+                    if (!waitSucceeded)\n+                    {\n+                        throw GetTimeoutException();\n+                    }\n+                    break;\n+                } while (YieldProcessor());\n+            }\n+\n+            // Success\n+            Debug.Assert((_state & LockStates.Writer) != 0);\n+            Debug.Assert((_state & LockStates.ReadersMask) == 0);\n+            Debug.Assert(_writerID == InvalidThreadID);\n+\n+            // Save threadid of the writer\n+            _writerID = threadID;\n+            _writerLevel = 1;\n+            ++_writerSeqNum;\n+#if RWLOCK_STATISTICS\n+            ++_writerEntryCount;\n+#endif\n+            return;\n+        }\n+\n+        public void AcquireWriterLock(TimeSpan timeout) => AcquireWriterLock(ToTimeoutMilliseconds(timeout));\n+\n+        public void ReleaseReaderLock()\n+        {\n+            // Check if the thread has writer lock\n+            if (_writerID == GetCurrentThreadID())\n+            {\n+                ReleaseWriterLock();\n+                return;\n+            }\n+\n+            LockEntry lockEntry = LockEntry.GetCurrent(_lockID);\n+            if (lockEntry == null)\n+            {\n+                throw GetNotOwnerException();\n+            }\n+\n+            Debug.Assert((_state & LockStates.Writer) == 0);\n+            Debug.Assert((_state & LockStates.ReadersMask) != 0);\n+            Debug.Assert(lockEntry._readerLevel != 0);\n+\n+            --lockEntry._readerLevel;\n+            if (lockEntry._readerLevel != 0)\n+            {\n+                return;\n+            }\n+\n+            // Not a reader any more\n+            bool isLastReader;\n+            bool cacheEvents;\n+            AutoResetEvent writerEvent = null;\n+            ManualResetEvent readerEvent = null;\n+            int currentState = _state;\n+            int knownState;\n+            do\n+            {\n+                isLastReader = false;\n+                cacheEvents = false;\n+                knownState = currentState;\n+                int modifyState = -LockStates.Reader;\n+\n+                if ((knownState & (LockStates.ReadersMask | LockStates.ReaderSignaled)) == LockStates.Reader)\n+                {\n+                    isLastReader = true;\n+                    if ((knownState & LockStates.WaitingWritersMask) != 0)\n+                    {\n+                        try\n+                        {\n+                            writerEvent = GetOrCreateWriterEvent();\n+                        }\n+                        catch (SystemException)\n+                        {\n+                            Helpers.Sleep(100);\n+                            currentState = _state;\n+                            knownState = 0;\n+                            Debug.Assert(currentState != knownState);\n+                            continue;\n+                        }\n+                        modifyState += LockStates.WriterSignaled;\n+                    }\n+                    else if ((knownState & LockStates.WaitingReadersMask) != 0)\n+                    {\n+                        try\n+                        {\n+                            readerEvent = GetOrCreateReaderEvent();\n+                        }\n+                        catch (SystemException)\n+                        {\n+                            Helpers.Sleep(100);\n+                            currentState = _state;\n+                            knownState = 0;\n+                            Debug.Assert(currentState != knownState);\n+                            continue;\n+                        }\n+                        modifyState += LockStates.ReaderSignaled;\n+                    }\n+                    else if (knownState == LockStates.Reader && (_readerEvent != null || _writerEvent != null))\n+                    {\n+                        cacheEvents = true;\n+                        modifyState += LockStates.CachingEvents;\n+                    }\n+                }\n+\n+                Debug.Assert((knownState & LockStates.Writer) == 0);\n+                Debug.Assert((knownState & LockStates.ReadersMask) != 0);\n+                currentState = Interlocked.CompareExchange(ref _state, knownState + modifyState, knownState);\n+            } while (currentState != knownState);\n+\n+            // Check for last reader\n+            if (isLastReader)\n+            {\n+                // Check for waiting writers\n+                if ((knownState & LockStates.WaitingWritersMask) != 0)\n+                {\n+                    Debug.Assert((_state & LockStates.WriterSignaled) != 0);\n+                    Debug.Assert(writerEvent != null);\n+                    writerEvent.Set();\n+                }\n+                // Check for waiting readers\n+                else if ((knownState & LockStates.WaitingReadersMask) != 0)\n+                {\n+                    Debug.Assert((_state & LockStates.ReaderSignaled) != 0);\n+                    Debug.Assert(readerEvent != null);\n+                    readerEvent.Set();\n+                }\n+                // Check for the need to release events\n+                else if (cacheEvents)\n+                {\n+                    ReleaseEvents();\n+                }\n+            }\n+\n+            Debug.Assert(lockEntry.IsEmpty);\n+        }\n+\n+        public void ReleaseWriterLock()\n+        {\n+            if (_writerID != GetCurrentThreadID())\n+            {\n+                throw GetNotOwnerException();\n+            }\n+\n+            Debug.Assert((_state & LockStates.ReadersMask) == 0);\n+            Debug.Assert((_state & LockStates.Writer) != 0);\n+            Debug.Assert(_writerLevel != 0);\n+\n+            // Check for nested release\n+            --_writerLevel;\n+            if (_writerLevel != 0)\n+            {\n+                return;\n+            }\n+\n+            // Not a writer any more\n+            _writerID = InvalidThreadID;\n+            bool cacheEvents;\n+            ManualResetEvent readerEvent = null;\n+            AutoResetEvent writerEvent = null;\n+            int currentState = _state;\n+            int knownState;\n+            do\n+            {\n+                cacheEvents = false;\n+                knownState = currentState;\n+                int modifyState = -LockStates.Writer;\n+\n+                if ((knownState & LockStates.WaitingReadersMask) != 0)\n+                {\n+                    try\n+                    {\n+                        readerEvent = GetOrCreateReaderEvent();\n+                    }\n+                    catch (SystemException)\n+                    {\n+                        Helpers.Sleep(100);\n+                        currentState = _state;\n+                        knownState = 0;\n+                        Debug.Assert(currentState != knownState);\n+                        continue;\n+                    }\n+                    modifyState += LockStates.ReaderSignaled;\n+                }\n+                else if ((knownState & LockStates.WaitingWritersMask) != 0)\n+                {\n+                    try\n+                    {\n+                        writerEvent = GetOrCreateWriterEvent();\n+                    }\n+                    catch (SystemException)\n+                    {\n+                        Helpers.Sleep(100);\n+                        currentState = _state;\n+                        knownState = 0;\n+                        Debug.Assert(currentState != knownState);\n+                        continue;\n+                    }\n+                    modifyState += LockStates.WriterSignaled;\n+                }\n+                else if (knownState == LockStates.Writer && (_readerEvent != null || _writerEvent != null))\n+                {\n+                    cacheEvents = true;\n+                    modifyState += LockStates.CachingEvents;\n+                }\n+\n+                Debug.Assert((knownState & LockStates.ReadersMask) == 0);\n+                Debug.Assert((knownState & LockStates.Writer) != 0);\n+                currentState = Interlocked.CompareExchange(ref _state, knownState + modifyState, knownState);\n+            } while (currentState != knownState);\n+\n+            // Check for waiting readers\n+            if ((knownState & LockStates.WaitingReadersMask) != 0)\n+            {\n+                Debug.Assert((_state & LockStates.ReaderSignaled) != 0);\n+                Debug.Assert(readerEvent != null);\n+                readerEvent.Set();\n+            }\n+            // Check for waiting writers\n+            else if ((knownState & LockStates.WaitingWritersMask) != 0)\n+            {\n+                Debug.Assert((_state & LockStates.WriterSignaled) != 0);\n+                Debug.Assert(writerEvent != null);\n+                writerEvent.Set();\n+            }\n+            // Check for the need to release events\n+            else if (cacheEvents)\n+            {\n+                ReleaseEvents();\n+            }\n+        }\n+\n+        public LockCookie UpgradeToWriterLock(int millisecondsTimeout)\n+        {\n+            if (millisecondsTimeout < -1)\n+            {\n+                throw GetInvalidTimeoutException(nameof(millisecondsTimeout));\n+            }\n+\n+            var lockCookie = new LockCookie();\n+            int threadID = GetCurrentThreadID();\n+            lockCookie._threadID = threadID;\n+\n+            // Check if the thread is already a writer\n+            if (_writerID == threadID)\n+            {\n+                // Update cookie state\n+                lockCookie._flags = LockCookieFlags.Upgrade | LockCookieFlags.OwnedWriter;\n+                lockCookie._writerLevel = _writerLevel;\n+\n+                // Acquire the writer lock again\n+                AcquireWriterLock(millisecondsTimeout);\n+                return lockCookie;\n+            }\n+\n+            LockEntry lockEntry = LockEntry.GetCurrent(_lockID);\n+            if (lockEntry == null)\n+            {\n+                lockCookie._flags = LockCookieFlags.Upgrade | LockCookieFlags.OwnedNone;\n+            }\n+            else\n+            {\n+                // Sanity check\n+                Debug.Assert((_state & LockStates.ReadersMask) != 0);\n+                Debug.Assert(lockEntry._readerLevel != 0);\n+\n+                // Save lock state in the cookie\n+                lockCookie._flags = LockCookieFlags.Upgrade | LockCookieFlags.OwnedReader;\n+                lockCookie._readerLevel = lockEntry._readerLevel;\n+\n+                // If there is only one reader, try to convert reader to a writer\n+                int knownState = Interlocked.CompareExchange(ref _state, LockStates.Writer, LockStates.Reader);\n+                if (knownState == LockStates.Reader)\n+                {\n+                    // Thread is no longer a reader\n+                    lockEntry._readerLevel = 0;\n+                    Debug.Assert(lockEntry.IsEmpty);\n+\n+                    // Thread is a writer\n+                    _writerID = threadID;\n+                    _writerLevel = 1;\n+                    ++_writerSeqNum;\n+\n+                    // No intevening writes\n+#if RWLOCK_STATISTICS\n+                    ++_writerEntryCount;\n+#endif\n+                    return lockCookie;\n+                }\n+\n+                // Release the reader lock\n+                lockEntry._readerLevel = 1;\n+                ReleaseReaderLock();\n+            }\n+\n+            // We are aware of the contention on the lock and the thread will most probably block to acquire writer lock\n+            bool acquired = false;\n+            try\n+            {\n+                AcquireWriterLock(millisecondsTimeout);\n+                acquired = true;\n+                return lockCookie;\n+            }\n+            finally\n+            {\n+                if (!acquired)\n+                {\n+                    // Invalidate cookie\n+                    LockCookieFlags flags = lockCookie._flags;\n+                    lockCookie._flags = LockCookieFlags.Invalid;\n+\n+                    RecoverLock(ref lockCookie, flags & LockCookieFlags.OwnedReader);\n+                }\n+            }\n+        }\n+\n+        public LockCookie UpgradeToWriterLock(TimeSpan timeout) => UpgradeToWriterLock(ToTimeoutMilliseconds(timeout));\n+\n+        public void DowngradeFromWriterLock(ref LockCookie lockCookie)\n+        {\n+            int threadID = GetCurrentThreadID();\n+            if (_writerID != threadID)\n+            {\n+                throw GetNotOwnerException();\n+            }\n+\n+            // Validate cookie\n+            LockCookieFlags flags = lockCookie._flags;\n+            ushort requestedWriterLevel = lockCookie._writerLevel;\n+            if ((flags & LockCookieFlags.Invalid) != 0 ||\n+                lockCookie._threadID != threadID ||\n+                // REVIEW: Addition to original code, see large REVIEW comment below\n+                (\n+                    // Cannot downgrade to a writer level that is greater than or equal to the current\n+                    (flags & (LockCookieFlags.OwnedWriter | LockCookieFlags.OwnedNone)) != 0 &&\n+                    _writerLevel <= requestedWriterLevel\n+                ))\n+            {\n+                throw GetInvalidLockCookieException();\n+            }\n+\n+            // Check if the thread was a reader\n+            if ((flags & LockCookieFlags.OwnedReader) != 0)\n+            {\n+                // REVIEW: Original code:","path":"src/System.Threading/src/System/Threading/ReaderWriterLock.cs","position":773,"original_position":773,"commit_id":"7e6ef1353b6086089faa74b526bace665f4448c4","original_commit_id":"7e6ef1353b6086089faa74b526bace665f4448c4","user":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"body":"Departure from original code, please review","created_at":"2016-11-02T10:17:32Z","updated_at":"2016-11-02T10:17:32Z","html_url":"https://github.com/dotnet/corefx/pull/13264#discussion_r86109165","pull_request_url":"https://api.github.com/repos/dotnet/corefx/pulls/13264","_links":{"self":{"href":"https://api.github.com/repos/dotnet/corefx/pulls/comments/86109165"},"html":{"href":"https://github.com/dotnet/corefx/pull/13264#discussion_r86109165"},"pull_request":{"href":"https://api.github.com/repos/dotnet/corefx/pulls/13264"}}},"pull_request":{"url":"https://api.github.com/repos/dotnet/corefx/pulls/13264","id":91941382,"html_url":"https://github.com/dotnet/corefx/pull/13264","diff_url":"https://github.com/dotnet/corefx/pull/13264.diff","patch_url":"https://github.com/dotnet/corefx/pull/13264.patch","issue_url":"https://api.github.com/repos/dotnet/corefx/issues/13264","number":13264,"state":"open","locked":false,"title":"Expose ReaderWriterLock","user":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"body":"- Rewrote ReaderWriterLock in C# from the original code, exposed it, and added tests\r\n- Original code:\r\n  - https://github.com/dotnet/coreclr/blob/master/src/vm/rwlock.h\r\n  - https://github.com/dotnet/coreclr/blob/master/src/vm/rwlock.cpp\r\n\r\nFixes #11634","created_at":"2016-11-02T10:14:55Z","updated_at":"2016-11-02T10:17:32Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4eedf1872c89da86fa516372ca814f679fbbc775","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/dotnet/corefx/pulls/13264/commits","review_comments_url":"https://api.github.com/repos/dotnet/corefx/pulls/13264/comments","review_comment_url":"https://api.github.com/repos/dotnet/corefx/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/13264/comments","statuses_url":"https://api.github.com/repos/dotnet/corefx/statuses/7e6ef1353b6086089faa74b526bace665f4448c4","head":{"label":"kouvel:RwLock","ref":"RwLock","sha":"7e6ef1353b6086089faa74b526bace665f4448c4","user":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"repo":{"id":39798901,"name":"corefx","full_name":"kouvel/corefx","owner":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kouvel/corefx","description":"This repo contains the .NET Core foundational libraries, called CoreFX. It includes classes for collections, file systems, console, XML, async and many others.","fork":true,"url":"https://api.github.com/repos/kouvel/corefx","forks_url":"https://api.github.com/repos/kouvel/corefx/forks","keys_url":"https://api.github.com/repos/kouvel/corefx/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kouvel/corefx/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kouvel/corefx/teams","hooks_url":"https://api.github.com/repos/kouvel/corefx/hooks","issue_events_url":"https://api.github.com/repos/kouvel/corefx/issues/events{/number}","events_url":"https://api.github.com/repos/kouvel/corefx/events","assignees_url":"https://api.github.com/repos/kouvel/corefx/assignees{/user}","branches_url":"https://api.github.com/repos/kouvel/corefx/branches{/branch}","tags_url":"https://api.github.com/repos/kouvel/corefx/tags","blobs_url":"https://api.github.com/repos/kouvel/corefx/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kouvel/corefx/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kouvel/corefx/git/refs{/sha}","trees_url":"https://api.github.com/repos/kouvel/corefx/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kouvel/corefx/statuses/{sha}","languages_url":"https://api.github.com/repos/kouvel/corefx/languages","stargazers_url":"https://api.github.com/repos/kouvel/corefx/stargazers","contributors_url":"https://api.github.com/repos/kouvel/corefx/contributors","subscribers_url":"https://api.github.com/repos/kouvel/corefx/subscribers","subscription_url":"https://api.github.com/repos/kouvel/corefx/subscription","commits_url":"https://api.github.com/repos/kouvel/corefx/commits{/sha}","git_commits_url":"https://api.github.com/repos/kouvel/corefx/git/commits{/sha}","comments_url":"https://api.github.com/repos/kouvel/corefx/comments{/number}","issue_comment_url":"https://api.github.com/repos/kouvel/corefx/issues/comments{/number}","contents_url":"https://api.github.com/repos/kouvel/corefx/contents/{+path}","compare_url":"https://api.github.com/repos/kouvel/corefx/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kouvel/corefx/merges","archive_url":"https://api.github.com/repos/kouvel/corefx/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kouvel/corefx/downloads","issues_url":"https://api.github.com/repos/kouvel/corefx/issues{/number}","pulls_url":"https://api.github.com/repos/kouvel/corefx/pulls{/number}","milestones_url":"https://api.github.com/repos/kouvel/corefx/milestones{/number}","notifications_url":"https://api.github.com/repos/kouvel/corefx/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kouvel/corefx/labels{/name}","releases_url":"https://api.github.com/repos/kouvel/corefx/releases{/id}","deployments_url":"https://api.github.com/repos/kouvel/corefx/deployments","created_at":"2015-07-27T21:30:34Z","updated_at":"2016-01-14T21:16:37Z","pushed_at":"2016-11-02T09:52:40Z","git_url":"git://github.com/kouvel/corefx.git","ssh_url":"git@github.com:kouvel/corefx.git","clone_url":"https://github.com/kouvel/corefx.git","svn_url":"https://github.com/kouvel/corefx","homepage":"http://dotnet.github.io/core/","size":109517,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"dotnet:master","ref":"master","sha":"f8a950e5ab23f22a16aaad75156080a0db48e9f4","user":{"login":"dotnet","id":9141961,"avatar_url":"https://avatars.githubusercontent.com/u/9141961?v=3","gravatar_id":"","url":"https://api.github.com/users/dotnet","html_url":"https://github.com/dotnet","followers_url":"https://api.github.com/users/dotnet/followers","following_url":"https://api.github.com/users/dotnet/following{/other_user}","gists_url":"https://api.github.com/users/dotnet/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet/subscriptions","organizations_url":"https://api.github.com/users/dotnet/orgs","repos_url":"https://api.github.com/users/dotnet/repos","events_url":"https://api.github.com/users/dotnet/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet/received_events","type":"Organization","site_admin":false},"repo":{"id":26295345,"name":"corefx","full_name":"dotnet/corefx","owner":{"login":"dotnet","id":9141961,"avatar_url":"https://avatars.githubusercontent.com/u/9141961?v=3","gravatar_id":"","url":"https://api.github.com/users/dotnet","html_url":"https://github.com/dotnet","followers_url":"https://api.github.com/users/dotnet/followers","following_url":"https://api.github.com/users/dotnet/following{/other_user}","gists_url":"https://api.github.com/users/dotnet/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet/subscriptions","organizations_url":"https://api.github.com/users/dotnet/orgs","repos_url":"https://api.github.com/users/dotnet/repos","events_url":"https://api.github.com/users/dotnet/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dotnet/corefx","description":"This repo contains the .NET Core foundational libraries, called CoreFX. It includes classes for collections, file systems, console, XML, async and many others.","fork":false,"url":"https://api.github.com/repos/dotnet/corefx","forks_url":"https://api.github.com/repos/dotnet/corefx/forks","keys_url":"https://api.github.com/repos/dotnet/corefx/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dotnet/corefx/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dotnet/corefx/teams","hooks_url":"https://api.github.com/repos/dotnet/corefx/hooks","issue_events_url":"https://api.github.com/repos/dotnet/corefx/issues/events{/number}","events_url":"https://api.github.com/repos/dotnet/corefx/events","assignees_url":"https://api.github.com/repos/dotnet/corefx/assignees{/user}","branches_url":"https://api.github.com/repos/dotnet/corefx/branches{/branch}","tags_url":"https://api.github.com/repos/dotnet/corefx/tags","blobs_url":"https://api.github.com/repos/dotnet/corefx/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dotnet/corefx/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dotnet/corefx/git/refs{/sha}","trees_url":"https://api.github.com/repos/dotnet/corefx/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dotnet/corefx/statuses/{sha}","languages_url":"https://api.github.com/repos/dotnet/corefx/languages","stargazers_url":"https://api.github.com/repos/dotnet/corefx/stargazers","contributors_url":"https://api.github.com/repos/dotnet/corefx/contributors","subscribers_url":"https://api.github.com/repos/dotnet/corefx/subscribers","subscription_url":"https://api.github.com/repos/dotnet/corefx/subscription","commits_url":"https://api.github.com/repos/dotnet/corefx/commits{/sha}","git_commits_url":"https://api.github.com/repos/dotnet/corefx/git/commits{/sha}","comments_url":"https://api.github.com/repos/dotnet/corefx/comments{/number}","issue_comment_url":"https://api.github.com/repos/dotnet/corefx/issues/comments{/number}","contents_url":"https://api.github.com/repos/dotnet/corefx/contents/{+path}","compare_url":"https://api.github.com/repos/dotnet/corefx/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dotnet/corefx/merges","archive_url":"https://api.github.com/repos/dotnet/corefx/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dotnet/corefx/downloads","issues_url":"https://api.github.com/repos/dotnet/corefx/issues{/number}","pulls_url":"https://api.github.com/repos/dotnet/corefx/pulls{/number}","milestones_url":"https://api.github.com/repos/dotnet/corefx/milestones{/number}","notifications_url":"https://api.github.com/repos/dotnet/corefx/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dotnet/corefx/labels{/name}","releases_url":"https://api.github.com/repos/dotnet/corefx/releases{/id}","deployments_url":"https://api.github.com/repos/dotnet/corefx/deployments","created_at":"2014-11-06T23:42:48Z","updated_at":"2016-11-02T08:50:25Z","pushed_at":"2016-11-02T10:14:55Z","git_url":"git://github.com/dotnet/corefx.git","ssh_url":"git@github.com:dotnet/corefx.git","clone_url":"https://github.com/dotnet/corefx.git","svn_url":"https://github.com/dotnet/corefx","homepage":"https://docs.microsoft.com/dotnet/","size":158351,"stargazers_count":10729,"watchers_count":10729,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2707,"mirror_url":null,"open_issues_count":1444,"forks":2707,"open_issues":1444,"watchers":10729,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dotnet/corefx/pulls/13264"},"html":{"href":"https://github.com/dotnet/corefx/pull/13264"},"issue":{"href":"https://api.github.com/repos/dotnet/corefx/issues/13264"},"comments":{"href":"https://api.github.com/repos/dotnet/corefx/issues/13264/comments"},"review_comments":{"href":"https://api.github.com/repos/dotnet/corefx/pulls/13264/comments"},"review_comment":{"href":"https://api.github.com/repos/dotnet/corefx/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dotnet/corefx/pulls/13264/commits"},"statuses":{"href":"https://api.github.com/repos/dotnet/corefx/statuses/7e6ef1353b6086089faa74b526bace665f4448c4"}}}}
{"actor":{"display_login":"kouvel"}}