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