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.InteropServices; 7 using Microsoft.Win32.SafeHandles; 8 9 namespace System.Threading 10 { 11 // 12 // Portable implementation of ThreadPool 13 // 14 15 /// <summary> 16 /// An object representing the registration of a <see cref="WaitHandle"/> via <see cref="ThreadPool.RegisterWaitForSingleObject"/>. 17 /// </summary> 18 public sealed class RegisteredWaitHandle : MarshalByRefObject 19 { RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, int millisecondsTimeout, bool repeating)20 internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, 21 int millisecondsTimeout, bool repeating) 22 { 23 Handle = waitHandle; 24 Callback = callbackHelper; 25 TimeoutDurationMs = millisecondsTimeout; 26 Repeating = repeating; 27 RestartTimeout(Environment.TickCount); 28 } 29 ~RegisteredWaitHandle()30 ~RegisteredWaitHandle() 31 { 32 if(WaitThread != null) 33 { 34 Unregister(null); 35 } 36 } 37 38 private static AutoResetEvent s_cachedEvent; 39 RentEvent()40 private static AutoResetEvent RentEvent() 41 { 42 AutoResetEvent resetEvent = Interlocked.Exchange(ref s_cachedEvent, (AutoResetEvent)null); 43 if (resetEvent == null) 44 { 45 resetEvent = new AutoResetEvent(false); 46 } 47 return resetEvent; 48 } 49 ReturnEvent(AutoResetEvent resetEvent)50 private static void ReturnEvent(AutoResetEvent resetEvent) 51 { 52 if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null) 53 { 54 resetEvent.Dispose(); 55 } 56 } 57 58 /// <summary> 59 /// The callback to execute when the wait on <see cref="Handle"/> either times out or completes. 60 /// </summary> 61 internal _ThreadPoolWaitOrTimerCallback Callback { get; } 62 63 64 /// <summary> 65 /// The <see cref="WaitHandle"/> that was registered. 66 /// </summary> 67 internal WaitHandle Handle { get; } 68 69 /// <summary> 70 /// The time this handle times out at in ms. 71 /// </summary> 72 internal int TimeoutTimeMs { get; private set; } 73 74 private int TimeoutDurationMs { get; } 75 76 internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; 77 RestartTimeout(int currentTimeMs)78 internal void RestartTimeout(int currentTimeMs) 79 { 80 TimeoutTimeMs = currentTimeMs + TimeoutDurationMs; 81 } 82 83 /// <summary> 84 /// Whether or not the wait is a repeating wait. 85 /// </summary> 86 internal bool Repeating { get; } 87 88 /// <summary> 89 /// The <see cref="WaitHandle"/> the user passed in via <see cref="Unregister(WaitHandle)"/>. 90 /// </summary> 91 private SafeWaitHandle UserUnregisterWaitHandle { get; set; } 92 93 private IntPtr UserUnregisterWaitHandleValue { get; set; } 94 95 internal bool IsBlocking => UserUnregisterWaitHandleValue == (IntPtr)(-1); 96 97 /// <summary> 98 /// The <see cref="ClrThreadPool.WaitThread"/> this <see cref="RegisteredWaitHandle"/> was registered on. 99 /// </summary> 100 internal ClrThreadPool.WaitThread WaitThread { get; set; } 101 102 /// <summary> 103 /// The number of callbacks that are currently queued on the Thread Pool or executing. 104 /// </summary> 105 private int _numRequestedCallbacks; 106 107 private LowLevelLock _callbackLock = new LowLevelLock(); 108 109 /// <summary> 110 /// Notes if we need to signal the user's unregister event after all callbacks complete. 111 /// </summary> 112 private bool _signalAfterCallbacksComplete; 113 114 private bool _unregisterCalled; 115 116 private bool _unregistered; 117 118 private AutoResetEvent _callbacksComplete; 119 120 private AutoResetEvent _removed; 121 122 /// <summary> 123 /// Unregisters this wait handle registration from the wait threads. 124 /// </summary> 125 /// <param name="waitObject">The event to signal when the handle is unregistered.</param> 126 /// <returns>If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event.</returns> 127 /// <remarks> 128 /// This method will only return true on the first call. 129 /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. 130 /// </remarks> Unregister(WaitHandle waitObject)131 public bool Unregister(WaitHandle waitObject) 132 { 133 GC.SuppressFinalize(this); 134 _callbackLock.Acquire(); 135 bool needToRollBackRefCountOnException = false; 136 try 137 { 138 if (_unregisterCalled) 139 { 140 return false; 141 } 142 143 UserUnregisterWaitHandle = waitObject?.SafeWaitHandle; 144 UserUnregisterWaitHandle?.DangerousAddRef(); 145 needToRollBackRefCountOnException = true; 146 147 UserUnregisterWaitHandleValue = UserUnregisterWaitHandle?.DangerousGetHandle() ?? IntPtr.Zero; 148 149 if (_unregistered) 150 { 151 SignalUserWaitHandle(); 152 return true; 153 } 154 155 if (IsBlocking) 156 { 157 _callbacksComplete = RentEvent(); 158 } 159 else 160 { 161 _removed = RentEvent(); 162 } 163 _unregisterCalled = true; 164 } 165 catch (Exception) // Rollback state on exception 166 { 167 if (_removed != null) 168 { 169 ReturnEvent(_removed); 170 _removed = null; 171 } 172 else if (_callbacksComplete != null) 173 { 174 ReturnEvent(_callbacksComplete); 175 _callbacksComplete = null; 176 } 177 178 UserUnregisterWaitHandleValue = IntPtr.Zero; 179 180 if (needToRollBackRefCountOnException) 181 { 182 UserUnregisterWaitHandle?.DangerousRelease(); 183 } 184 185 UserUnregisterWaitHandle = null; 186 throw; 187 } 188 finally 189 { 190 _callbackLock.Release(); 191 } 192 193 WaitThread.UnregisterWait(this); 194 return true; 195 } 196 197 /// <summary> 198 /// Signal <see cref="UserUnregisterWaitHandle"/> if it has not been signaled yet and is a valid handle. 199 /// </summary> SignalUserWaitHandle()200 private void SignalUserWaitHandle() 201 { 202 _callbackLock.VerifyIsLocked(); 203 SafeWaitHandle handle = UserUnregisterWaitHandle; 204 IntPtr handleValue = UserUnregisterWaitHandleValue; 205 try 206 { 207 if (handleValue != IntPtr.Zero && handleValue != (IntPtr)(-1)) 208 { 209 EventWaitHandle.Set(handleValue); 210 } 211 } 212 finally 213 { 214 handle?.DangerousRelease(); 215 _callbacksComplete?.Set(); 216 _unregistered = true; 217 } 218 } 219 220 /// <summary> 221 /// Perform the registered callback if the <see cref="UserUnregisterWaitHandle"/> has not been signaled. 222 /// </summary> 223 /// <param name="timedOut">Whether or not the wait timed out.</param> PerformCallback(bool timedOut)224 internal void PerformCallback(bool timedOut) 225 { 226 #if DEBUG 227 _callbackLock.Acquire(); 228 try 229 { 230 Debug.Assert(_numRequestedCallbacks != 0); 231 } 232 finally 233 { 234 _callbackLock.Release(); 235 } 236 #endif 237 _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut); 238 CompleteCallbackRequest(); 239 } 240 241 /// <summary> 242 /// Tell this handle that there is a callback queued on the thread pool for this handle. 243 /// </summary> RequestCallback()244 internal void RequestCallback() 245 { 246 _callbackLock.Acquire(); 247 try 248 { 249 _numRequestedCallbacks++; 250 } 251 finally 252 { 253 _callbackLock.Release(); 254 } 255 } 256 257 /// <summary> 258 /// Called when the wait thread removes this handle registration. This will signal the user's event if there are no callbacks pending, 259 /// or note that the user's event must be signaled when the callbacks complete. 260 /// </summary> OnRemoveWait()261 internal void OnRemoveWait() 262 { 263 _callbackLock.Acquire(); 264 try 265 { 266 _removed?.Set(); 267 if (_numRequestedCallbacks == 0) 268 { 269 SignalUserWaitHandle(); 270 } 271 else 272 { 273 _signalAfterCallbacksComplete = true; 274 } 275 } 276 finally 277 { 278 _callbackLock.Release(); 279 } 280 } 281 282 /// <summary> 283 /// Reduces the number of callbacks requested. If there are no more callbacks and the user's handle is queued to be signaled, signal it. 284 /// </summary> CompleteCallbackRequest()285 private void CompleteCallbackRequest() 286 { 287 _callbackLock.Acquire(); 288 try 289 { 290 --_numRequestedCallbacks; 291 if (_numRequestedCallbacks == 0 && _signalAfterCallbacksComplete) 292 { 293 SignalUserWaitHandle(); 294 } 295 } 296 finally 297 { 298 _callbackLock.Release(); 299 } 300 } 301 302 /// <summary> 303 /// Wait for all queued callbacks and the full unregistration to complete. 304 /// </summary> WaitForCallbacks()305 internal void WaitForCallbacks() 306 { 307 Debug.Assert(IsBlocking); 308 Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. 309 310 _callbacksComplete.WaitOne(); 311 ReturnEvent(_callbacksComplete); 312 _callbacksComplete = null; 313 } 314 WaitForRemoval()315 internal void WaitForRemoval() 316 { 317 Debug.Assert(!IsBlocking); 318 Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. 319 320 _removed.WaitOne(); 321 ReturnEvent(_removed); 322 _removed = null; 323 } 324 } 325 326 public static partial class ThreadPool 327 { SetMaxThreads(int workerThreads, int completionPortThreads)328 public static bool SetMaxThreads(int workerThreads, int completionPortThreads) 329 { 330 if (workerThreads < 0 || completionPortThreads < 0) 331 { 332 return false; 333 } 334 return ClrThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads); 335 } 336 GetMaxThreads(out int workerThreads, out int completionPortThreads)337 public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) 338 { 339 // Note that worker threads and completion port threads share the same thread pool. 340 // The total number of threads cannot exceed MaxThreadCount. 341 workerThreads = ClrThreadPool.ThreadPoolInstance.GetMaxThreads(); 342 completionPortThreads = 1; 343 } 344 SetMinThreads(int workerThreads, int completionPortThreads)345 public static bool SetMinThreads(int workerThreads, int completionPortThreads) 346 { 347 if (workerThreads < 0 || completionPortThreads < 0) 348 { 349 return false; 350 } 351 return ClrThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads); 352 } 353 GetMinThreads(out int workerThreads, out int completionPortThreads)354 public static void GetMinThreads(out int workerThreads, out int completionPortThreads) 355 { 356 // All threads are pre-created at present 357 workerThreads = ClrThreadPool.ThreadPoolInstance.GetMinThreads(); 358 completionPortThreads = 0; 359 } 360 GetAvailableThreads(out int workerThreads, out int completionPortThreads)361 public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) 362 { 363 workerThreads = ClrThreadPool.ThreadPoolInstance.GetAvailableThreads(); 364 completionPortThreads = 0; 365 } 366 367 /// <summary> 368 /// This method is called to request a new thread pool worker to handle pending work. 369 /// </summary> RequestWorkerThread()370 internal static void RequestWorkerThread() 371 { 372 ClrThreadPool.ThreadPoolInstance.RequestWorker(); 373 } 374 KeepDispatching(int startTickCount)375 internal static bool KeepDispatching(int startTickCount) 376 { 377 return true; 378 } 379 NotifyWorkItemProgress()380 internal static void NotifyWorkItemProgress() 381 { 382 ClrThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); 383 } 384 NotifyWorkItemComplete()385 internal static bool NotifyWorkItemComplete() 386 { 387 return ClrThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); 388 } 389 RegisterWaitForSingleObject( WaitHandle waitObject, WaitOrTimerCallback callBack, Object state, uint millisecondsTimeOutInterval, bool executeOnlyOnce, bool flowExecutionContext)390 private static RegisteredWaitHandle RegisterWaitForSingleObject( 391 WaitHandle waitObject, 392 WaitOrTimerCallback callBack, 393 Object state, 394 uint millisecondsTimeOutInterval, 395 bool executeOnlyOnce, 396 bool flowExecutionContext) 397 { 398 if (waitObject == null) 399 throw new ArgumentNullException(nameof(waitObject)); 400 401 if (callBack == null) 402 throw new ArgumentNullException(nameof(callBack)); 403 404 RegisteredWaitHandle registeredHandle = new RegisteredWaitHandle( 405 waitObject, 406 new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext), 407 (int)millisecondsTimeOutInterval, 408 !executeOnlyOnce); 409 ClrThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredHandle); 410 return registeredHandle; 411 } 412 } 413 } 414