//
// 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.
///
public sealed partial class Monitor {
///
/// Private so that only we can create instances.
///
internal Monitor()
{
this.mutex = new Mutex();
this.depth = 0;
}
///
/// 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;
}
}