064227bf51b6ca6d13721dd160e3a0e23f7b1874
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / shared / System / Threading / ThreadPool.cs
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 /*=============================================================================
6 **
7 **
8 **
9 ** Purpose: Class for creating and managing a threadpool
10 **
11 **
12 =============================================================================*/
13
14 using System.Collections.Concurrent;
15 using System.Collections.Generic;
16 using System.Diagnostics;
17 using System.Diagnostics.CodeAnalysis;
18 using System.Diagnostics.Tracing;
19 using System.Runtime.CompilerServices;
20 using System.Runtime.InteropServices;
21 using System.Threading.Tasks;
22 using Internal.Runtime.CompilerServices;
23
24 namespace System.Threading
25 {
26     internal static class ThreadPoolGlobals
27     {
28         public static readonly int processorCount = Environment.ProcessorCount;
29
30         public static volatile bool threadPoolInitialized;
31         public static bool enableWorkerTracking;
32
33         public static readonly ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue();
34
35         /// <summary>Shim used to invoke <see cref="IAsyncStateMachineBox.MoveNext"/> of the supplied <see cref="IAsyncStateMachineBox"/>.</summary>
36         internal static readonly Action<object?> s_invokeAsyncStateMachineBox = state =>
37         {
38             if (!(state is IAsyncStateMachineBox box))
39             {
40                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
41                 return;
42             }
43
44             box.MoveNext();
45         };
46     }
47
48     [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing
49     internal sealed class ThreadPoolWorkQueue
50     {
51         internal static class WorkStealingQueueList
52         {
53             private static volatile WorkStealingQueue[] _queues = new WorkStealingQueue[0];
54
55             public static WorkStealingQueue[] Queues => _queues;
56
57             public static void Add(WorkStealingQueue queue)
58             {
59                 Debug.Assert(queue != null);
60                 while (true)
61                 {
62                     WorkStealingQueue[] oldQueues = _queues;
63                     Debug.Assert(Array.IndexOf(oldQueues, queue) == -1);
64
65                     var newQueues = new WorkStealingQueue[oldQueues.Length + 1];
66                     Array.Copy(oldQueues, 0, newQueues, 0, oldQueues.Length);
67                     newQueues[newQueues.Length - 1] = queue;
68                     if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
69                     {
70                         break;
71                     }
72                 }
73             }
74
75             public static void Remove(WorkStealingQueue queue)
76             {
77                 Debug.Assert(queue != null);
78                 while (true)
79                 {
80                     WorkStealingQueue[] oldQueues = _queues;
81                     if (oldQueues.Length == 0)
82                     {
83                         return;
84                     }
85
86                     int pos = Array.IndexOf(oldQueues, queue);
87                     if (pos == -1)
88                     {
89                         Debug.Fail("Should have found the queue");
90                         return;
91                     }
92
93                     var newQueues = new WorkStealingQueue[oldQueues.Length - 1];
94                     if (pos == 0)
95                     {
96                         Array.Copy(oldQueues, 1, newQueues, 0, newQueues.Length);
97                     }
98                     else if (pos == oldQueues.Length - 1)
99                     {
100                         Array.Copy(oldQueues, 0, newQueues, 0, newQueues.Length);
101                     }
102                     else
103                     {
104                         Array.Copy(oldQueues, 0, newQueues, 0, pos);
105                         Array.Copy(oldQueues, pos + 1, newQueues, pos, newQueues.Length - pos);
106                     }
107
108                     if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
109                     {
110                         break;
111                     }
112                 }
113             }
114         }
115
116         internal sealed class WorkStealingQueue
117         {
118             private const int INITIAL_SIZE = 32;
119             internal volatile object?[] m_array = new object[INITIAL_SIZE]; // SOS's ThreadPool command depends on this name
120             private volatile int m_mask = INITIAL_SIZE - 1;
121
122 #if DEBUG
123             // in debug builds, start at the end so we exercise the index reset logic.
124             private const int START_INDEX = int.MaxValue;
125 #else
126             private const int START_INDEX = 0;
127 #endif
128
129             private volatile int m_headIndex = START_INDEX;
130             private volatile int m_tailIndex = START_INDEX;
131
132             private SpinLock m_foreignLock = new SpinLock(enableThreadOwnerTracking: false);
133
134             public void LocalPush(object obj)
135             {
136                 int tail = m_tailIndex;
137
138                 // We're going to increment the tail; if we'll overflow, then we need to reset our counts
139                 if (tail == int.MaxValue)
140                 {
141                     bool lockTaken = false;
142                     try
143                     {
144                         m_foreignLock.Enter(ref lockTaken);
145
146                         if (m_tailIndex == int.MaxValue)
147                         {
148                             //
149                             // Rather than resetting to zero, we'll just mask off the bits we don't care about.
150                             // This way we don't need to rearrange the items already in the queue; they'll be found
151                             // correctly exactly where they are.  One subtlety here is that we need to make sure that
152                             // if head is currently < tail, it remains that way.  This happens to just fall out from
153                             // the bit-masking, because we only do this if tail == int.MaxValue, meaning that all
154                             // bits are set, so all of the bits we're keeping will also be set.  Thus it's impossible
155                             // for the head to end up > than the tail, since you can't set any more bits than all of 
156                             // them.
157                             //
158                             m_headIndex = m_headIndex & m_mask;
159                             m_tailIndex = tail = m_tailIndex & m_mask;
160                             Debug.Assert(m_headIndex <= m_tailIndex);
161                         }
162                     }
163                     finally
164                     {
165                         if (lockTaken)
166                             m_foreignLock.Exit(useMemoryBarrier: true);
167                     }
168                 }
169
170                 // When there are at least 2 elements' worth of space, we can take the fast path.
171                 if (tail < m_headIndex + m_mask)
172                 {
173                     Volatile.Write(ref m_array[tail & m_mask], obj);
174                     m_tailIndex = tail + 1;
175                 }
176                 else
177                 {
178                     // We need to contend with foreign pops, so we lock.
179                     bool lockTaken = false;
180                     try
181                     {
182                         m_foreignLock.Enter(ref lockTaken);
183
184                         int head = m_headIndex;
185                         int count = m_tailIndex - m_headIndex;
186
187                         // If there is still space (one left), just add the element.
188                         if (count >= m_mask)
189                         {
190                             // We're full; expand the queue by doubling its size.
191                             var newArray = new object?[m_array.Length << 1];
192                             for (int i = 0; i < m_array.Length; i++)
193                                 newArray[i] = m_array[(i + head) & m_mask];
194
195                             // Reset the field values, incl. the mask.
196                             m_array = newArray;
197                             m_headIndex = 0;
198                             m_tailIndex = tail = count;
199                             m_mask = (m_mask << 1) | 1;
200                         }
201
202                         Volatile.Write(ref m_array[tail & m_mask], obj);
203                         m_tailIndex = tail + 1;
204                     }
205                     finally
206                     {
207                         if (lockTaken)
208                             m_foreignLock.Exit(useMemoryBarrier: false);
209                     }
210                 }
211             }
212
213             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
214             public bool LocalFindAndPop(object obj)
215             {
216                 // Fast path: check the tail. If equal, we can skip the lock.
217                 if (m_array[(m_tailIndex - 1) & m_mask] == obj)
218                 {
219                     object? unused = LocalPop();
220                     Debug.Assert(unused == null || unused == obj);
221                     return unused != null;
222                 }
223
224                 // Else, do an O(N) search for the work item. The theory of work stealing and our
225                 // inlining logic is that most waits will happen on recently queued work.  And
226                 // since recently queued work will be close to the tail end (which is where we
227                 // begin our search), we will likely find it quickly.  In the worst case, we
228                 // will traverse the whole local queue; this is typically not going to be a
229                 // problem (although degenerate cases are clearly an issue) because local work
230                 // queues tend to be somewhat shallow in length, and because if we fail to find
231                 // the work item, we are about to block anyway (which is very expensive).
232                 for (int i = m_tailIndex - 2; i >= m_headIndex; i--)
233                 {
234                     if (m_array[i & m_mask] == obj)
235                     {
236                         // If we found the element, block out steals to avoid interference.
237                         bool lockTaken = false;
238                         try
239                         {
240                             m_foreignLock.Enter(ref lockTaken);
241
242                             // If we encountered a race condition, bail.
243                             if (m_array[i & m_mask] == null)
244                                 return false;
245
246                             // Otherwise, null out the element.
247                             Volatile.Write(ref m_array[i & m_mask], null);
248
249                             // And then check to see if we can fix up the indexes (if we're at
250                             // the edge).  If we can't, we just leave nulls in the array and they'll
251                             // get filtered out eventually (but may lead to superfluous resizing).
252                             if (i == m_tailIndex)
253                                 m_tailIndex -= 1;
254                             else if (i == m_headIndex)
255                                 m_headIndex += 1;
256
257                             return true;
258                         }
259                         finally
260                         {
261                             if (lockTaken)
262                                 m_foreignLock.Exit(useMemoryBarrier: false);
263                         }
264                     }
265                 }
266
267                 return false;
268             }
269
270             public object? LocalPop() => m_headIndex < m_tailIndex ? LocalPopCore() : null;
271
272             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
273             private object? LocalPopCore()
274             {
275                 while (true)
276                 {
277                     int tail = m_tailIndex;
278                     if (m_headIndex >= tail)
279                     {
280                         return null;
281                     }
282
283                     // Decrement the tail using a fence to ensure subsequent read doesn't come before.
284                     tail -= 1;
285                     Interlocked.Exchange(ref m_tailIndex, tail);
286
287                     // If there is no interaction with a take, we can head down the fast path.
288                     if (m_headIndex <= tail)
289                     {
290                         int idx = tail & m_mask;
291                         object? obj = Volatile.Read(ref m_array[idx]);
292
293                         // Check for nulls in the array.
294                         if (obj == null) continue;
295
296                         m_array[idx] = null;
297                         return obj;
298                     }
299                     else
300                     {
301                         // Interaction with takes: 0 or 1 elements left.
302                         bool lockTaken = false;
303                         try
304                         {
305                             m_foreignLock.Enter(ref lockTaken);
306
307                             if (m_headIndex <= tail)
308                             {
309                                 // Element still available. Take it.
310                                 int idx = tail & m_mask;
311                                 object? obj = Volatile.Read(ref m_array[idx]);
312
313                                 // Check for nulls in the array.
314                                 if (obj == null) continue;
315
316                                 m_array[idx] = null;
317                                 return obj;
318                             }
319                             else
320                             {
321                                 // If we encountered a race condition and element was stolen, restore the tail.
322                                 m_tailIndex = tail + 1;
323                                 return null;
324                             }
325                         }
326                         finally
327                         {
328                             if (lockTaken)
329                                 m_foreignLock.Exit(useMemoryBarrier: false);
330                         }
331                     }
332                 }
333             }
334
335             public bool CanSteal => m_headIndex < m_tailIndex;
336
337             public object? TrySteal(ref bool missedSteal)
338             {
339                 while (true)
340                 {
341                     if (CanSteal)
342                     {
343                         bool taken = false;
344                         try
345                         {
346                             m_foreignLock.TryEnter(ref taken);
347                             if (taken)
348                             {
349                                 // Increment head, and ensure read of tail doesn't move before it (fence).
350                                 int head = m_headIndex;
351                                 Interlocked.Exchange(ref m_headIndex, head + 1);
352
353                                 if (head < m_tailIndex)
354                                 {
355                                     int idx = head & m_mask;
356                                     object? obj = Volatile.Read(ref m_array[idx]);
357
358                                     // Check for nulls in the array.
359                                     if (obj == null) continue;
360
361                                     m_array[idx] = null;
362                                     return obj;
363                                 }
364                                 else
365                                 {
366                                     // Failed, restore head.
367                                     m_headIndex = head;
368                                 }
369                             }
370                         }
371                         finally
372                         {
373                             if (taken)
374                                 m_foreignLock.Exit(useMemoryBarrier: false);
375                         }
376
377                         missedSteal = true;
378                     }
379
380                     return null;
381                 }
382             }
383
384             public int Count
385             {
386                 get
387                 {
388                     bool lockTaken = false;
389                     try
390                     {
391                         m_foreignLock.Enter(ref lockTaken);
392                         return Math.Max(0, m_tailIndex - m_headIndex);
393                     }
394                     finally
395                     {
396                         if (lockTaken)
397                         {
398                             m_foreignLock.Exit(useMemoryBarrier: false);
399                         }
400                     }
401                 }
402             }
403         }
404
405         internal bool loggingEnabled;
406         internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>(); // SOS's ThreadPool command depends on this name
407
408         private Internal.PaddingFor32 pad1;
409
410         private volatile int numOutstandingThreadRequests = 0;
411
412         private Internal.PaddingFor32 pad2;
413
414         public ThreadPoolWorkQueue()
415         {
416             loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
417         }
418
419         public ThreadPoolWorkQueueThreadLocals GetOrCreateThreadLocals() =>
420             ThreadPoolWorkQueueThreadLocals.threadLocals ?? CreateThreadLocals();
421
422         [MethodImpl(MethodImplOptions.NoInlining)]
423         private ThreadPoolWorkQueueThreadLocals CreateThreadLocals()
424         {
425             Debug.Assert(ThreadPoolWorkQueueThreadLocals.threadLocals == null);
426
427             return (ThreadPoolWorkQueueThreadLocals.threadLocals = new ThreadPoolWorkQueueThreadLocals(this));
428         }
429
430         internal void EnsureThreadRequested()
431         {
432             //
433             // If we have not yet requested #procs threads, then request a new thread.
434             //
435             // CoreCLR: Note that there is a separate count in the VM which has already been incremented
436             // by the VM by the time we reach this point.
437             //
438             int count = numOutstandingThreadRequests;
439             while (count < ThreadPoolGlobals.processorCount)
440             {
441                 int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count + 1, count);
442                 if (prev == count)
443                 {
444                     ThreadPool.RequestWorkerThread();
445                     break;
446                 }
447                 count = prev;
448             }
449         }
450
451         internal void MarkThreadRequestSatisfied()
452         {
453             //
454             // One of our outstanding thread requests has been satisfied.
455             // Decrement the count so that future calls to EnsureThreadRequested will succeed.
456             //
457             // CoreCLR: Note that there is a separate count in the VM which has already been decremented
458             // by the VM by the time we reach this point.
459             //
460             int count = numOutstandingThreadRequests;
461             while (count > 0)
462             {
463                 int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count - 1, count);
464                 if (prev == count)
465                 {
466                     break;
467                 }
468                 count = prev;
469             }
470         }
471
472         public void Enqueue(object callback, bool forceGlobal)
473         {
474             Debug.Assert((callback is IThreadPoolWorkItem) ^ (callback is Task));
475
476             if (loggingEnabled)
477                 System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
478
479             ThreadPoolWorkQueueThreadLocals? tl = null;
480             if (!forceGlobal)
481                 tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
482
483             if (null != tl)
484             {
485                 tl.workStealingQueue.LocalPush(callback);
486             }
487             else
488             {
489                 workItems.Enqueue(callback);
490             }
491
492             EnsureThreadRequested();
493         }
494
495         internal bool LocalFindAndPop(object callback)
496         {
497             ThreadPoolWorkQueueThreadLocals tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
498             return tl != null && tl.workStealingQueue.LocalFindAndPop(callback);
499         }
500
501         public object? Dequeue(ThreadPoolWorkQueueThreadLocals tl, ref bool missedSteal)
502         {
503             WorkStealingQueue localWsq = tl.workStealingQueue;
504             object? callback;
505
506             if ((callback = localWsq.LocalPop()) == null && // first try the local queue
507                 !workItems.TryDequeue(out callback)) // then try the global queue
508             {
509                 // finally try to steal from another thread's local queue
510                 WorkStealingQueue[] queues = WorkStealingQueueList.Queues;
511                 int c = queues.Length;
512                 Debug.Assert(c > 0, "There must at least be a queue for this thread.");
513                 int maxIndex = c - 1;
514                 int i = tl.random.Next(c);
515                 while (c > 0)
516                 {
517                     i = (i < maxIndex) ? i + 1 : 0;
518                     WorkStealingQueue otherQueue = queues[i];
519                     if (otherQueue != localWsq && otherQueue.CanSteal)
520                     {
521                         callback = otherQueue.TrySteal(ref missedSteal);
522                         if (callback != null)
523                         {
524                             break;
525                         }
526                     }
527                     c--;
528                 }
529             }
530
531             return callback;
532         }
533
534         public long LocalCount
535         {
536             get
537             {
538                 long count = 0;
539                 foreach (WorkStealingQueue workStealingQueue in WorkStealingQueueList.Queues)
540                 {
541                     count += workStealingQueue.Count;
542                 }
543                 return count;
544             }
545         }
546
547         public long GlobalCount => workItems.Count;
548
549         /// <summary>
550         /// Dispatches work items to this thread.
551         /// </summary>
552         /// <returns>
553         /// <c>true</c> if this thread did as much work as was available or its quantum expired.
554         /// <c>false</c> if this thread stopped working early.
555         /// </returns>
556         internal static bool Dispatch()
557         {
558             ThreadPoolWorkQueue outerWorkQueue = ThreadPoolGlobals.workQueue;
559
560             //
561             // Save the start time
562             //
563             int startTickCount = Environment.TickCount;
564
565             //
566             // Update our records to indicate that an outstanding request for a thread has now been fulfilled.
567             // From this point on, we are responsible for requesting another thread if we stop working for any
568             // reason, and we believe there might still be work in the queue.
569             //
570             // CoreCLR: Note that if this thread is aborted before we get a chance to request another one, the VM will
571             // record a thread request on our behalf.  So we don't need to worry about getting aborted right here.
572             //
573             outerWorkQueue.MarkThreadRequestSatisfied();
574
575             // Has the desire for logging changed since the last time we entered?
576             outerWorkQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
577
578             //
579             // Assume that we're going to need another thread if this one returns to the VM.  We'll set this to 
580             // false later, but only if we're absolutely certain that the queue is empty.
581             //
582             bool needAnotherThread = true;
583             object? outerWorkItem = null;
584             try
585             {
586                 //
587                 // Set up our thread-local data
588                 //
589                 // Use operate on workQueue local to try block so it can be enregistered 
590                 ThreadPoolWorkQueue workQueue = outerWorkQueue;
591                 ThreadPoolWorkQueueThreadLocals tl = workQueue.GetOrCreateThreadLocals();
592                 Thread currentThread = tl.currentThread;
593
594                 // Start on clean ExecutionContext and SynchronizationContext
595                 currentThread._executionContext = null;
596                 currentThread._synchronizationContext = null;
597
598                 //
599                 // Loop until our quantum expires or there is no work.
600                 //
601                 while (ThreadPool.KeepDispatching(startTickCount))
602                 {
603                     bool missedSteal = false;
604                     // Use operate on workItem local to try block so it can be enregistered 
605                     object? workItem = outerWorkItem = workQueue.Dequeue(tl, ref missedSteal);
606
607                     if (workItem == null)
608                     {
609                         //
610                         // No work.
611                         // If we missed a steal, though, there may be more work in the queue.
612                         // Instead of looping around and trying again, we'll just request another thread.  Hopefully the thread
613                         // that owns the contended work-stealing queue will pick up its own workitems in the meantime, 
614                         // which will be more efficient than this thread doing it anyway.
615                         //
616                         needAnotherThread = missedSteal;
617
618                         // Tell the VM we're returning normally, not because Hill Climbing asked us to return.
619                         return true;
620                     }
621
622                     if (workQueue.loggingEnabled)
623                         System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem);
624
625                     //
626                     // If we found work, there may be more work.  Ask for another thread so that the other work can be processed
627                     // in parallel.  Note that this will only ask for a max of #procs threads, so it's safe to call it for every dequeue.
628                     //
629                     workQueue.EnsureThreadRequested();
630
631                     //
632                     // Execute the workitem outside of any finally blocks, so that it can be aborted if needed.
633                     //
634                     if (ThreadPoolGlobals.enableWorkerTracking)
635                     {
636                         bool reportedStatus = false;
637                         try
638                         {
639                             ThreadPool.ReportThreadStatus(isWorking: true);
640                             reportedStatus = true;
641                             if (workItem is Task task)
642                             {
643                                 task.ExecuteFromThreadPool(currentThread);
644                             }
645                             else
646                             {
647                                 Debug.Assert(workItem is IThreadPoolWorkItem);
648                                 Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
649                             }
650                         }
651                         finally
652                         {
653                             if (reportedStatus)
654                                 ThreadPool.ReportThreadStatus(isWorking: false);
655                         }
656                     }
657                     else if (workItem is Task task)
658                     {
659                         // Check for Task first as it's currently faster to type check
660                         // for Task and then Unsafe.As for the interface, rather than
661                         // vice versa, in particular when the object implements a bunch
662                         // of interfaces.
663                         task.ExecuteFromThreadPool(currentThread);
664                     }
665                     else
666                     {
667                         Debug.Assert(workItem is IThreadPoolWorkItem);
668                         Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
669                     }
670
671                     currentThread.ResetThreadPoolThread();
672
673                     // Release refs
674                     outerWorkItem = workItem = null;
675
676                     // Return to clean ExecutionContext and SynchronizationContext
677                     ExecutionContext.ResetThreadPoolThread(currentThread);
678
679                     // 
680                     // Notify the VM that we executed this workitem.  This is also our opportunity to ask whether Hill Climbing wants
681                     // us to return the thread to the pool or not.
682                     //
683                     if (!ThreadPool.NotifyWorkItemComplete())
684                         return false;
685                 }
686
687                 // If we get here, it's because our quantum expired.  Tell the VM we're returning normally.
688                 return true;
689             }
690             finally
691             {
692                 //
693                 // If we are exiting for any reason other than that the queue is definitely empty, ask for another
694                 // thread to pick up where we left off.
695                 //
696                 if (needAnotherThread)
697                     outerWorkQueue.EnsureThreadRequested();
698             }
699         }
700     }
701
702     // Simple random number generator. We don't need great randomness, we just need a little and for it to be fast.
703     internal struct FastRandom // xorshift prng
704     {
705         private uint _w, _x, _y, _z;
706
707         public FastRandom(int seed)
708         {
709             _x = (uint)seed;
710             _w = 88675123;
711             _y = 362436069;
712             _z = 521288629;
713         }
714
715         public int Next(int maxValue)
716         {
717             Debug.Assert(maxValue > 0);
718
719             uint t = _x ^ (_x << 11);
720             _x = _y; _y = _z; _z = _w;
721             _w = _w ^ (_w >> 19) ^ (t ^ (t >> 8));
722
723             return (int)(_w % (uint)maxValue);
724         }
725     }
726
727     // Holds a WorkStealingQueue, and removes it from the list when this object is no longer referenced.
728     internal sealed class ThreadPoolWorkQueueThreadLocals
729     {
730         [ThreadStatic]
731         public static ThreadPoolWorkQueueThreadLocals threadLocals;
732
733         public readonly ThreadPoolWorkQueue workQueue;
734         public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue;
735         public readonly Thread currentThread;
736         public FastRandom random = new FastRandom(Thread.CurrentThread.ManagedThreadId); // mutable struct, do not copy or make readonly
737
738         public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq)
739         {
740             workQueue = tpq;
741             workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue();
742             ThreadPoolWorkQueue.WorkStealingQueueList.Add(workStealingQueue);
743             currentThread = Thread.CurrentThread;
744         }
745
746         ~ThreadPoolWorkQueueThreadLocals()
747         {
748             // Transfer any pending workitems into the global queue so that they will be executed by another thread
749             if (null != workStealingQueue)
750             {
751                 if (null != workQueue)
752                 {
753                     object? cb;
754                     while ((cb = workStealingQueue.LocalPop()) != null)
755                     {
756                         Debug.Assert(null != cb);
757                         workQueue.Enqueue(cb, forceGlobal: true);
758                     }
759                 }
760
761                 ThreadPoolWorkQueue.WorkStealingQueueList.Remove(workStealingQueue);
762             }
763         }
764     }
765
766     public delegate void WaitCallback(object? state);
767
768     public delegate void WaitOrTimerCallback(object? state, bool timedOut);  // signaled or timed out
769
770     internal abstract class QueueUserWorkItemCallbackBase : IThreadPoolWorkItem
771     {
772 #if DEBUG
773         private volatile int executed;
774
775         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1821:RemoveEmptyFinalizers")]
776         ~QueueUserWorkItemCallbackBase()
777         {
778             Debug.Assert(
779                 executed != 0, "A QueueUserWorkItemCallback was never called!");
780         }
781 #endif
782
783         public virtual void Execute()
784         {
785 #if DEBUG
786             GC.SuppressFinalize(this);
787             Debug.Assert(
788                 0 == Interlocked.Exchange(ref executed, 1),
789                 "A QueueUserWorkItemCallback was called twice!");
790 #endif
791         }
792     }
793
794     internal sealed class QueueUserWorkItemCallback : QueueUserWorkItemCallbackBase
795     {
796         private WaitCallback? _callback; // SOS's ThreadPool command depends on this name
797         private readonly object? _state;
798         private readonly ExecutionContext _context;
799
800         private static readonly Action<QueueUserWorkItemCallback> s_executionContextShim = quwi =>
801         {
802             Debug.Assert(quwi._callback != null);
803             WaitCallback callback = quwi._callback;
804             quwi._callback = null;
805
806             callback(quwi._state);
807         };
808
809         internal QueueUserWorkItemCallback(WaitCallback callback, object? state, ExecutionContext context)
810         {
811             Debug.Assert(context != null);
812
813             _callback = callback;
814             _state = state;
815             _context = context;
816         }
817
818         public override void Execute()
819         {
820             base.Execute();
821
822             ExecutionContext.RunForThreadPoolUnsafe(_context, s_executionContextShim, this);
823         }
824     }
825
826     internal sealed class QueueUserWorkItemCallback<TState> : QueueUserWorkItemCallbackBase
827     {
828         private Action<TState>? _callback; // SOS's ThreadPool command depends on this name
829         private readonly TState _state;
830         private readonly ExecutionContext _context;
831
832         internal QueueUserWorkItemCallback(Action<TState> callback, TState state, ExecutionContext context)
833         {
834             Debug.Assert(callback != null);
835
836             _callback = callback;
837             _state = state;
838             _context = context;
839         }
840
841         public override void Execute()
842         {
843             base.Execute();
844
845             Debug.Assert(_callback != null);
846             Action<TState> callback = _callback;
847             _callback = null;
848
849             ExecutionContext.RunForThreadPoolUnsafe(_context, callback, in _state);
850         }
851     }
852
853     internal sealed class QueueUserWorkItemCallbackDefaultContext : QueueUserWorkItemCallbackBase
854     {
855         private WaitCallback? _callback; // SOS's ThreadPool command depends on this name
856         private readonly object? _state;
857
858         internal QueueUserWorkItemCallbackDefaultContext(WaitCallback callback, object? state)
859         {
860             Debug.Assert(callback != null);
861
862             _callback = callback;
863             _state = state;
864         }
865
866         public override void Execute()
867         {
868             ExecutionContext.CheckThreadPoolAndContextsAreDefault();
869             base.Execute();
870
871             Debug.Assert(_callback != null);
872             WaitCallback callback = _callback;
873             _callback = null;
874
875             callback(_state);
876
877             // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
878         }
879     }
880
881     internal sealed class QueueUserWorkItemCallbackDefaultContext<TState> : QueueUserWorkItemCallbackBase
882     {
883         private Action<TState>? _callback; // SOS's ThreadPool command depends on this name
884         private readonly TState _state;
885
886         internal QueueUserWorkItemCallbackDefaultContext(Action<TState> callback, TState state)
887         {
888             Debug.Assert(callback != null);
889
890             _callback = callback;
891             _state = state;
892         }
893
894         public override void Execute()
895         {
896             ExecutionContext.CheckThreadPoolAndContextsAreDefault();
897             base.Execute();
898
899             Debug.Assert(_callback != null);
900             Action<TState> callback = _callback;
901             _callback = null;
902
903             callback(_state);
904
905             // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
906         }
907     }
908
909     internal sealed class _ThreadPoolWaitOrTimerCallback
910     {
911         private WaitOrTimerCallback _waitOrTimerCallback;
912         private ExecutionContext? _executionContext;
913         private object? _state;
914         private static readonly ContextCallback _ccbt = new ContextCallback(WaitOrTimerCallback_Context_t);
915         private static readonly ContextCallback _ccbf = new ContextCallback(WaitOrTimerCallback_Context_f);
916
917         internal _ThreadPoolWaitOrTimerCallback(WaitOrTimerCallback waitOrTimerCallback, object? state, bool flowExecutionContext)
918         {
919             _waitOrTimerCallback = waitOrTimerCallback;
920             _state = state;
921
922             if (flowExecutionContext)
923             {
924                 // capture the exection context
925                 _executionContext = ExecutionContext.Capture();
926             }
927         }
928
929         private static void WaitOrTimerCallback_Context_t(object? state) =>
930             WaitOrTimerCallback_Context(state, timedOut: true);
931
932         private static void WaitOrTimerCallback_Context_f(object? state) =>
933             WaitOrTimerCallback_Context(state, timedOut: false);
934
935         private static void WaitOrTimerCallback_Context(object? state, bool timedOut)
936         {
937             _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state!;
938             helper._waitOrTimerCallback(helper._state, timedOut);
939         }
940
941         // call back helper
942         internal static void PerformWaitOrTimerCallback(_ThreadPoolWaitOrTimerCallback helper, bool timedOut)
943         {
944             Debug.Assert(helper != null, "Null state passed to PerformWaitOrTimerCallback!");
945             // call directly if it is an unsafe call OR EC flow is suppressed
946             ExecutionContext? context = helper._executionContext;
947             if (context == null)
948             {
949                 WaitOrTimerCallback callback = helper._waitOrTimerCallback;
950                 callback(helper._state, timedOut);
951             }
952             else
953             {
954                 ExecutionContext.Run(context, timedOut ? _ccbt : _ccbf, helper);
955             }
956         }
957     }
958
959     public static partial class ThreadPool
960     {
961         [CLSCompliant(false)]
962         public static RegisteredWaitHandle RegisterWaitForSingleObject(
963              WaitHandle waitObject,
964              WaitOrTimerCallback callBack,
965              object? state,
966              uint millisecondsTimeOutInterval,
967              bool executeOnlyOnce    // NOTE: we do not allow other options that allow the callback to be queued as an APC
968              )
969         {
970             if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue)
971                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
972             return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, true);
973         }
974
975         [CLSCompliant(false)]
976         public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
977              WaitHandle waitObject,
978              WaitOrTimerCallback callBack,
979              object? state,
980              uint millisecondsTimeOutInterval,
981              bool executeOnlyOnce    // NOTE: we do not allow other options that allow the callback to be queued as an APC
982              )
983         {
984             if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue)
985                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
986             return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, false);
987         }
988
989         public static RegisteredWaitHandle RegisterWaitForSingleObject(
990              WaitHandle waitObject,
991              WaitOrTimerCallback callBack,
992              object? state,
993              int millisecondsTimeOutInterval,
994              bool executeOnlyOnce    // NOTE: we do not allow other options that allow the callback to be queued as an APC
995              )
996         {
997             if (millisecondsTimeOutInterval < -1)
998                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
999             return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, true);
1000         }
1001
1002         public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
1003              WaitHandle waitObject,
1004              WaitOrTimerCallback callBack,
1005              object? state,
1006              int millisecondsTimeOutInterval,
1007              bool executeOnlyOnce    // NOTE: we do not allow other options that allow the callback to be queued as an APC
1008              )
1009         {
1010             if (millisecondsTimeOutInterval < -1)
1011                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1012             return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false);
1013         }
1014
1015         public static RegisteredWaitHandle RegisterWaitForSingleObject(
1016             WaitHandle waitObject,
1017             WaitOrTimerCallback callBack,
1018             object? state,
1019             long millisecondsTimeOutInterval,
1020             bool executeOnlyOnce    // NOTE: we do not allow other options that allow the callback to be queued as an APC
1021         )
1022         {
1023             if (millisecondsTimeOutInterval < -1)
1024                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1025             if (millisecondsTimeOutInterval > (uint)int.MaxValue)
1026                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
1027             return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, true);
1028         }
1029
1030         public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
1031             WaitHandle waitObject,
1032             WaitOrTimerCallback callBack,
1033             object? state,
1034             long millisecondsTimeOutInterval,
1035             bool executeOnlyOnce    // NOTE: we do not allow other options that allow the callback to be queued as an APC
1036         )
1037         {
1038             if (millisecondsTimeOutInterval < -1)
1039                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1040             if (millisecondsTimeOutInterval > (uint)int.MaxValue)
1041                 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
1042             return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false);
1043         }
1044
1045         public static RegisteredWaitHandle RegisterWaitForSingleObject(
1046                           WaitHandle waitObject,
1047                           WaitOrTimerCallback callBack,
1048                           object? state,
1049                           TimeSpan timeout,
1050                           bool executeOnlyOnce
1051                           )
1052         {
1053             long tm = (long)timeout.TotalMilliseconds;
1054             if (tm < -1)
1055                 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1056             if (tm > (long)int.MaxValue)
1057                 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
1058             return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, true);
1059         }
1060
1061         public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
1062                           WaitHandle waitObject,
1063                           WaitOrTimerCallback callBack,
1064                           object? state,
1065                           TimeSpan timeout,
1066                           bool executeOnlyOnce
1067                           )
1068         {
1069             long tm = (long)timeout.TotalMilliseconds;
1070             if (tm < -1)
1071                 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1072             if (tm > (long)int.MaxValue)
1073                 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
1074             return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, false);
1075         }
1076
1077         public static bool QueueUserWorkItem(WaitCallback callBack) =>
1078             QueueUserWorkItem(callBack, null);
1079
1080         public static bool QueueUserWorkItem(WaitCallback callBack, object? state)
1081         {
1082             if (callBack == null)
1083             {
1084                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1085             }
1086
1087             EnsureInitialized();
1088
1089             ExecutionContext? context = ExecutionContext.Capture();
1090
1091             object tpcallBack = (context == null || context.IsDefault) ?
1092                 new QueueUserWorkItemCallbackDefaultContext(callBack!, state) :
1093                 (object)new QueueUserWorkItemCallback(callBack!, state, context);
1094
1095             ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
1096
1097             return true;
1098         }
1099
1100         public static bool QueueUserWorkItem<TState>(Action<TState> callBack, TState state, bool preferLocal)
1101         {
1102             if (callBack == null)
1103             {
1104                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1105             }
1106
1107             EnsureInitialized();
1108
1109             ExecutionContext? context = ExecutionContext.Capture();
1110
1111             object tpcallBack = (context == null || context.IsDefault) ?
1112                 new QueueUserWorkItemCallbackDefaultContext<TState>(callBack!, state) :
1113                 (object)new QueueUserWorkItemCallback<TState>(callBack!, state, context);
1114
1115             ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: !preferLocal);
1116
1117             return true;
1118         }
1119
1120         public static bool UnsafeQueueUserWorkItem<TState>(Action<TState> callBack, TState state, bool preferLocal)
1121         {
1122             if (callBack == null)
1123             {
1124                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1125             }
1126
1127             // If the callback is the runtime-provided invocation of an IAsyncStateMachineBox,
1128             // then we can queue the Task state directly to the ThreadPool instead of 
1129             // wrapping it in a QueueUserWorkItemCallback.
1130             //
1131             // This occurs when user code queues its provided continuation to the ThreadPool;
1132             // internally we call UnsafeQueueUserWorkItemInternal directly for Tasks.
1133             if (ReferenceEquals(callBack, ThreadPoolGlobals.s_invokeAsyncStateMachineBox))
1134             {
1135                 if (!(state is IAsyncStateMachineBox))
1136                 {
1137                     // The provided state must be the internal IAsyncStateMachineBox (Task) type
1138                     ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
1139                 }
1140
1141                 UnsafeQueueUserWorkItemInternal((object)state!, preferLocal);
1142                 return true;
1143             }
1144
1145             EnsureInitialized();
1146
1147             ThreadPoolGlobals.workQueue.Enqueue(
1148                 new QueueUserWorkItemCallbackDefaultContext<TState>(callBack!, state), forceGlobal: !preferLocal);
1149
1150             return true;
1151         }
1152
1153         public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object? state)
1154         {
1155             if (callBack == null)
1156             {
1157                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1158             }
1159
1160             EnsureInitialized();
1161
1162             object tpcallBack = new QueueUserWorkItemCallbackDefaultContext(callBack!, state);
1163
1164             ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
1165
1166             return true;
1167         }
1168
1169         public static bool UnsafeQueueUserWorkItem(IThreadPoolWorkItem callBack, bool preferLocal)
1170         {
1171             if (callBack == null)
1172             {
1173                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1174             }
1175             if (callBack is Task)
1176             {
1177                 // Prevent code from queueing a derived Task that also implements the interface,
1178                 // as that would bypass Task.Start and its safety checks.
1179                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.callBack);
1180             }
1181
1182             UnsafeQueueUserWorkItemInternal(callBack!, preferLocal);
1183             return true;
1184         }
1185
1186         internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal)
1187         {
1188             Debug.Assert((callBack is IThreadPoolWorkItem) ^ (callBack is Task));
1189
1190             EnsureInitialized();
1191
1192             ThreadPoolGlobals.workQueue.Enqueue(callBack, forceGlobal: !preferLocal);
1193         }
1194
1195         // This method tries to take the target callback out of the current thread's queue.
1196         internal static bool TryPopCustomWorkItem(object workItem)
1197         {
1198             Debug.Assert(null != workItem);
1199             return
1200                 ThreadPoolGlobals.threadPoolInitialized && // if not initialized, so there's no way this workitem was ever queued.
1201                 ThreadPoolGlobals.workQueue.LocalFindAndPop(workItem);
1202         }
1203
1204         // Get all workitems.  Called by TaskScheduler in its debugger hooks.
1205         internal static IEnumerable<object> GetQueuedWorkItems()
1206         {
1207             // Enumerate global queue
1208             foreach (object workItem in ThreadPoolGlobals.workQueue.workItems)
1209             {
1210                 yield return workItem;
1211             }
1212
1213             // Enumerate each local queue
1214             foreach (ThreadPoolWorkQueue.WorkStealingQueue wsq in ThreadPoolWorkQueue.WorkStealingQueueList.Queues)
1215             {
1216                 if (wsq != null && wsq.m_array != null)
1217                 {
1218                     object?[] items = wsq.m_array;
1219                     for (int i = 0; i < items.Length; i++)
1220                     {
1221                         object? item = items[i];
1222                         if (item != null)
1223                         {
1224                             yield return item;
1225                         }
1226                     }
1227                 }
1228             }
1229         }
1230
1231         internal static IEnumerable<object> GetLocallyQueuedWorkItems()
1232         {
1233             ThreadPoolWorkQueue.WorkStealingQueue wsq = ThreadPoolWorkQueueThreadLocals.threadLocals.workStealingQueue;
1234             if (wsq != null && wsq.m_array != null)
1235             {
1236                 object?[] items = wsq.m_array;
1237                 for (int i = 0; i < items.Length; i++)
1238                 {
1239                     object? item = items[i];
1240                     if (item != null)
1241                         yield return item;
1242                 }
1243             }
1244         }
1245
1246         internal static IEnumerable<object> GetGloballyQueuedWorkItems() => ThreadPoolGlobals.workQueue.workItems;
1247
1248         private static object[] ToObjectArray(IEnumerable<object> workitems)
1249         {
1250             int i = 0;
1251             foreach (object item in workitems)
1252             {
1253                 i++;
1254             }
1255
1256             object[] result = new object[i];
1257             i = 0;
1258             foreach (object item in workitems)
1259             {
1260                 if (i < result.Length) //just in case someone calls us while the queues are in motion
1261                     result[i] = item;
1262                 i++;
1263             }
1264
1265             return result;
1266         }
1267
1268         // This is the method the debugger will actually call, if it ends up calling
1269         // into ThreadPool directly.  Tests can use this to simulate a debugger, as well.
1270         internal static object[] GetQueuedWorkItemsForDebugger() =>
1271             ToObjectArray(GetQueuedWorkItems());
1272
1273         internal static object[] GetGloballyQueuedWorkItemsForDebugger() =>
1274             ToObjectArray(GetGloballyQueuedWorkItems());
1275
1276         internal static object[] GetLocallyQueuedWorkItemsForDebugger() =>
1277             ToObjectArray(GetLocallyQueuedWorkItems());
1278
1279         /// <summary>
1280         /// Gets the number of work items that are currently queued to be processed.
1281         /// </summary>
1282         /// <remarks>
1283         /// For a thread pool implementation that may have different types of work items, the count includes all types that can
1284         /// be tracked, which may only be the user work items including tasks. Some implementations may also include queued
1285         /// timer and wait callbacks in the count. On Windows, the count is unlikely to include the number of pending IO
1286         /// completions, as they get posted directly to an IO completion port.
1287         /// </remarks>
1288         public static long PendingWorkItemCount
1289         {
1290             get
1291             {
1292                 ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue;
1293                 return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount;
1294             }
1295         }
1296     }
1297 }