// // Copyright (c) Microsoft Corporation. All rights reserved. // namespace System.Threading { using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Bartok.Runtime; using Microsoft.Singularity; /// /// A monitor is used for synchronization. Only a single thread can /// hold the monitor at any given time. /// /// The monitor maintains two lists of threads: one for threads waiting /// to enter the monitor, and one for threads waiting for a pulse within /// the monitor. /// [NoCCtor] public sealed class Monitor { /// /// Private so that only we can create instances. /// internal Monitor() { this.mutex = new Mutex(); this.depth = 0; } /// /// Attempt to enter the monitor, blocking until it is held. /// [Intrinsic] public static extern void Enter(Object obj); [RequiredByBartok("Enter gets lowered to this")] private static void EnterImpl(Object obj) { Monitor monitor = GetMonitorFromObject(obj); monitor.Enter(); } /// /// Exit the monitor. /// [Intrinsic] public static extern void Exit(Object obj); [RequiredByBartok("Exit gets lowered to this")] public static void ExitImpl(Object obj) { Monitor monitor = GetMonitorFromObject(obj); monitor.Exit(); } /// /// Wake up a thread waiting on the monitor. /// public static void Pulse(Object obj) { Monitor monitor = GetMonitorFromObject(obj); monitor.Pulse(); } /// /// Wake up all threads waiting on the monitor. /// public static void PulseAll(Object obj) { Monitor monitor = GetMonitorFromObject(obj); monitor.PulseAll(); } /// /// Attempt to enter the monitor, returning immediately if it is /// already held by another thread. /// public static bool TryEnter(Object obj) { Monitor monitor = GetMonitorFromObject(obj); return monitor.TryEnter(); } /// /// Attempt to enter the monitor, returning if it can not be taken /// within the specified timeout. /// public static bool TryEnter(Object obj, TimeSpan timeout) { Monitor monitor = GetMonitorFromObject(obj); return monitor.TryEnter(SchedulerTime.Now + timeout); } /// /// Wait to be woken up by a holder of the monitor. /// public static bool Wait(Object obj) { Monitor monitor = GetMonitorFromObject(obj); return monitor.Wait(SchedulerTime.MaxValue); } /// /// Wait to be woken up by a holder of the monitor. Give up after /// a specified timeout. /// public static bool Wait(Object obj, TimeSpan timeout) { Monitor monitor = GetMonitorFromObject(obj); return monitor.Wait(SchedulerTime.Now + timeout); } /// /// Wait to be woken up by a holder of the monitor. Give up after /// a specified timeout. /// /// Overload exists to match the CLR. Exit Context not supported. /// public static bool Wait(Object obj, TimeSpan timeout, bool exitContext) { if (exitContext) { DebugStub.Break(); throw new NotSupportedException("exitContext not supported!"); } Monitor monitor = GetMonitorFromObject(obj); return monitor.Wait(SchedulerTime.Now + timeout); } /// /// Enter the monitor, blocking until it is held. /// internal void Enter() { TryEnter(SchedulerTime.MaxValue); } /// /// Exit the monitor. /// internal void Exit() { if (!mutex.IsOwnedByCurrentThread()) { DebugStub.Break(); throw new SynchronizationLockException("Monitor not held on Exit"); } depth--; if (depth == 0) { mutex.ReleaseMutex(); } } /// /// Wake up a single thread waiting on the monitor. /// internal void Pulse() { if (!mutex.IsOwnedByCurrentThread()) { DebugStub.Break(); throw new SynchronizationLockException("Monitor not held on Pulse"); } // Wake up thread at the head of the wait list. if (waitListHead != null) { Thread t = Dequeue(); if (t != null) { t.nextThread = null; t.SignalMonitor(); } } } /// /// Wake up all threads waiting on the monitor. /// internal void PulseAll() { if (!mutex.IsOwnedByCurrentThread()) { DebugStub.Break(); throw new SynchronizationLockException("Monitor not held on PulseAll"); } // Wake up all threads the wait list. if (waitListHead != null) { Thread t = waitListHead; while (t != null) { Thread next = t.nextThread; t.nextThread = null; t.SignalMonitor(); t = next; } waitListHead = null; waitListTail = null; } } /// /// Try to enter the monitor, returning immediately if it is /// already held. /// internal bool TryEnter() { return TryEnter(new SchedulerTime(0)); } /// /// Try to enter the monitor, giving up if it cannot be /// entered after a timeout. /// internal bool TryEnter(SchedulerTime stop) { if (mutex.IsOwnedByCurrentThread()) { depth++; return true; } if (mutex.AcquireMutex(stop)) { depth = 1; return true; } return false; } /// /// Wait within the monitor for a Pulse. /// internal bool Wait(SchedulerTime stop) { Thread currentThread = Thread.CurrentThread; if (!mutex.IsOwnedByCurrentThread()) { DebugStub.Break(); throw new SynchronizationLockException("Monitor not held on Wait"); } int rememberedDepth = depth; depth = 0; // Add me onto the waiting list. Enqueue(currentThread); // Exit the monitor mutex.ReleaseMutex(); // Wait currentThread.WaitForMonitor(stop); // Re-enter the monitor mutex.AcquireMutex(); depth = rememberedDepth; bool success = !Remove(currentThread); if (!success && stop == SchedulerTime.MaxValue) { VTable.DebugBreak(); } return success; } /// /// Ensure that the passed object has a monitor (and associated /// SyncBlock) allocated. /// internal static void CreateMonitor(Object obj) { GetMonitorFromObject(obj); } // BUGBUG: The garbage collectors will not collect monitors that are // in use. Use a very defensive strategy for now. internal bool IsInUse() { return true; } /// /// Internal Type conversion method. /// Note: we don't use VTable.fromAddress because we /// cannot do a checked cast from Object to Monitor during GC /// (because the GC may be using the vtable word) /// /// internal static Monitor FromAddress(UIntPtr v) { return Magic.toMonitor(Magic.fromAddress(v)); } /// /// Look up the Monitor for the specified object in the SyncBlock /// tables. If no Monitor exists for the object then one is created. /// private static Monitor GetMonitorFromObject(Object obj) { if (obj == null) { DebugStub.Break(); throw new ArgumentNullException("obj"); } Monitor result = MultiUseWord.GetMonitor(obj); return result; } ////////////////////////////////////////////////////////////////////// // // Linked list of threads waiting for a Pulse in a monitor. private Thread waitListHead; private Thread waitListTail; /// /// Dequeue a thread from the singly linked list from head to tail, /// acquiring the ListLock if necessary. /// /// If the list is empty then this method returns null. /// [Inline] private Thread Dequeue() { Thread result; if (waitListHead == null) { // Empty list result = null; } else if (waitListHead == waitListTail) { // Single entry on list VTable.Assert(waitListHead.nextThread == null); result = waitListHead; waitListHead = waitListTail = null; } else { // Multiple entries on list result = waitListHead; waitListHead = waitListHead.nextThread; } return result; } /// /// Search the linked list and remove the specified thread if /// it is linked in. /// /// Acquires the ListLock if necessary. /// [Inline] private bool Remove(Thread target) { if (waitListHead == null) { return false; } if (waitListHead == waitListTail) { // Single entry on list VTable.Assert(waitListHead.nextThread == null); if (waitListHead != target) { // Not on list return false; } waitListHead = waitListTail = null; } else if (waitListHead == target) { // At waitListHead of list waitListHead = target.nextThread; target.nextThread = null; } else { // Multiple entries on list Thread next = waitListHead; while (next != null && next.nextThread != target) { next = next.nextThread; } if (next == null) { // Not on list return false; } if (waitListTail == target) { // Update the waitListTail waitListTail = next; } next.nextThread = target.nextThread; target.nextThread = null; } return true; } /// /// Append a thread at the tail of a queue. If the queue is /// currently null this method initializes it. /// /// Acquires the ListLock if necessary. /// [Inline] private void Enqueue(Thread target) { if (waitListHead == null) { waitListHead = waitListTail = target; } else { waitListTail.nextThread = target; waitListTail = target; } } /// /// The recursion depth of the current holder of the monitor. /// private int depth; /// /// The mutex that is held by the thread that holds the monitor /// private Mutex mutex; } }