Beispiel #1
0
        public void CountTest()
        {
            RemoteExecutor.Invoke(() =>
            {
                const int TimersPerThread = 64;
                int processorCount        = Environment.ProcessorCount;
                int totalTimerCount       = processorCount * TimersPerThread;

                var timers = new List <Timer>(totalTimerCount);
                TimerCallback timerCallback   = _ => { };
                var startCreateTimerThreads   = new ManualResetEvent(false);
                Action createTimerThreadStart = () =>
                {
                    startCreateTimerThreads.WaitOne();
                    for (int i = 0; i < TimersPerThread; ++i)
                    {
                        lock (timers)
                        {
                            timers.Add(
                                new Timer(
                                    timerCallback,
                                    null,
                                    ThreadTestHelpers.UnexpectedTimeoutMilliseconds,
                                    ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                            Assert.True(Timer.ActiveCount >= timers.Count);
                        }
                    }
                };
                var waitsForThread = new Action[processorCount];
                for (int i = 0; i < processorCount; ++i)
                {
                    Thread t       = ThreadTestHelpers.CreateGuardedThread(out waitsForThread[i], createTimerThreadStart);
                    t.IsBackground = true;
                    t.Start();
                }

                startCreateTimerThreads.Set();
                foreach (Action waitForThread in waitsForThread)
                {
                    waitForThread();
                }

                // To leave some room for unknown timers to be scheduled and removed, remove a large number of timers at a time and
                // verify that the timer count has decreased
                while (timers.Count > 0)
                {
                    long timerCountBeforeRemove = Timer.ActiveCount;
                    int endIndex = timers.Count - processorCount * 8;
                    for (int i = timers.Count - 1; i >= Math.Max(0, endIndex); --i)
                    {
                        timers[i].Dispose();
                        timers.RemoveAt(i);
                    }

                    if (endIndex >= 0)
                    {
                        Assert.True(Timer.ActiveCount < timerCountBeforeRemove);
                    }
                }
            }).Dispose();
        }
Beispiel #2
0
        public void AbandonExisting(
            string name,
            WaitHandleWaitType waitType,
            int waitCount,
            int notAbandonedWaitIndex,
            bool isNotAbandonedWaitObjectSignaled,
            bool abandonDuringWait)
        {
            ThreadTestHelpers.RunTestInBackgroundThread(() =>
            {
                using (var m = new Mutex(false, name))
                    using (Mutex m2 = waitCount == 1 ? null : new Mutex(false, name == null ? null : name + "_2"))
                        using (ManualResetEvent e = waitCount == 1 ? null : new ManualResetEvent(isNotAbandonedWaitObjectSignaled))
                            using (ManualResetEvent threadReadyForAbandon = abandonDuringWait ? new ManualResetEvent(false) : null)
                                using (ManualResetEvent abandonSoon = abandonDuringWait ? new ManualResetEvent(false) : null)
                                {
                                    WaitHandle[] waitHandles = null;
                                    if (waitType != WaitHandleWaitType.WaitOne)
                                    {
                                        waitHandles = new WaitHandle[waitCount];
                                        if (waitCount == 1)
                                        {
                                            waitHandles[0] = m;
                                        }
                                        else
                                        {
                                            waitHandles[notAbandonedWaitIndex] = e;
                                            waitHandles[notAbandonedWaitIndex == 0 ? 1 : 0] = m;
                                            waitHandles[notAbandonedWaitIndex == 2 ? 1 : 2] = m2;
                                        }
                                    }

                                    Thread t = ThreadTestHelpers.CreateGuardedThread(out Action waitForThread, () =>
                                    {
                                        Assert.True(m.WaitOne(0));
                                        if (m2 != null)
                                        {
                                            Assert.True(m2.WaitOne(0));
                                        }

                                        if (abandonDuringWait)
                                        {
                                            threadReadyForAbandon.Set();
                                            abandonSoon.CheckedWait();
                                            Thread.Sleep(ThreadTestHelpers.ExpectedTimeoutMilliseconds);
                                        }

                                        // don't release the mutexes; abandon them on this thread
                                    });
                                    t.IsBackground = true;
                                    t.Start();

                                    if (abandonDuringWait)
                                    {
                                        threadReadyForAbandon.CheckedWait();
                                        abandonSoon.Set();
                                    }
                                    else
                                    {
                                        waitForThread();
                                    }

                                    AbandonedMutexException ame;
                                    switch (waitType)
                                    {
                                    case WaitHandleWaitType.WaitOne:
                                        ame =
                                            AssertExtensions.Throws <AbandonedMutexException, bool>(
                                                () => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                        Assert.Equal(-1, ame.MutexIndex);
                                        Assert.Null(ame.Mutex);
                                        break;

                                    case WaitHandleWaitType.WaitAny:
                                        if (waitCount != 1 && isNotAbandonedWaitObjectSignaled && notAbandonedWaitIndex == 0)
                                        {
                                            Assert.Equal(0, WaitHandle.WaitAny(waitHandles, 0));
                                            AssertExtensions.Throws <AbandonedMutexException, bool>(
                                                () => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                            AssertExtensions.Throws <AbandonedMutexException, bool>(
                                                () => m2.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                            break;
                                        }

                                        if (waitCount != 1 && isNotAbandonedWaitObjectSignaled && notAbandonedWaitIndex != 0)
                                        {
                                            ame =
                                                Assert.Throws <AbandonedMutexException>(() =>
                                            {
                                                ThreadTestHelpers.WaitForCondition(() =>
                                                {
                                                    // Actually expecting an exception from WaitAny(), but there may be a delay before
                                                    // the mutex is actually released and abandoned. If there is no exception, the
                                                    // WaitAny() must have succeeded due to the event being signaled.
                                                    int r = WaitHandle.WaitAny(waitHandles, ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
                                                    Assert.Equal(notAbandonedWaitIndex, r);
                                                    return(false);
                                                });
                                            });
                                        }
                                        else
                                        {
                                            ame =
                                                AssertExtensions.Throws <AbandonedMutexException, int>(
                                                    () => WaitHandle.WaitAny(waitHandles, ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                        }

                                        // Due to a potential delay in abandoning mutexes, either mutex may have been seen to be
                                        // abandoned first
                                        Assert.True(ame.Mutex == m || (m2 != null && ame.Mutex == m2));
                                        int mIndex  = waitCount != 1 && notAbandonedWaitIndex == 0 ? 1 : 0;
                                        int m2Index = waitCount != 1 && notAbandonedWaitIndex == 2 ? 1 : 2;
                                        if (ame.Mutex == m)
                                        {
                                            Assert.Equal(mIndex, ame.MutexIndex);
                                        }
                                        else
                                        {
                                            Assert.True(!isNotAbandonedWaitObjectSignaled || m2Index < notAbandonedWaitIndex);
                                            Assert.Equal(m2Index, ame.MutexIndex);
                                        }

                                        // Verify that the other mutex also gets abandoned
                                        if (ame.MutexIndex == mIndex)
                                        {
                                            if (m2 != null)
                                            {
                                                AssertExtensions.Throws <AbandonedMutexException, bool>(
                                                    () => m2.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                            }
                                        }
                                        else
                                        {
                                            AssertExtensions.Throws <AbandonedMutexException, bool>(
                                                () => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                        }

                                        break;

                                    case WaitHandleWaitType.WaitAll:
                                        if (waitCount != 1 && !isNotAbandonedWaitObjectSignaled)
                                        {
                                            Assert.False(WaitHandle.WaitAll(waitHandles, ThreadTestHelpers.ExpectedTimeoutMilliseconds * 2));
                                            Assert.True(e.Set());
                                        }

                                        ame =
                                            AssertExtensions.Throws <AbandonedMutexException, bool>(
                                                () => WaitHandle.WaitAll(waitHandles, ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                                        Assert.Equal(-1, ame.MutexIndex);
                                        Assert.Null(ame.Mutex);
                                        break;
                                    }

                                    if (abandonDuringWait)
                                    {
                                        waitForThread();
                                    }

                                    m.ReleaseMutex();
                                    m2?.ReleaseMutex();
                                }
            });
        }
Beispiel #3
0
 public void Ctor_TryCreateGlobalMutexTest_Uwp()
 {
     ThreadTestHelpers.RunTestInBackgroundThread(() =>
                                                 Assert.Throws <UnauthorizedAccessException>(() => new Mutex(false, "Global\\" + Guid.NewGuid().ToString("N"))));
 }
        public static void WaitNotificationTest()
        {
            ThreadTestHelpers.RunTestInBackgroundThread(() =>
            {
                var tsc = new TestSynchronizationContext();
                SynchronizationContext.SetSynchronizationContext(tsc);
                Assert.Same(tsc, SynchronizationContext.Current);

                var e          = new ManualResetEvent(false);
                tsc.WaitAction = () => e.Set();
                Assert.False(tsc.IsWaitNotificationRequired());
                Assert.False(e.WaitOne(0));
                tsc.SetWaitNotificationRequired();
                Assert.True(tsc.IsWaitNotificationRequired());
                Assert.True(e.WaitOne(0));

                var mres       = new ManualResetEventSlim();
                tsc.WaitAction = () => mres.Set();
                mres.Reset();
                mres.CheckedWait();

                e.Reset();
                tsc.WaitAction = () => e.Set();
                SynchronizationContext.SetSynchronizationContext(new TestSynchronizationContext());
                Assert.False(e.WaitOne(0));
                SynchronizationContext.SetSynchronizationContext(tsc);
                Assert.True(e.WaitOne(0));
                e.Reset();
                e.CheckedWait();

                e.Reset();
                var lockObj = new object();
                var lockAcquiredFromBackground = new AutoResetEvent(false);
                Action waitForThread;
                Thread t =
                    ThreadTestHelpers.CreateGuardedThread(out waitForThread, () =>
                {
                    lock (lockObj)
                    {
                        lockAcquiredFromBackground.Set();
                        e.CheckedWait();
                    }
                });
                t.IsBackground = true;
                t.Start();
                lockAcquiredFromBackground.CheckedWait();
                Assert.True(Monitor.TryEnter(lockObj, ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                Monitor.Exit(lockObj);
                waitForThread();

                e.Reset();
                var m = new Mutex();
                t     = ThreadTestHelpers.CreateGuardedThread(out waitForThread, () =>
                {
                    m.CheckedWait();
                    try
                    {
                        lockAcquiredFromBackground.Set();
                        e.CheckedWait();
                    }
                    finally
                    {
                        m.ReleaseMutex();
                    }
                });
                t.IsBackground = true;
                t.Start();
                lockAcquiredFromBackground.CheckedWait();
                m.CheckedWait();
                m.ReleaseMutex();
                waitForThread();
            });
        }
        public static void FlowTest()
        {
            ThreadTestHelpers.RunTestInBackgroundThread(() =>
            {
                var asyncLocal   = new AsyncLocal <int>();
                asyncLocal.Value = 1;

                var asyncFlowControl = default(AsyncFlowControl);
                Action <Action, Action> verifySuppressRestore =
                    (suppressFlow, restoreFlow) =>
                {
                    VerifyExecutionContextFlow(asyncLocal, 1);
                    ExecutionContext executionContext2 = ExecutionContext.Capture();

                    suppressFlow();
                    VerifyExecutionContextFlow(asyncLocal, 0);
                    VerifyExecutionContext(executionContext2, asyncLocal, 1);
                    executionContext2 = ExecutionContext.Capture();

                    restoreFlow();
                    VerifyExecutionContextFlow(asyncLocal, 1);
                    VerifyExecutionContext(executionContext2, asyncLocal, 0);
                };

                verifySuppressRestore(
                    () => asyncFlowControl = ExecutionContext.SuppressFlow(),
                    () => asyncFlowControl.Undo());
                verifySuppressRestore(
                    () => asyncFlowControl = ExecutionContext.SuppressFlow(),
                    () => asyncFlowControl.Dispose());
                verifySuppressRestore(
                    () => ExecutionContext.SuppressFlow(),
                    () => ExecutionContext.RestoreFlow());

                Assert.Throws <InvalidOperationException>(() => ExecutionContext.RestoreFlow());
                asyncFlowControl = ExecutionContext.SuppressFlow();
                Assert.Throws <InvalidOperationException>(() => ExecutionContext.SuppressFlow());

                ThreadTestHelpers.RunTestInBackgroundThread(() =>
                {
                    ExecutionContext.SuppressFlow();
                    Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Undo());
                    Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Dispose());
                    ExecutionContext.RestoreFlow();
                });

                asyncFlowControl.Undo();
                Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Undo());
                Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Dispose());

                // Changing an async local value does not prevent undoing a flow-suppressed execution context. In .NET Core, the
                // execution context is immutable, so changing an async local value changes the execution context instance,
                // contrary to the .NET Framework.
                asyncFlowControl = ExecutionContext.SuppressFlow();
                asyncLocal.Value = 2;
                asyncFlowControl.Undo();
                VerifyExecutionContextFlow(asyncLocal, 2);
                asyncFlowControl = ExecutionContext.SuppressFlow();
                asyncLocal.Value = 3;
                asyncFlowControl.Dispose();
                VerifyExecutionContextFlow(asyncLocal, 3);
                ExecutionContext.SuppressFlow();
                asyncLocal.Value = 4;
                ExecutionContext.RestoreFlow();
                VerifyExecutionContextFlow(asyncLocal, 4);

                // An async flow control cannot be undone when a different execution context is applied. The .NET Framework
                // mutates the execution context when its state changes, and only changes the instance when an execution context
                // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
                // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
                // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
                // local's value, the .NET Framework verifies that a different execution context has not been applied by
                // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
                // since the execution context instance will change after changing the async local's value, it verifies that a
                // different execution context has not been applied, by instead ensuring that the current execution context's
                // flow is suppressed.
                {
                    ExecutionContext executionContext = null;
                    Action verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext =
                        () =>
                    {
                        ExecutionContext.Run(
                            executionContext,
                            state =>
                        {
                            Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Undo());
                            Assert.Throws <InvalidOperationException>(() => asyncFlowControl.Dispose());
                        },
                            null);
                    };

                    executionContext = ExecutionContext.Capture();
                    asyncFlowControl = ExecutionContext.SuppressFlow();
                    verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext();
                    asyncFlowControl.Undo();

                    executionContext = ExecutionContext.Capture();
                    asyncFlowControl = ExecutionContext.SuppressFlow();
                    asyncLocal.Value = 5;
                    verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext();
                    asyncFlowControl.Undo();
                    VerifyExecutionContextFlow(asyncLocal, 5);
                }
            });
        }
Beispiel #6
0
        public static void ValuesGetterDoesNotThrowUnexpectedExceptionWhenDisposed()
        {
            var startTest = new ManualResetEvent(false);
            var gotUnexpectedException    = new ManualResetEvent(false);
            ThreadLocal <int> threadLocal = null;
            bool stop = false;

            Action waitForCreatorDisposer;
            Thread creatorDisposer = ThreadTestHelpers.CreateGuardedThread(out waitForCreatorDisposer, () =>
            {
                startTest.CheckedWait();
                do
                {
                    var tl = new ThreadLocal <int>(trackAllValues: true);
                    Volatile.Write(ref threadLocal, tl);
                    tl.Value = 1;
                    tl.Dispose();
                } while (!Volatile.Read(ref stop));
            });

            creatorDisposer.IsBackground = true;
            creatorDisposer.Start();

            int readerCount    = Math.Max(1, Environment.ProcessorCount - 1);
            var waitsForReader = new Action[readerCount];

            for (int i = 0; i < readerCount; ++i)
            {
                Thread reader = ThreadTestHelpers.CreateGuardedThread(out waitsForReader[i], () =>
                {
                    startTest.CheckedWait();
                    do
                    {
                        var tl = Volatile.Read(ref threadLocal);
                        if (tl == null)
                        {
                            continue;
                        }

                        try
                        {
                            IList <int> values = tl.Values;
                        }
                        catch (ObjectDisposedException)
                        {
                        }
                        catch
                        {
                            gotUnexpectedException.Set();
                            throw;
                        }
                    } while (!Volatile.Read(ref stop));
                });
                reader.IsBackground = true;
                reader.Start();
            }

            startTest.Set();
            bool failed = gotUnexpectedException.WaitOne(500);

            Volatile.Write(ref stop, true);
            foreach (Action waitForReader in waitsForReader)
            {
                waitForReader();
            }
            waitForCreatorDisposer();
            Assert.False(failed);
        }
Beispiel #7
0
        public static void Enter_HasToWait()
        {
            var thinLock  = new object();
            var awareLock = new object();

            // Actually transition the aware lock to an aware lock by having a background thread wait for a lock
            {
                Action waitForThread;
                Thread t =
                    ThreadTestHelpers.CreateGuardedThread(out waitForThread, () =>
                                                          Assert.False(Monitor.TryEnter(awareLock, ThreadTestHelpers.ExpectedTimeoutMilliseconds)));
                t.IsBackground = true;
                lock (awareLock)
                {
                    t.Start();
                    waitForThread();
                }
            }

            // When the current thread has the lock, have background threads wait for the lock in various ways. After a short
            // duration, release the lock and allow the background threads to acquire the lock.
            {
                var     backgroundTestDelegates = new List <Action <object> >();
                Barrier readyBarrier            = null;

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    Monitor.Enter(lockObj);
                    Monitor.Exit(lockObj);
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    bool lockTaken = false;
                    Monitor.Enter(lockObj, ref lockTaken);
                    Assert.True(lockTaken);
                    Monitor.Exit(lockObj);
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    lock (lockObj)
                    {
                    }
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    Assert.True(Monitor.TryEnter(lockObj, ThreadTestHelpers.UnexpectedTimeoutMilliseconds));
                    Monitor.Exit(lockObj);
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    Assert.True(
                        Monitor.TryEnter(lockObj, TimeSpan.FromMilliseconds(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)));
                    Monitor.Exit(lockObj);
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    bool lockTaken = false;
                    Monitor.TryEnter(lockObj, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, ref lockTaken);
                    Assert.True(lockTaken);
                    Monitor.Exit(lockObj);
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    bool lockTaken = false;
                    Monitor.TryEnter(
                        lockObj,
                        TimeSpan.FromMilliseconds(ThreadTestHelpers.UnexpectedTimeoutMilliseconds),
                        ref lockTaken);
                    Assert.True(lockTaken);
                    Monitor.Exit(lockObj);
                });

                int testCount = backgroundTestDelegates.Count * 2; // two iterations each, one for thin lock and one for aware lock
                readyBarrier = new Barrier(testCount + 1);         // plus main thread
                var waitForThreadArray = new Action[testCount];
                for (int i = 0; i < backgroundTestDelegates.Count; ++i)
                {
                    int    icopy = i; // for use in delegates
                    Thread t     =
                        ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2],
                                                              () => backgroundTestDelegates[icopy](thinLock));
                    t.IsBackground = true;
                    t.Start();
                    t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2 + 1],
                                                              () => backgroundTestDelegates[icopy](awareLock));
                    t.IsBackground = true;
                    t.Start();
                }

                lock (thinLock)
                {
                    lock (awareLock)
                    {
                        readyBarrier.SignalAndWait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
                        Thread.Sleep(ThreadTestHelpers.ExpectedTimeoutMilliseconds);
                    }
                }
                foreach (Action waitForThread in waitForThreadArray)
                {
                    waitForThread();
                }
            }

            // When the current thread has the lock, have background threads wait for the lock in various ways and time out
            // after a short duration
            {
                var     backgroundTestDelegates = new List <Action <object> >();
                Barrier readyBarrier            = null;

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    Assert.False(Monitor.TryEnter(lockObj, ThreadTestHelpers.ExpectedTimeoutMilliseconds));
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    Assert.False(
                        Monitor.TryEnter(lockObj, TimeSpan.FromMilliseconds(ThreadTestHelpers.ExpectedTimeoutMilliseconds)));
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    bool lockTaken = false;
                    Monitor.TryEnter(lockObj, ThreadTestHelpers.ExpectedTimeoutMilliseconds, ref lockTaken);
                    Assert.False(lockTaken);
                });

                backgroundTestDelegates.Add(lockObj =>
                {
                    readyBarrier.SignalAndWait();
                    bool lockTaken = false;
                    Monitor.TryEnter(
                        lockObj,
                        TimeSpan.FromMilliseconds(ThreadTestHelpers.ExpectedTimeoutMilliseconds),
                        ref lockTaken);
                    Assert.False(lockTaken);
                });

                int testCount = backgroundTestDelegates.Count * 2; // two iterations each, one for thin lock and one for aware lock
                readyBarrier = new Barrier(testCount + 1);         // plus main thread
                var waitForThreadArray = new Action[testCount];
                for (int i = 0; i < backgroundTestDelegates.Count; ++i)
                {
                    int    icopy = i; // for use in delegates
                    Thread t     =
                        ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2],
                                                              () => backgroundTestDelegates[icopy](thinLock));
                    t.IsBackground = true;
                    t.Start();
                    t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i * 2 + 1],
                                                              () => backgroundTestDelegates[icopy](awareLock));
                    t.IsBackground = true;
                    t.Start();
                }

                lock (thinLock)
                {
                    lock (awareLock)
                    {
                        readyBarrier.SignalAndWait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
                        foreach (Action waitForThread in waitForThreadArray)
                        {
                            waitForThread();
                        }
                    }
                }
            }
        }
Beispiel #8
0
        public static void BasicLockTest()
        {
            var            trwl = new TestReaderWriterLock();
            TestLockCookie tlc;
            var            threadReady = new AutoResetEvent(false);
            var            continueThread = new AutoResetEvent(false);
            Action         checkForThreadErrors, waitForThread;
            Thread         t =
                ThreadTestHelpers.CreateGuardedThread(out checkForThreadErrors, out waitForThread, () =>
            {
                TestLockCookie tlc2;
                Action switchToMainThread =
                    () =>
                {
                    threadReady.Set();
                    continueThread.CheckedWait();
                };

                switchToMainThread();

                // Multiple readers from multiple threads
                {
                    trwl.AcquireReaderLock();
                    trwl.AcquireReaderLock();
                    switchToMainThread();
                    trwl.ReleaseReaderLock();
                    switchToMainThread();
                    trwl.ReleaseReaderLock();
                    switchToMainThread();

                    trwl.AcquireReaderLock();
                    trwl.ReleaseReaderLock();
                    switchToMainThread();
                }

                // What can be done when a read lock is held
                {
                    trwl.AcquireReaderLock();
                    switchToMainThread();

                    // Any thread can take a read lock
                    trwl.AcquireReaderLock();
                    trwl.ReleaseReaderLock();
                    switchToMainThread();

                    // No thread can take a write lock
                    trwl.AcquireWriterLock(TimeoutExceptionHResult);
                    trwl.AcquireReaderLock();
                    trwl.UpgradeToWriterLock(TimeoutExceptionHResult);
                    trwl.ReleaseReaderLock();
                    switchToMainThread();

                    trwl.ReleaseReaderLock();
                    switchToMainThread();

                    // Owning thread releases read lock when upgrading
                    trwl.AcquireWriterLock();
                    trwl.ReleaseWriterLock();
                    switchToMainThread();

                    // Owning thread cannot upgrade if there are other readers or writers
                    trwl.AcquireReaderLock();
                    switchToMainThread();
                    trwl.ReleaseReaderLock();
                    trwl.AcquireWriterLock();
                    switchToMainThread();
                    trwl.ReleaseWriterLock();
                    switchToMainThread();
                }

                // What can be done when a write lock is held
                {
                    // Write lock acquired through AcquireWriteLock is exclusive
                    trwl.AcquireWriterLock();
                    switchToMainThread();
                    trwl.ReleaseWriterLock();
                    switchToMainThread();

                    // Write lock acquired through upgrading is also exclusive
                    trwl.AcquireReaderLock();
                    tlc2 = trwl.UpgradeToWriterLock();
                    switchToMainThread();
                    trwl.DowngradeFromWriterLock(tlc2);
                    trwl.ReleaseReaderLock();
                    switchToMainThread();

                    // Write lock acquired through restore is also exclusive
                    trwl.AcquireWriterLock();
                    tlc = trwl.ReleaseLock();
                    trwl.RestoreLock(tlc);
                    switchToMainThread();
                    trwl.ReleaseWriterLock();
                    switchToMainThread();
                }
            });

            t.IsBackground = true;
            t.Start();

            Action beginSwitchToBackgroundThread = () => continueThread.Set();
            Action endSwitchToBackgroundThread   =
                () =>
            {
                try
                {
                    threadReady.CheckedWait();
                }
                finally
                {
                    checkForThreadErrors();
                }
            };
            Action switchToBackgroundThread =
                () =>
            {
                beginSwitchToBackgroundThread();
                endSwitchToBackgroundThread();
            };

            endSwitchToBackgroundThread();

            // Multiple readers from muliple threads
            {
                trwl.AcquireReaderLock();
                trwl.AcquireReaderLock();
                switchToBackgroundThread(); // AcquireReaderLock * 2
                trwl.ReleaseReaderLock();
                switchToBackgroundThread(); // ReleaseReaderLock

                // Release/restore the read lock while a read lock is held by another thread
                tlc = trwl.ReleaseLock();
                trwl.RestoreLock(tlc);

                switchToBackgroundThread(); // ReleaseReaderLock

                // Downgrade to read lock allows another thread to acquire read lock
                tlc = trwl.UpgradeToWriterLock();
                trwl.DowngradeFromWriterLock(tlc);
                switchToBackgroundThread(); // AcquireReaderLock, ReleaseReaderLock

                trwl.ReleaseReaderLock();
            }

            // What can be done when a read lock is held
            {
                switchToBackgroundThread(); // AcquireReaderLock
                {
                    // Any thread can take a read lock
                    trwl.AcquireReaderLock();
                    trwl.ReleaseReaderLock();
                    switchToBackgroundThread(); // same as above

                    // No thread can take a write lock
                    trwl.AcquireWriterLock(TimeoutExceptionHResult);
                    trwl.AcquireReaderLock();
                    trwl.UpgradeToWriterLock(TimeoutExceptionHResult);
                    switchToBackgroundThread(); // same as above
                    trwl.ReleaseReaderLock();

                    // Other threads cannot upgrade to a write lock, but the owning thread can
                    trwl.AcquireReaderLock();
                    trwl.UpgradeToWriterLock(TimeoutExceptionHResult);
                    trwl.ReleaseReaderLock();
                }
                switchToBackgroundThread(); // ReleaseReaderLock

                // Owning thread releases read lock when upgrading
                trwl.AcquireReaderLock();
                beginSwitchToBackgroundThread(); // AcquireWriterLock: background thread gets blocked
                trwl.UpgradeToWriterLock();      // unblocks background thread: ReleaseWriterLock
                trwl.ReleaseWriterLock();
                endSwitchToBackgroundThread();

                // Owning thread cannot upgrade if there are other readers or writers
                trwl.AcquireReaderLock();
                switchToBackgroundThread(); // AcquireReaderLock
                trwl.UpgradeToWriterLock(TimeoutExceptionHResult);
                trwl.ReleaseReaderLock();
                switchToBackgroundThread(); // ReleaseReaderLock, AcquireWriterLock
                trwl.UpgradeToWriterLock(TimeoutExceptionHResult);
                switchToBackgroundThread(); // ReleaseWriterLock
            }

            // What can be done when a write lock is held
            {
                trwl.AcquireWriterLock();
                TestLockCookie restoreToWriteLockTlc   = trwl.ReleaseLock();
                Action         verifyCannotAcquireLock =
                    () =>
                {
                    trwl.AcquireReaderLock(TimeoutExceptionHResult);
                    trwl.AcquireWriterLock(TimeoutExceptionHResult);
                    trwl.UpgradeToWriterLock(TimeoutExceptionHResult);
                };
                Action verifyCanAcquireLock =
                    () =>
                {
                    trwl.AcquireReaderLock();
                    tlc = trwl.UpgradeToWriterLock();
                    trwl.DowngradeFromWriterLock(tlc);
                    trwl.ReleaseReaderLock();
                    trwl.AcquireWriterLock();
                    trwl.ReleaseWriterLock();
                    trwl.RestoreLock(restoreToWriteLockTlc.Clone());
                    trwl.ReleaseWriterLock();
                };

                // Write lock acquired through AcquireWriteLock is exclusive
                switchToBackgroundThread(); // AcquireWriterLock
                verifyCannotAcquireLock();
                switchToBackgroundThread(); // ReleaseWriterLock
                verifyCanAcquireLock();

                // Write lock acquired through upgrading is also exclusive
                switchToBackgroundThread(); // AcquireReaderLock, UpgradeToWriterLock
                verifyCannotAcquireLock();
                switchToBackgroundThread(); // DowngradeFromWriterLock, ReleaseReaderLock
                verifyCanAcquireLock();

                // Write lock acquired through restore is also exclusive
                switchToBackgroundThread(); // AcquireWriterLock, ReleaseLock, RestoreLock
                verifyCannotAcquireLock();
                switchToBackgroundThread(); // ReleaseWriterLock
                verifyCanAcquireLock();
            }

            beginSwitchToBackgroundThread();
            waitForThread();
            trwl.Dispose();
        }