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