1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Runtime.CompilerServices;
7 using Internal.Runtime.Augments;
8 using Microsoft.Win32.SafeHandles;
9 
10 namespace System.Threading
11 {
12     /// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
13     /// // Wait subsystem
14     ///
15     /// ////////////////////////////////////////////////////////////////
16     /// // Types
17     ///
18     /// <see cref="WaitSubsystem"/>
19     ///   - Static API surface for dealing with synchronization objects that support multi-wait, and to put a thread into a wait
20     ///     state, on Unix
21     ///   - Any interaction with the wait subsystem from outside should go through APIs on this class, and should not directly
22     ///     go through any of the nested classes
23     ///
24     /// <see cref="WaitableObject"/>
25     ///   - An object that supports the features of <see cref="EventWaitHandle"/>, <see cref="Semaphore"/>, and
26     ///     <see cref="Mutex"/>. The handle of each of those classes is associated with a <see cref="WaitableObject"/>.
27     ///
28     /// <see cref="ThreadWaitInfo"/>
29     ///   - Keeps information about a thread's wait and provides functionlity to put a thread into a wait state and to take it
30     ///     out of a wait state. Each thread has an instance available through <see cref="RuntimeThread.WaitInfo"/>.
31     ///
32     /// <see cref="HandleManager"/>
33     ///   - Provides functionality to allocate a handle associated with a <see cref="WaitableObject"/>, to retrieve the object
34     ///     from a handle, and to delete a handle.
35     ///
36     /// <see cref="WaitHandleArray{T}"/>
37     ///   - Used to precreate an array that will be used for storing information about a multi-wait operation, and dynamically
38     ///     resize up to the maximum capacity <see cref="WaitHandle.MaxWaitHandles"/> as needed
39     ///
40     /// <see cref="LowLevelLock"/> and <see cref="LowLevelMonitor"/>
41     ///   - These are "low level" in the sense they don't depend on this wait subsystem, and any waits done are not
42     ///     interruptible
43     ///   - <see cref="LowLevelLock"/> is used for the process-wide lock <see cref="s_lock"/>
44     ///   - <see cref="LowLevelMonitor"/> is the main system dependency of the wait subsystem, and all waits are done through
45     ///     it. It is backed by a C++ equivalent in CoreLib.Native's pal_threading.*, which wraps a pthread mutex/condition
46     ///     pair. Each thread has an instance in <see cref="ThreadWaitInfo._waitMonitor"/>, which is used to synchronize the
47     ///     thread's wait state and for waiting. <see cref="LowLevelLock"/> also uses an instance of
48     ///     <see cref="LowLevelMonitor"/> for waiting.
49     ///
50     /// ////////////////////////////////////////////////////////////////
51     /// // Design goals
52     ///
53     /// Behave similarly to wait operations on Windows
54     ///   - The design is similar to the one used by CoreCLR's PAL, but much simpler due to there being no need for supporting
55     ///     process/thread waits, or cross-process multi-waits (which CoreCLR also does not support but there are many design
56     ///     elements specific to it)
57     ///   - Waiting
58     ///     - A waiter keeps an array of objects on which it is waiting (see <see cref="ThreadWaitInfo._waitedObjects"/>).
59     ///     - The waiter registers a <see cref="ThreadWaitInfo.WaitedListNode"/> with each <see cref="WaitableObject"/>
60     ///     - The waiter waits on its own <see cref="ThreadWaitInfo._waitMonitor"/> to go into a wait state
61     ///     - Upon timeout, the waiter unregisters the wait and continues
62     ///   - Sleeping
63     ///     - Sleeping is just another way of waiting, only there would not be any waited objects
64     ///   - Signaling
65     ///     - A signaler iterates over waiters and tries to release waiters based on the signal count
66     ///     - For each waiter, the signaler checks if the waiter's wait can be terminated
67     ///     - When a waiter's wait can be terminated, the signaler does everything necesary before waking the waiter, such that
68     ///       the waiter can simply continue after awakening, including unregistering the wait and assigining ownership if
69     ///       applicable
70     ///   - Interrupting
71     ///     - Interrupting is just another way of signaling a waiting thread. The interrupter unregisters the wait and wakes the
72     ///       waiter.
73     ///   - Wait release fairness
74     ///     - As mentioned above in how signaling works, waiters are released in fair order (first come, first served)
75     ///     - This is mostly done to match the behavior of synchronization objects in Windows, which are also fair
76     ///     - Events have an implicit requirement to be fair
77     ///       - For a <see cref="ManualResetEvent"/>, Set/Reset in quick succession requires that it wakes up all waiters,
78     ///         implying that the design cannot be to signal a thread to wake and have it check the state when it awakens some
79     ///         time in the future
80     ///       - For an <see cref="AutoResetEvent"/>, Set/Set in quick succession requires that it wakes up two threads, implying
81     ///         that a Set/Wait in quick succession cannot have the calling thread accept its own signal if there is a waiter
82     ///     - There is an advantage to being fair, as it guarantees that threads are only awakened when necessary. That is, a
83     ///       thread will never wake up and find that it has to go back to sleep because the wait is not satisfied (except due
84     ///       to spurious wakeups caused by external factors).
85     ///   - Synchronization
86     ///     - A process-wide lock <see cref="s_lock"/> is used to synchronize most operations and the signal state of all
87     ///       <see cref="WaitableObject"/>s in the process. Given that it is recommended to use alternative synchronization
88     ///       types (<see cref="ManualResetEventSlim"/>, <see cref="SemaphoreSlim"/>, <see cref="Monitor"/>) for single-wait
89     ///       cases, it is probably not worth optimizing for the single-wait case. It is possible with a small design change to
90     ///       bypass the lock and use interlocked operations for uncontended cases, but at the cost of making multi-waits more
91     ///       complicated and slower.
92     ///     - The wait state of a thread (<see cref="ThreadWaitInfo._waitSignalState"/>), among other things, is synchornized
93     ///       using the thread's <see cref="ThreadWaitInfo._waitMonitor"/>, so signalers and interrupters acquire the monitor's
94     ///       lock before checking the wait state of a thread and signaling the thread to wake up.
95     ///
96     /// Self-consistent in the event of any exception
97     ///   - Try/finally is used extensively, including around any operation that could fail due to out-of-memory
98     ///
99     /// Decent balance between memory usage and performance
100     ///   - <see cref="WaitableObject"/> is intended to be as small as possible while avoiding virtual calls and casts
101     ///   - As <see cref="Mutex"/> is not commonly used and requires more state, some of its state is separated into
102     ///     <see cref="WaitableObject._ownershipInfo"/>
103     ///   - When support for cross-process objects is added, the current thought is to have an <see cref="object"/> field that
104     ///     is used for both cross-process state and ownership state.
105     ///
106     /// No allocation in typical cases of any operation except where necessary
107     ///   - Since the maximum number of wait handles for a multi-wait operation is limited to
108     ///     <see cref="WaitHandle.MaxWaitHandles"/>, arrays necessary for holding information about a multi-wait, and list nodes
109     ///     necessary for registering a wait, are precreated using <see cref="WaitHandleArray{T}"/> with a low initial capacity
110     ///     that covers most typical cases
111     ///   - Threads track owned mutexes by linking the <see cref="WaitableObject.OwnershipInfo"/> instance into a linked list
112     ///     <see cref="ThreadWaitInfo.LockedMutexesHead"/>. <see cref="WaitableObject.OwnershipInfo"/> is itself a list node,
113     ///     and is created along with the mutex <see cref="WaitableObject"/>.
114     ///
115     /// Minimal p/invokes in typical uncontended cases
116     ///   - <see cref="HandleManager"/> currently uses <see cref="Runtime.InteropServices.GCHandle"/> in the interest of
117     ///     simplicity, which p/invokes and does a cast to get the <see cref="WaitableObject"/> from a handle
118     ///   - Most of the wait subsystem is written in C#, so there is no initially required p/invoke
119     ///   - <see cref="LowLevelLock"/>, used by the process-wide lock <see cref="s_lock"/>, uses interlocked operations to
120     ///     acquire and release the lock when there is no need to wait or to release a waiter. This is significantly faster than
121     ///     using <see cref="LowLevelMonitor"/> as a lock, which uses pthread mutex functionality through p/invoke. The lock is
122     ///     typically not held for very long, especially since allocations inside the lock will be rare.
123     ///   - Since <see cref="s_lock"/> provides mutual exclusion for the states of all <see cref="WaitableObject"/>s in the
124     ///     process, any operation that does not involve waiting or releasing a wait can occur with minimal p/invokes
125     ///
126     /// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
127 
128     [EagerStaticClassConstruction] // the wait subsystem is used during lazy class construction
129     internal static partial class WaitSubsystem
130     {
131         private static readonly LowLevelLock s_lock = new LowLevelLock();
132 
NewHandle(WaitableObject waitableObject)133         private static SafeWaitHandle NewHandle(WaitableObject waitableObject)
134         {
135             IntPtr handle = HandleManager.NewHandle(waitableObject);
136             SafeWaitHandle safeWaitHandle = null;
137             try
138             {
139                 safeWaitHandle = new SafeWaitHandle(handle, ownsHandle: true);
140                 return safeWaitHandle;
141             }
142             finally
143             {
144                 if (safeWaitHandle == null)
145                 {
146                     HandleManager.DeleteHandle(handle);
147                 }
148             }
149         }
150 
NewEvent(bool initiallySignaled, EventResetMode resetMode)151         public static SafeWaitHandle NewEvent(bool initiallySignaled, EventResetMode resetMode)
152         {
153             return NewHandle(WaitableObject.NewEvent(initiallySignaled, resetMode));
154         }
155 
NewSemaphore(int initialSignalCount, int maximumSignalCount)156         public static SafeWaitHandle NewSemaphore(int initialSignalCount, int maximumSignalCount)
157         {
158             return NewHandle(WaitableObject.NewSemaphore(initialSignalCount, maximumSignalCount));
159         }
160 
NewMutex(bool initiallyOwned)161         public static SafeWaitHandle NewMutex(bool initiallyOwned)
162         {
163             WaitableObject waitableObject = WaitableObject.NewMutex();
164             SafeWaitHandle safeWaitHandle = NewHandle(waitableObject);
165             if (!initiallyOwned)
166             {
167                 return safeWaitHandle;
168             }
169 
170             /// Acquire the mutex. A thread's <see cref="ThreadWaitInfo"/> has a reference to all <see cref="Mutex"/>es locked
171             /// by the thread. See <see cref="ThreadWaitInfo.LockedMutexesHead"/>. So, acquire the lock only after all
172             /// possibilities for exceptions have been exhausted.
173             ThreadWaitInfo waitInfo = RuntimeThread.CurrentThread.WaitInfo;
174             bool acquiredLock = waitableObject.Wait(waitInfo, timeoutMilliseconds: 0, interruptible: false, prioritize: false);
175             Debug.Assert(acquiredLock);
176             return safeWaitHandle;
177         }
178 
DeleteHandle(IntPtr handle)179         public static void DeleteHandle(IntPtr handle)
180         {
181             HandleManager.DeleteHandle(handle);
182         }
183 
SetEvent(IntPtr handle)184         public static void SetEvent(IntPtr handle)
185         {
186             SetEvent(HandleManager.FromHandle(handle));
187         }
188 
SetEvent(WaitableObject waitableObject)189         public static void SetEvent(WaitableObject waitableObject)
190         {
191             Debug.Assert(waitableObject != null);
192 
193             s_lock.Acquire();
194             try
195             {
196                 waitableObject.SignalEvent();
197             }
198             finally
199             {
200                 s_lock.Release();
201             }
202         }
203 
ResetEvent(IntPtr handle)204         public static void ResetEvent(IntPtr handle)
205         {
206             ResetEvent(HandleManager.FromHandle(handle));
207         }
208 
ResetEvent(WaitableObject waitableObject)209         public static void ResetEvent(WaitableObject waitableObject)
210         {
211             Debug.Assert(waitableObject != null);
212 
213             s_lock.Acquire();
214             try
215             {
216                 waitableObject.UnsignalEvent();
217             }
218             finally
219             {
220                 s_lock.Release();
221             }
222         }
223 
ReleaseSemaphore(IntPtr handle, int count)224         public static int ReleaseSemaphore(IntPtr handle, int count)
225         {
226             Debug.Assert(count > 0);
227             return ReleaseSemaphore(HandleManager.FromHandle(handle), count);
228         }
229 
ReleaseSemaphore(WaitableObject waitableObject, int count)230         public static int ReleaseSemaphore(WaitableObject waitableObject, int count)
231         {
232             Debug.Assert(waitableObject != null);
233             Debug.Assert(count > 0);
234 
235             s_lock.Acquire();
236             try
237             {
238                 return waitableObject.SignalSemaphore(count);
239             }
240             finally
241             {
242                 s_lock.Release();
243             }
244         }
245 
ReleaseMutex(IntPtr handle)246         public static void ReleaseMutex(IntPtr handle)
247         {
248             ReleaseMutex(HandleManager.FromHandle(handle));
249         }
250 
ReleaseMutex(WaitableObject waitableObject)251         public static void ReleaseMutex(WaitableObject waitableObject)
252         {
253             Debug.Assert(waitableObject != null);
254 
255             s_lock.Acquire();
256             try
257             {
258                 waitableObject.SignalMutex();
259             }
260             finally
261             {
262                 s_lock.Release();
263             }
264         }
265 
Wait(IntPtr handle, int timeoutMilliseconds, bool interruptible)266         public static bool Wait(IntPtr handle, int timeoutMilliseconds, bool interruptible)
267         {
268             Debug.Assert(timeoutMilliseconds >= -1);
269             return Wait(HandleManager.FromHandle(handle), timeoutMilliseconds, interruptible);
270         }
271 
Wait( WaitableObject waitableObject, int timeoutMilliseconds, bool interruptible = true, bool prioritize = false)272         public static bool Wait(
273             WaitableObject waitableObject,
274             int timeoutMilliseconds,
275             bool interruptible = true,
276             bool prioritize = false)
277         {
278             Debug.Assert(waitableObject != null);
279             Debug.Assert(timeoutMilliseconds >= -1);
280 
281             return waitableObject.Wait(RuntimeThread.CurrentThread.WaitInfo, timeoutMilliseconds, interruptible, prioritize);
282         }
283 
Wait( RuntimeThread currentThread, SafeWaitHandle[] safeWaitHandles, WaitHandle[] waitHandles, int numWaitHandles, bool waitForAll, int timeoutMilliseconds)284         public static int Wait(
285             RuntimeThread currentThread,
286             SafeWaitHandle[] safeWaitHandles,
287             WaitHandle[] waitHandles,
288             int numWaitHandles,
289             bool waitForAll,
290             int timeoutMilliseconds)
291         {
292             Debug.Assert(currentThread == RuntimeThread.CurrentThread);
293             Debug.Assert(safeWaitHandles != null);
294             Debug.Assert(numWaitHandles > 0);
295             Debug.Assert(numWaitHandles <= safeWaitHandles.Length);
296             Debug.Assert(numWaitHandles <= waitHandles.Length);
297             Debug.Assert(numWaitHandles <= WaitHandle.MaxWaitHandles);
298             Debug.Assert(timeoutMilliseconds >= -1);
299 
300             ThreadWaitInfo waitInfo = currentThread.WaitInfo;
301             WaitableObject[] waitableObjects = waitInfo.GetWaitedObjectArray(numWaitHandles);
302             bool success = false;
303             try
304             {
305                 for (int i = 0; i < numWaitHandles; ++i)
306                 {
307                     Debug.Assert(safeWaitHandles[i] != null);
308                     WaitableObject waitableObject = HandleManager.FromHandle(safeWaitHandles[i].DangerousGetHandle());
309                     if (waitForAll)
310                     {
311                         /// Check if this is a duplicate, as wait-for-all does not support duplicates. Including the parent
312                         /// loop, this becomes a brute force O(n^2) search, which is intended since the typical array length is
313                         /// short enough that this would actually be faster than other alternatives. Also, the worst case is not
314                         /// so bad considering that the array length is limited by <see cref="WaitHandle.MaxWaitHandles"/>.
315                         for (int j = 0; j < i; ++j)
316                         {
317                             if (waitableObject == waitableObjects[j])
318                             {
319                                 throw new DuplicateWaitObjectException("waitHandles[" + i + ']');
320                             }
321                         }
322                     }
323 
324                     waitableObjects[i] = waitableObject;
325                 }
326                 success = true;
327             }
328             finally
329             {
330                 if (!success)
331                 {
332                     for (int i = 0; i < numWaitHandles; ++i)
333                     {
334                         waitableObjects[i] = null;
335                     }
336                 }
337             }
338 
339             if (numWaitHandles == 1)
340             {
341                 WaitableObject waitableObject = waitableObjects[0];
342                 waitableObjects[0] = null;
343                 return
344                     waitableObject.Wait(waitInfo, timeoutMilliseconds, interruptible: true, prioritize : false)
345                         ? 0
346                         : WaitHandle.WaitTimeout;
347             }
348 
349             return
350                 WaitableObject.Wait(
351                     waitableObjects,
352                     numWaitHandles,
353                     waitForAll,
354                     waitInfo,
355                     timeoutMilliseconds,
356                     interruptible: true,
357                     prioritize: false,
358                     waitHandlesForAbandon: waitHandles);
359         }
360 
Wait( RuntimeThread currentThread, WaitableObject waitableObject0, WaitableObject waitableObject1, bool waitForAll, int timeoutMilliseconds, bool interruptible = true, bool prioritize = false)361         public static int Wait(
362             RuntimeThread currentThread,
363             WaitableObject waitableObject0,
364             WaitableObject waitableObject1,
365             bool waitForAll,
366             int timeoutMilliseconds,
367             bool interruptible = true,
368             bool prioritize = false)
369         {
370             Debug.Assert(currentThread == RuntimeThread.CurrentThread);
371             Debug.Assert(waitableObject0 != null);
372             Debug.Assert(waitableObject1 != null);
373             Debug.Assert(waitableObject1 != waitableObject0);
374             Debug.Assert(timeoutMilliseconds >= -1);
375 
376             ThreadWaitInfo waitInfo = currentThread.WaitInfo;
377             int count = 2;
378             WaitableObject[] waitableObjects = waitInfo.GetWaitedObjectArray(count);
379             waitableObjects[0] = waitableObject0;
380             waitableObjects[1] = waitableObject1;
381             return
382                 WaitableObject.Wait(
383                     waitableObjects,
384                     count,
385                     waitForAll,
386                     waitInfo,
387                     timeoutMilliseconds,
388                     interruptible,
389                     prioritize,
390                     waitHandlesForAbandon: null);
391         }
392 
SignalAndWait( IntPtr handleToSignal, IntPtr handleToWaitOn, int timeoutMilliseconds)393         public static bool SignalAndWait(
394             IntPtr handleToSignal,
395             IntPtr handleToWaitOn,
396             int timeoutMilliseconds)
397         {
398             Debug.Assert(timeoutMilliseconds >= -1);
399 
400             return
401                 SignalAndWait(
402                     HandleManager.FromHandle(handleToSignal),
403                     HandleManager.FromHandle(handleToWaitOn),
404                     timeoutMilliseconds);
405         }
406 
SignalAndWait( WaitableObject waitableObjectToSignal, WaitableObject waitableObjectToWaitOn, int timeoutMilliseconds, bool interruptible = true, bool prioritize = false)407         public static bool SignalAndWait(
408             WaitableObject waitableObjectToSignal,
409             WaitableObject waitableObjectToWaitOn,
410             int timeoutMilliseconds,
411             bool interruptible = true,
412             bool prioritize = false)
413         {
414             Debug.Assert(waitableObjectToSignal != null);
415             Debug.Assert(waitableObjectToWaitOn != null);
416             Debug.Assert(timeoutMilliseconds >= -1);
417 
418             ThreadWaitInfo waitInfo = RuntimeThread.CurrentThread.WaitInfo;
419             bool waitCalled = false;
420             s_lock.Acquire();
421             try
422             {
423                 // A pending interrupt does not signal the specified handle
424                 if (interruptible && waitInfo.CheckAndResetPendingInterrupt)
425                 {
426                     throw new ThreadInterruptedException();
427                 }
428 
429                 waitableObjectToSignal.Signal(1);
430                 waitCalled = true;
431                 return waitableObjectToWaitOn.Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize);
432             }
433             finally
434             {
435                 // Once the wait function is called, it will release the lock
436                 if (waitCalled)
437                 {
438                     s_lock.VerifyIsNotLocked();
439                 }
440                 else
441                 {
442                     s_lock.Release();
443                 }
444             }
445         }
446 
UninterruptibleSleep0()447         public static void UninterruptibleSleep0()
448         {
449             ThreadWaitInfo.UninterruptibleSleep0();
450         }
451 
Sleep(int timeoutMilliseconds, bool interruptible = true)452         public static void Sleep(int timeoutMilliseconds, bool interruptible = true)
453         {
454             ThreadWaitInfo.Sleep(timeoutMilliseconds, interruptible);
455         }
456 
Interrupt(RuntimeThread thread)457         public static void Interrupt(RuntimeThread thread)
458         {
459             Debug.Assert(thread != null);
460 
461             s_lock.Acquire();
462             try
463             {
464                 thread.WaitInfo.TrySignalToInterruptWaitOrRecordPendingInterrupt();
465             }
466             finally
467             {
468                 s_lock.Release();
469             }
470         }
471 
OnThreadExiting(RuntimeThread thread)472         public static void OnThreadExiting(RuntimeThread thread)
473         {
474             thread.WaitInfo.OnThreadExiting();
475         }
476     }
477 }
478