// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== using System; using System.Threading; using System.Runtime.CompilerServices; using System.Collections; using Microsoft.Singularity; using Microsoft.Singularity.Io; using Microsoft.Singularity.Scheduling; namespace System.Threading { /// /// /// Base class for all synchronization objects such as events and mutex /// /// [NoCCtor] [CLSCompliant(false)] public abstract class WaitHandle : WaitHandleBase { /// /// /// Constructor /// /// /// Initial state of an handle /// /// Value represents a state of a handle when wait satisfied right a way /// /// The spin lock type of the wait handle /// protected WaitHandle( SignalState initialState, SignalState signalStateAfterImediateWait, SpinLock.Types spinLockType) : base(initialState, signalStateAfterImediateWait) { this.singleHandle = new WaitHandle[1] { this }; // Initialize waithandle spinlock this.myLock = new SpinLock(spinLockType); } /// /// /// Wait on a handle with a specified time out. /// /// /// Time out /// public bool WaitOne(TimeSpan timeout) { return (WaitAny(singleHandle, 1, timeout) != WaitTimeout); } /// /// /// Wait on a handle with a specified time out. /// /// /// Time out /// internal bool WaitOne(SchedulerTime stop) { return (WaitAny(singleHandle, 1, stop) != WaitTimeout); } /// /// /// Wait on a handle without time out /// /// public bool WaitOne() { return WaitOne(SchedulerTime.MaxValue); } /// /// /// Wait on a set of handles until one of them becomes signaled with a specified time out. /// /// /// Wait handles to wait on /// Time out /// public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout) { SchedulerTime stop = SchedulerTime.Now + timeout; return WaitAny(waitHandles, waitHandles.Length, stop); } /// /// /// Wait on a set of handles until one of them becomes signaled with a specified time out. /// /// /// Wait handles to wait on /// Time out /// public static int WaitAny(WaitHandle[] waitHandles, SchedulerTime stop) { return WaitAny(waitHandles, waitHandles.Length, stop); } /// /// /// Wait on a set of handles until one of them becomes signaled without timeout. /// /// /// Wait handles to wait on /// public static int WaitAny(WaitHandle[] waitHandles) { return WaitAny(waitHandles, SchedulerTime.MaxValue); } /// /// /// Wait on a set of handles until one of them becomes signaled with a specified time out. /// /// /// Wait handles to wait on /// A number of wait handles to wait on /// Time out /// public int WaitAny( WaitHandle[] waitHandles, int waitHandlesCount, TimeSpan timeout) { SchedulerTime stop; if (timeout != TimeSpan.MaxValue) { stop = SchedulerTime.Now + timeout; } else { stop = SchedulerTime.MaxValue; } return WaitAny(waitHandles, waitHandlesCount, stop); } /// /// /// Wait on a set of handles until one of them becomes signaled with a specified time out. /// /// /// Wait handles to wait on /// A number of wait handles to wait on /// Time out /// public static int WaitAny( WaitHandle[] waitHandles, int waitHandlesCount, SchedulerTime stop) { // Retrieve current thread information Thread currentThread = Thread.CurrentThread; Thread target = null; int unblockedBy; ThreadEntry[] entries = currentThread.GetWaitEntries( waitHandlesCount); // Before we attempting to enqueue ourselves into the wait queues make sure // we disable abort currentThread.DelayStop(true); // Perepare for a wait - enqueue ourselves into every wait handle unblockedBy = PreWaitAnyInternal(currentThread, waitHandles, entries, waitHandlesCount); // If we are in the process of blocking: Block if (UninitWait == unblockedBy) { // Allow thread to be aborted at this point currentThread.DelayStop(false); // Update thread WaitFor information: Indicate every one that we waiting - // scheduler ones wakes us up is responsible for cleaning up wait information // by using ResetWaitInfo call //currentThread.UpdateWaitInfo (waitHandles, // waitHandlesCount, // entries, // stop); // Write out log record Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.WaitAny, 0, (uint)stop.Ticks, (uint)(stop.Ticks >> 32), (uint)currentThread.threadIndex, 0, 0); // Let scheduler know that we are blocking Kernel.TheScheduler.OnThreadBlocked(currentThread, stop); // Our thread is about to run so we can disassociate it from wait handles PostWaitAnyInternal(currentThread, waitHandles, entries, waitHandlesCount); // Thread has the unblocked information unblockedBy = currentThread.UnblockedBy; } // Assert post condition: unblocked by can't be uninitialized VTable.Assert(unblockedBy != WaitHandle.UninitWait); // If there are wait handles and we were unblocked by not the timeout if (waitHandles != null && unblockedBy >= 0 && unblockedBy < waitHandlesCount) { // Complete wait waitHandles[unblockedBy].CompleteWait (currentThread); // When we were signalged delay abort has been set - now we can turn it off // For mutex complete wait will add delay abort. It will remain on until // mutex is released currentThread.DelayStop(false); } // Make sure that we pay attention to abort currentThread.ProcessAbortIfRequired(); return unblockedBy; } /// /// /// Signal wait handle by waking up a single waiter or if there are no waiters /// set handle to specified stated /// /// /// /// Set the wait handle into specified state if no waiters present /// /// [NoHeapAllocation] protected void SignalOne(SignalState signalStateIfNoWaiters) { // Single a waiting thread and put it in the deferredWakeupQueue ThreadQueueStruct deferredWakeupQueue = SignalOneWithNoWakeup(signalStateIfNoWaiters); // Wakeup a waiter if we need to WakeupOneWaiter(deferredWakeupQueue); } /// /// /// Signal wait handle by waking up all waiters. Set wait handle into specified state /// in both cases when waiters present and not /// /// /// /// Set the wait handle into specified state if no waiters present /// /// /// /// Set the wait handle into specified state if waiter are present /// /// [NoHeapAllocation] protected void SignalAll( SignalState signalStateIfNoWaiters, SignalState signalStateIfWaiters) { // Single the waiting threads and put them in the deferredWakeupQueue ThreadQueueStruct deferredWakeupQueue = SignalAllWithNoWakeup( signalStateIfNoWaiters, signalStateIfWaiters); // Wakeup a waiter if we need to WakeupAllWaiters(deferredWakeupQueue); } /// /// This field is an array of length 1 containing 'this'. /// It is used to avoid allocation when calling WaitAny from WaitOne. /// private WaitHandle[] singleHandle; } /// /// /// Base class for all synchronization objects such as events and mutex /// /// [NoCCtor] [CLSCompliant(false)] public abstract class WaitHandleBase { /// Time out constant public const int WaitTimeout = -1; /// Time out constant public const int UninitWait = -2; /// Infinite time out public const int InfinityTimeout = -1; /// GC uses this for unblocked by information public const int UnblockedByGC = -3; /// Unblocked by abort public const int UnblockedByAbort = -4; /// The state of the handle, signaled or unsignaled protected enum SignalState : int { Unsignaled = 0, Signaled = 1, } /// /// /// Constructor /// /// /// Initial state of an handle /// /// Value represents a state of a handle when wait satisfied right a way /// /// protected WaitHandleBase( SignalState initialState, SignalState signalStateAfterImediateWait) { this.id = ++idGenerator; this.owner = null; this.signaledQueue = new ThreadQueue(this); this.waitingQueue = new ThreadQueue(this); this.signaled = initialState; this.signalStateAfterImediateWait = signalStateAfterImediateWait; } /// /// /// Perform the work to signal wait handle or if there are no waiters /// set handle to specified stated. Put the signaled waiter to a deferred /// wakeup queue if there is any and return the queue. /// /// /// /// Set the wait handle into specified state if no waiters present /// /// [Inline] [NoHeapAllocation] protected ThreadQueueStruct SignalOneWithNoWakeup(SignalState signalStateIfNoWaiters) { Thread currentThread = Thread.CurrentThread; ThreadEntry nextThreadEntry; ThreadEntry waitDoneThreadEntry; int unblockerId = UninitWait; int entryId = UninitWait; // The method below should allocate struct on the stack!! ThreadQueueStruct deferredWakeupQueue = new ThreadQueueStruct(); bool shouldEnable = Processor.DisableInterrupts(); myLock.Acquire(currentThread); try { // Acquire lock. If you hit assert during acquire, and the code is handling // interrupt, you may need to use interrupt aware synchronization classes. // See the comment for InterruptAwareAutoResetEvent, InterruptAwareManualResetEvent, // and InterruptAwareMutex for details. ThreadEntryEnum threadEntryEnum = new ThreadEntryEnum(this.waitingQueue.Head); nextThreadEntry = threadEntryEnum.GetNext(); // Waitdone can move entry from one queue to another. Because of that we have // to be extremly careful. We move enumerator always one step ahead. if (nextThreadEntry != null) { do { waitDoneThreadEntry = nextThreadEntry; entryId = waitDoneThreadEntry.Id; // Move enumerator one step further - we have to do it before we call // WaitDone otherwise we might be enumerating wrong list because entry // can move lists during WaitDone call nextThreadEntry = threadEntryEnum.GetNext(); // Perform actual signal unblockerId = WaitDone(waitDoneThreadEntry, entryId, ref deferredWakeupQueue); // We will need to loop until we have an entry that we succefully signalled } while (entryId != unblockerId && nextThreadEntry != null); } // If we succesfully process signal mark state appropriately if (unblockerId != UninitWait && (entryId == unblockerId)) { // Make sure that state is no longer signalled this.signaled = SignalState.Unsignaled; } else { // Set state to specified by caller signaled = signalStateIfNoWaiters; } } finally { // Release lock myLock.Release(); // Reenable interrupts Processor.RestoreInterrupts(shouldEnable); } return deferredWakeupQueue; } /// /// /// Wakeup a single waiter in the deferred queue if there is one. /// /// /// The queue that contains the waiter thread /// [Inline] [NoHeapAllocation] protected static void WakeupOneWaiter(ThreadQueueStruct deferredWakeupQueue) { // Wakeup a waiter if we need to if (!deferredWakeupQueue.IsEmpty()) { // Retrieve thread from deferred queue Thread threadToWakeup = deferredWakeupQueue.DequeueHead().Thread; // Add the unblocked thread to the scheduler's runnable queue Kernel.TheScheduler.OnThreadUnblocked(threadToWakeup); // At this point queue should be empty! VTable.Assert(deferredWakeupQueue.IsEmpty()); } } /// /// /// Perform the work to signal all waiters and then set handle to specified stated. /// Put the signaled waiters to a deferred wakeup queue if there is any and /// return the queue. /// /// /// /// Set the wait handle into specified state if no waiters present /// /// /// /// Set the wait handle into specified state if waiter are present /// /// [NoHeapAllocation] protected ThreadQueueStruct SignalAllWithNoWakeup( SignalState signalStateIfNoWaiters, SignalState signalStateIfWaiters) { bool notified = false; Thread currentThread = Thread.CurrentThread; ThreadEntry waitDoneThreadEntry; ThreadEntry nextThreadEntry; ThreadQueueStruct deferredWakeupQueue = new ThreadQueueStruct(); int unblockerId = UninitWait; int numberOfUnblockedWaiters = 0; int entryId = UninitWait; // Acquire lock. If you hit assert during acquire, and the code is handling // interrupt, you may need to use interrupt aware synchronization classes. // See the comment for InterruptAwareAutoResetEvent, InterruptAwareManualResetEvent, // and InterruptAwareMutex for details. bool shouldEnable = Processor.DisableInterrupts(); myLock.Acquire(currentThread); try { ThreadEntryEnum threadEntryEnum = new ThreadEntryEnum(this.waitingQueue.Head); nextThreadEntry = threadEntryEnum.GetNext(); // Waitdone can move entry from one queue to another. Because of that we have // to be extremly careful. We move enumerator always one step ahead. if (nextThreadEntry != null) { do { waitDoneThreadEntry = nextThreadEntry; entryId = waitDoneThreadEntry.Id; // Move enumerator one step further - we have to do it before we call // WaitDone otherwise we might be enumerating wrong list because entry // can move lists during WaitDone call nextThreadEntry = threadEntryEnum.GetNext(); // We are ready to call waitdone unblockerId = WaitDone(waitDoneThreadEntry, entryId, ref deferredWakeupQueue); // Remember if we succesfully signalled someone if (unblockerId == entryId) { numberOfUnblockedWaiters++; } } while (nextThreadEntry != null); } // If we succesfully signalled someone, record state accordingly if (numberOfUnblockedWaiters > 0) { // Update signal state in case when waiters are present this.signaled = signalStateIfWaiters; } else { // Don't forget to update state when waiters are not present this.signaled = signalStateIfNoWaiters; } } finally { // Release lock myLock.Release(); // Reenable interrupts Processor.RestoreInterrupts(shouldEnable); } return deferredWakeupQueue; } /// /// /// Wakeup all waiters in the deferred queue if there are any /// /// /// The queue that contains the waiter threads /// [Inline] [NoHeapAllocation] protected static void WakeupAllWaiters(ThreadQueueStruct deferredWakeupQueue) { ThreadEntry entryToWakeup; // Unblock threads that we found we need to unblock while ((entryToWakeup = deferredWakeupQueue.DequeueHead()) != null) { // Get the thread Thread threadToWakeup = entryToWakeup.Thread; // Add the unblocked thread to the scheduler's runnable queue Kernel.TheScheduler.OnThreadUnblocked(threadToWakeup); } } /// /// /// Depending on the state of a handle decide either to acquire handle or wait /// /// /// /// Thread entry to enqueu onto waiting queue when can't satisfy request /// /// /// Id of the handle that we are trying to acquire - is used to check if thread can be unblocked /// /// [NoHeapAllocation] protected virtual bool AcquireOrEnqueue(ThreadEntry entry, int handleId) { bool didAcquire = true; Thread currentThread = Thread.CurrentThread; // Acquire lock. If you hit assert during acquire, and the code is handling // interrupt, you may need to use interrupt aware synchronization classes. // See the comment for InterruptAwareAutoResetEvent, InterruptAwareManualResetEvent, // and InterruptAwareMutex for details. bool shouldEnable = Processor.DisableInterrupts(); myLock.Acquire(currentThread); try { // If the handle is signaled and thread hasn't been unblocked yet. if ((this.signaled == SignalState.Signaled) && (currentThread.Unblock(handleId) == handleId)) { // Make signaled state to be as dictated by child class during initialization this.signaled = this.signalStateAfterImediateWait; } else { // Enqueue entry into wating queue: this.waitingQueue.EnqueueHead(entry); // We couldn't acquire object set return value properly didAcquire = false; } } finally { // Release lock myLock.Release(); // Reenable interrupts Processor.RestoreInterrupts(shouldEnable); } return didAcquire; } /// /// /// Associate a thread with wait handles if one of the waits satisfied return /// waithandle id - actual unblocker. if none of the states satisfied return UninitWait /// indicating that thread has to proceede with blocking /// /// protected static int PreWaitAnyInternal( Thread currentThread, WaitHandleBase[] waitHandles, ThreadEntry[] entries, int waitHandlesCount) { int marked; int released; int unblockedBy = UninitWait; VTable.Assert(currentThread.ThreadState == ThreadState.Running); // Prepare thread for blocking - Unblock call might be called from // either AcquireOrEnqueue or from WaitDone currentThread.PrepareForBlocking(); // Enqueue on all of the non-signaled handles. for (marked = 0; marked < waitHandlesCount; marked++) { // Initialize entry id entries[marked].Id = marked; // Attempt to wait on a handler if (waitHandles[marked].AcquireOrEnqueue(entries[marked], marked)) { // Our wait succeeded, remove ourselves from the wait queues for (released = 0; released < marked; released++) { waitHandles[released].Remove(entries[released]); } // Remember unblockedBy unblockedBy = currentThread.UnblockedBy; break; } } return unblockedBy; } /// /// /// Post wait is to dissasociate thread from all handlers /// /// protected static void PostWaitAnyInternal( Thread currentThread, WaitHandleBase[] waitHandles, ThreadEntry[] entries, int waitHandlesCount) { ThreadState state; int handlerIdx; int unblockedBy = WaitHandle.WaitTimeout; int resultUnblockedBy = WaitHandle.WaitTimeout; VTable.Assert(currentThread.ThreadState == ThreadState.Running); // Dequeue a thread from all hadndles it was waiting on for (handlerIdx = 0; handlerIdx < waitHandlesCount; handlerIdx++) { // Dissaociate handler from entry waitHandles[handlerIdx].Remove(entries[handlerIdx]); } } /// /// /// This method is called when a synchronization object completes the wait for /// thread. /// /// /// Thread entry to signal /// Id of wait handle /// Deferred queue to use for deferred wakeup /// [NoHeapAllocation] private int WaitDone( ThreadEntry entry, int handleId, ref ThreadQueueStruct deferredQueue) { ThreadState state; int handledIdUnblocked; // Assert preconditions: We assume that queues are stable - spinlock is held when // this method is called VTable.Assert(myLock.IsHeldBy(Thread.CurrentThread)); // Indicate that thread migth be given owrneship of the object so that it can't be aborted entry.Thread.DelayStop(true); //Attempt to unblock thread, if we fail it means that thread has already timed out // If thread has timed out don't move it to signaled queue if ((handledIdUnblocked = entry.Thread.Unblock(handleId)) == handleId) { // The signal is good-we can take a thread from non-signaled queue and // move it to signaled MoveEntryToSignalQueue(entry); // If thread state is blocked - we will be responsible for waking it up if (entry.Thread.ShouldCallSchedulerUnBlock(handleId)) { // Enqueue entry onto deferred unblock queue. We will call unblock // once we are done with processing signal and we released the lock deferredQueue.EnqueueTail(entry.Thread.deferredEntry); } } else { // We haven't granted ownership to the thread so that we need to restore its // delay abort status entry.Thread.DelayStop(false); } return handledIdUnblocked; } /// /// /// This method is called when a synchrnoization object completes the wait for /// thread. /// /// /// Thread entry to move from unsignaled queue to singlaed /// [NoHeapAllocation] private void MoveEntryToSignalQueue(ThreadEntry entry) { // Assert preconditions: Entry should be enqueued to nonsignaled queue protectiong // spinlock has to be taken VTable.Assert(waitingQueue.IsEnqueued(entry)); VTable.Assert(myLock.IsHeldBy(Thread.CurrentThread)); // Remove thread from waiting queue waitingQueue.Remove(entry); // Insert thread into signaled queue signaledQueue.EnqueueHead(entry); } /// /// /// Remove entry from the wait handle /// /// /// /// Entry to remove /// /// [NoHeapAllocation] private void Remove(ThreadEntry entry) { Thread currentThread = Thread.CurrentThread; // Acquire lock. If you hit assert during acquire, and the code is handling // interrupt, you may need to use interrupt aware synchronization classes. // See the comment for InterruptAwareAutoResetEvent, InterruptAwareManualResetEvent, // and InterruptAwareMutex for details. bool shouldEnable = Processor.DisableInterrupts(); myLock.Acquire(currentThread); try { // Assert preconditions: entry should be on a either non signaled queue or if it is // on signaled queue thread's unblocked Id should be equal to entry's Id // We can run this assert only while spinlock is held because first we unblock thread // and then moving it from one queue to another. We do both of these operations while // holding spinlock. It is possible for thread to start running once it was unblocked. // When it reaches this method it is possible that it hasn't been moved to signaled queue yet // If that happens and we try to assert without holding spinlock assert will fire. VTable.Assert(entry.queue == this.waitingQueue || (entry.queue == this.signaledQueue && entry.Id == currentThread.UnblockedBy)); // Remove entry from the queue entry.RemoveFromQueue(); } finally { // Release lock myLock.Release(); // Reenable interrupts Processor.RestoreInterrupts(shouldEnable); } } /// /// /// Return owner of a handle /// /// /// /// Consider removing this method /// [NoHeapAllocation] internal Thread GetBeneficiary() { return owner; } /// /// /// Complete wait - used by mutex to record ownership /// /// /// Thread owner /// [NoHeapAllocation] protected virtual void CompleteWait(Thread ownerThread) { return; } /// State of the handler private volatile SignalState signaled; /// Signal state after wait - policy of the child class protected SignalState signalStateAfterImediateWait; /// An owner of the handler protected volatile Thread owner; /// A waiting queue protected ThreadQueue waitingQueue; /// A signaled queue protected ThreadQueue signaledQueue; /// A unique id of wait handler protected int id; /// Uniqifier private static int idGenerator; /// Lock to protect queues protected SpinLock myLock; } }