xref: /reactos/ntoskrnl/ex/work.c (revision 34593d93)
1 /*
2  * COPYRIGHT:          See COPYING in the top level directory
3  * PROJECT:            ReactOS Kernel
4  * FILE:               ntoskrnl/ex/work.c
5  * PURPOSE:            Manage system work queues and worker threads
6  * PROGRAMMER:         Alex Ionescu (alex@relsoft.net)
7  */
8 
9 /* INCLUDES ******************************************************************/
10 
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 /* DATA **********************************************************************/
16 
17 /* Number of worker threads for each Queue */
18 #define EX_HYPERCRITICAL_WORK_THREADS               1
19 #define EX_DELAYED_WORK_THREADS                     3
20 #define EX_CRITICAL_WORK_THREADS                    5
21 
22 /* Magic flag for dynamic worker threads */
23 #define EX_DYNAMIC_WORK_THREAD                      0x80000000
24 
25 /* Worker thread priority increments (added to base priority) */
26 #define EX_HYPERCRITICAL_QUEUE_PRIORITY_INCREMENT   7
27 #define EX_CRITICAL_QUEUE_PRIORITY_INCREMENT        5
28 #define EX_DELAYED_QUEUE_PRIORITY_INCREMENT         4
29 
30 /* The actual worker queue array */
31 EX_WORK_QUEUE ExWorkerQueue[MaximumWorkQueue];
32 
33 /* Accounting of the total threads and registry hacked threads */
34 ULONG ExCriticalWorkerThreads;
35 ULONG ExDelayedWorkerThreads;
36 ULONG ExpAdditionalCriticalWorkerThreads;
37 ULONG ExpAdditionalDelayedWorkerThreads;
38 
39 /* Future support for stack swapping worker threads */
40 BOOLEAN ExpWorkersCanSwap;
41 LIST_ENTRY ExpWorkerListHead;
42 FAST_MUTEX ExpWorkerSwapinMutex;
43 
44 /* The worker balance set manager events */
45 KEVENT ExpThreadSetManagerEvent;
46 KEVENT ExpThreadSetManagerShutdownEvent;
47 
48 /* Thread pointers for future worker thread shutdown support */
49 PETHREAD ExpWorkerThreadBalanceManagerPtr;
50 PETHREAD ExpLastWorkerThread;
51 
52 /* PRIVATE FUNCTIONS *********************************************************/
53 
54 /*++
55  * @name ExpWorkerThreadEntryPoint
56  *
57  *     The ExpWorkerThreadEntryPoint routine is the entrypoint for any new
58  *     worker thread created by teh system.
59  *
60  * @param Context
61  *        Contains the work queue type masked with a flag specifing whether the
62  *        thread is dynamic or not.
63  *
64  * @return None.
65  *
66  * @remarks A dynamic thread can timeout after 10 minutes of waiting on a queue
67  *          while a static thread will never timeout.
68  *
69  *          Worker threads must return at IRQL == PASSIVE_LEVEL, must not have
70  *          active impersonation info, and must not have disabled APCs.
71  *
72  *          NB: We will re-enable APCs for broken threads but all other cases
73  *              will generate a bugcheck.
74  *
75  *--*/
76 VOID
77 NTAPI
78 ExpWorkerThreadEntryPoint(IN PVOID Context)
79 {
80     PWORK_QUEUE_ITEM WorkItem;
81     PLIST_ENTRY QueueEntry;
82     WORK_QUEUE_TYPE WorkQueueType;
83     PEX_WORK_QUEUE WorkQueue;
84     LARGE_INTEGER Timeout;
85     PLARGE_INTEGER TimeoutPointer = NULL;
86     PETHREAD Thread = PsGetCurrentThread();
87     KPROCESSOR_MODE WaitMode;
88     EX_QUEUE_WORKER_INFO OldValue, NewValue;
89 
90     /* Check if this is a dyamic thread */
91     if ((ULONG_PTR)Context & EX_DYNAMIC_WORK_THREAD)
92     {
93         /* It is, which means we will eventually time out after 10 minutes */
94         Timeout.QuadPart = Int32x32To64(10, -10000000 * 60);
95         TimeoutPointer = &Timeout;
96     }
97 
98     /* Get Queue Type and Worker Queue */
99     WorkQueueType = (WORK_QUEUE_TYPE)((ULONG_PTR)Context &
100                                       ~EX_DYNAMIC_WORK_THREAD);
101     WorkQueue = &ExWorkerQueue[WorkQueueType];
102 
103     /* Select the wait mode */
104     WaitMode = (UCHAR)WorkQueue->Info.WaitMode;
105 
106     /* Nobody should have initialized this yet, do it now */
107     ASSERT(Thread->ExWorkerCanWaitUser == 0);
108     if (WaitMode == UserMode) Thread->ExWorkerCanWaitUser = TRUE;
109 
110     /* If we shouldn't swap, disable that feature */
111     if (!ExpWorkersCanSwap) KeSetKernelStackSwapEnable(FALSE);
112 
113     /* Set the worker flags */
114     do
115     {
116         /* Check if the queue is being disabled */
117         if (WorkQueue->Info.QueueDisabled)
118         {
119             /* Re-enable stack swapping and kill us */
120             KeSetKernelStackSwapEnable(TRUE);
121             PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN);
122         }
123 
124         /* Increase the worker count */
125         OldValue = WorkQueue->Info;
126         NewValue = OldValue;
127         NewValue.WorkerCount++;
128     }
129     while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,
130                                       *(PLONG)&NewValue,
131                                       *(PLONG)&OldValue) != *(PLONG)&OldValue);
132 
133     /* Success, you are now officially a worker thread! */
134     Thread->ActiveExWorker = TRUE;
135 
136     /* Loop forever */
137 ProcessLoop:
138     for (;;)
139     {
140         /* Wait for something to happen on the queue */
141         QueueEntry = KeRemoveQueue(&WorkQueue->WorkerQueue,
142                                    WaitMode,
143                                    TimeoutPointer);
144 
145         /* Check if we timed out and quit this loop in that case */
146         if ((NTSTATUS)(ULONG_PTR)QueueEntry == STATUS_TIMEOUT) break;
147 
148         /* Increment Processed Work Items */
149         InterlockedIncrement((PLONG)&WorkQueue->WorkItemsProcessed);
150 
151         /* Get the Work Item */
152         WorkItem = CONTAINING_RECORD(QueueEntry, WORK_QUEUE_ITEM, List);
153 
154         /* Make sure nobody is trying to play smart with us */
155         ASSERT((ULONG_PTR)WorkItem->WorkerRoutine > MmUserProbeAddress);
156 
157         /* Call the Worker Routine */
158         WorkItem->WorkerRoutine(WorkItem->Parameter);
159 
160         /* Make sure APCs are not disabled */
161         if (Thread->Tcb.CombinedApcDisable != 0)
162         {
163             /* We're nice and do it behind your back */
164             DPRINT1("Warning: Broken Worker Thread: %p %p %p came back "
165                     "with APCs disabled!\n",
166                     WorkItem->WorkerRoutine,
167                     WorkItem->Parameter,
168                     WorkItem);
169             ASSERT(Thread->Tcb.CombinedApcDisable == 0);
170             Thread->Tcb.CombinedApcDisable = 0;
171         }
172 
173         /* Make sure it returned at right IRQL */
174         if (KeGetCurrentIrql() != PASSIVE_LEVEL)
175         {
176             /* It didn't, bugcheck! */
177             KeBugCheckEx(WORKER_THREAD_RETURNED_AT_BAD_IRQL,
178                          (ULONG_PTR)WorkItem->WorkerRoutine,
179                          KeGetCurrentIrql(),
180                          (ULONG_PTR)WorkItem->Parameter,
181                          (ULONG_PTR)WorkItem);
182         }
183 
184         /* Make sure it returned with Impersionation Disabled */
185         if (Thread->ActiveImpersonationInfo)
186         {
187             /* It didn't, bugcheck! */
188             KeBugCheckEx(IMPERSONATING_WORKER_THREAD,
189                          (ULONG_PTR)WorkItem->WorkerRoutine,
190                          (ULONG_PTR)WorkItem->Parameter,
191                          (ULONG_PTR)WorkItem,
192                          0);
193         }
194     }
195 
196     /* This is a dynamic thread. Terminate it unless IRPs are pending */
197     if (!IsListEmpty(&Thread->IrpList)) goto ProcessLoop;
198 
199     /* Don't terminate it if the queue is disabled either */
200     if (WorkQueue->Info.QueueDisabled) goto ProcessLoop;
201 
202     /* Set the worker flags */
203     do
204     {
205         /* Decrease the worker count */
206         OldValue = WorkQueue->Info;
207         NewValue = OldValue;
208         NewValue.WorkerCount--;
209     }
210     while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,
211                                       *(PLONG)&NewValue,
212                                       *(PLONG)&OldValue) != *(PLONG)&OldValue);
213 
214     /* Decrement dynamic thread count */
215     InterlockedDecrement(&WorkQueue->DynamicThreadCount);
216 
217     /* We're not a worker thread anymore */
218     Thread->ActiveExWorker = FALSE;
219 
220     /* Re-enable the stack swap */
221     KeSetKernelStackSwapEnable(TRUE);
222     return;
223 }
224 
225 /*++
226  * @name ExpCreateWorkerThread
227  *
228  *     The ExpCreateWorkerThread routine creates a new worker thread for the
229  *     specified queue.
230  *
231  * @param QueueType
232  *        Type of the queue to use for this thread. Valid values are:
233  *          - DelayedWorkQueue
234  *          - CriticalWorkQueue
235  *          - HyperCriticalWorkQueue
236  *
237  * @param Dynamic
238  *        Specifies whether or not this thread is a dynamic thread.
239  *
240  * @return None.
241  *
242  * @remarks HyperCritical work threads run at priority 7; Critical work threads
243  *          run at priority 5, and delayed work threads run at priority 4.
244  *
245  *          This, worker threads cannot pre-empty a normal user-mode thread.
246  *
247  *--*/
248 VOID
249 NTAPI
250 ExpCreateWorkerThread(WORK_QUEUE_TYPE WorkQueueType,
251                       IN BOOLEAN Dynamic)
252 {
253     PETHREAD Thread;
254     HANDLE hThread;
255     ULONG Context;
256     KPRIORITY Priority;
257 
258     /* Check if this is going to be a dynamic thread */
259     Context = WorkQueueType;
260 
261     /* Add the dynamic mask */
262     if (Dynamic) Context |= EX_DYNAMIC_WORK_THREAD;
263 
264     /* Create the System Thread */
265     PsCreateSystemThread(&hThread,
266                          THREAD_ALL_ACCESS,
267                          NULL,
268                          NULL,
269                          NULL,
270                          ExpWorkerThreadEntryPoint,
271                          UlongToPtr(Context));
272 
273     /* If the thread is dynamic */
274     if (Dynamic)
275     {
276         /* Increase the count */
277         InterlockedIncrement(&ExWorkerQueue[WorkQueueType].DynamicThreadCount);
278     }
279 
280     /* Set the priority */
281     if (WorkQueueType == DelayedWorkQueue)
282     {
283         /* Priority == 4 */
284         Priority = EX_DELAYED_QUEUE_PRIORITY_INCREMENT;
285     }
286     else if (WorkQueueType == CriticalWorkQueue)
287     {
288         /* Priority == 5 */
289         Priority = EX_CRITICAL_QUEUE_PRIORITY_INCREMENT;
290     }
291     else
292     {
293         /* Priority == 7 */
294         Priority = EX_HYPERCRITICAL_QUEUE_PRIORITY_INCREMENT;
295     }
296 
297     /* Get the Thread */
298     ObReferenceObjectByHandle(hThread,
299                               THREAD_SET_INFORMATION,
300                               PsThreadType,
301                               KernelMode,
302                               (PVOID*)&Thread,
303                               NULL);
304 
305     /* Set the Priority */
306     KeSetBasePriorityThread(&Thread->Tcb, Priority);
307 
308     /* Dereference and close handle */
309     ObDereferenceObject(Thread);
310     ObCloseHandle(hThread, KernelMode);
311 }
312 
313 /*++
314  * @name ExpDetectWorkerThreadDeadlock
315  *
316  *     The ExpDetectWorkerThreadDeadlock routine checks every queue and creates
317  *     a dynamic thread if the queue seems to be deadlocked.
318  *
319  * @param None
320  *
321  * @return None.
322  *
323  * @remarks The algorithm for deciding if a new thread must be created is based
324  *          on whether the queue has processed no new items in the last second,
325  *          and new items are still enqueued.
326  *
327  *--*/
328 VOID
329 NTAPI
330 ExpDetectWorkerThreadDeadlock(VOID)
331 {
332     ULONG i;
333     PEX_WORK_QUEUE Queue;
334 
335     /* Loop the 3 queues */
336     for (i = 0; i < MaximumWorkQueue; i++)
337     {
338         /* Get the queue */
339         Queue = &ExWorkerQueue[i];
340         ASSERT(Queue->DynamicThreadCount <= 16);
341 
342         /* Check if stuff is on the queue that still is unprocessed */
343         if ((Queue->QueueDepthLastPass) &&
344             (Queue->WorkItemsProcessed == Queue->WorkItemsProcessedLastPass) &&
345             (Queue->DynamicThreadCount < 16))
346         {
347             /* Stuff is still on the queue and nobody did anything about it */
348             DPRINT1("EX: Work Queue Deadlock detected: %lu\n", i);
349             ExpCreateWorkerThread(i, TRUE);
350             DPRINT1("Dynamic threads queued %d\n", Queue->DynamicThreadCount);
351         }
352 
353         /* Update our data */
354         Queue->WorkItemsProcessedLastPass = Queue->WorkItemsProcessed;
355         Queue->QueueDepthLastPass = KeReadStateQueue(&Queue->WorkerQueue);
356     }
357 }
358 
359 /*++
360  * @name ExpCheckDynamicThreadCount
361  *
362  *     The ExpCheckDynamicThreadCount routine checks every queue and creates
363  *     a dynamic thread if the queue requires one.
364  *
365  * @param None
366  *
367  * @return None.
368  *
369  * @remarks The algorithm for deciding if a new thread must be created is
370  *          documented in the ExQueueWorkItem routine.
371  *
372  *--*/
373 VOID
374 NTAPI
375 ExpCheckDynamicThreadCount(VOID)
376 {
377     ULONG i;
378     PEX_WORK_QUEUE Queue;
379 
380     /* Loop the 3 queues */
381     for (i = 0; i < MaximumWorkQueue; i++)
382     {
383         /* Get the queue */
384         Queue = &ExWorkerQueue[i];
385 
386         /* Check if still need a new thread. See ExQueueWorkItem */
387         if ((Queue->Info.MakeThreadsAsNecessary) &&
388             (!IsListEmpty(&Queue->WorkerQueue.EntryListHead)) &&
389             (Queue->WorkerQueue.CurrentCount <
390              Queue->WorkerQueue.MaximumCount) &&
391             (Queue->DynamicThreadCount < 16))
392         {
393             /* Create a new thread */
394             DPRINT1("EX: Creating new dynamic thread as requested\n");
395             ExpCreateWorkerThread(i, TRUE);
396         }
397     }
398 }
399 
400 /*++
401  * @name ExpWorkerThreadBalanceManager
402  *
403  *     The ExpWorkerThreadBalanceManager routine is the entrypoint for the
404  *     worker thread balance set manager.
405  *
406  * @param Context
407  *        Unused.
408  *
409  * @return None.
410  *
411  * @remarks The worker thread balance set manager listens every second, but can
412  *          also be woken up by an event when a new thread is needed, or by the
413  *          special shutdown event. This thread runs at priority 7.
414  *
415  *          This routine must run at IRQL == PASSIVE_LEVEL.
416  *
417  *--*/
418 VOID
419 NTAPI
420 ExpWorkerThreadBalanceManager(IN PVOID Context)
421 {
422     KTIMER Timer;
423     LARGE_INTEGER Timeout;
424     NTSTATUS Status;
425     PVOID WaitEvents[3];
426     PAGED_CODE();
427     UNREFERENCED_PARAMETER(Context);
428 
429     /* Raise our priority above all other worker threads */
430     KeSetBasePriorityThread(KeGetCurrentThread(),
431                             EX_CRITICAL_QUEUE_PRIORITY_INCREMENT + 1);
432 
433     /* Setup the timer */
434     KeInitializeTimer(&Timer);
435     Timeout.QuadPart = Int32x32To64(-1, 10000000);
436 
437     /* We'll wait on the periodic timer and also the emergency event */
438     WaitEvents[0] = &Timer;
439     WaitEvents[1] = &ExpThreadSetManagerEvent;
440     WaitEvents[2] = &ExpThreadSetManagerShutdownEvent;
441 
442     /* Start wait loop */
443     for (;;)
444     {
445         /* Wait for the timer */
446         KeSetTimer(&Timer, Timeout, NULL);
447         Status = KeWaitForMultipleObjects(3,
448                                           WaitEvents,
449                                           WaitAny,
450                                           Executive,
451                                           KernelMode,
452                                           FALSE,
453                                           NULL,
454                                           NULL);
455         if (Status == 0)
456         {
457             /* Our timer expired. Check for deadlocks */
458             ExpDetectWorkerThreadDeadlock();
459         }
460         else if (Status == 1)
461         {
462             /* Someone notified us, verify if we should create a new thread */
463             ExpCheckDynamicThreadCount();
464         }
465         else if (Status == 2)
466         {
467             /* We are shutting down. Cancel the timer */
468             DPRINT1("System shutdown\n");
469             KeCancelTimer(&Timer);
470 
471             /* Make sure we have a final thread */
472             ASSERT(ExpLastWorkerThread);
473 
474             /* Wait for it */
475             KeWaitForSingleObject(ExpLastWorkerThread,
476                                   Executive,
477                                   KernelMode,
478                                   FALSE,
479                                   NULL);
480 
481             /* Dereference it and kill us */
482             ObDereferenceObject(ExpLastWorkerThread);
483             PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN);
484         }
485 
486         /*
487          * If WinDBG wants to attach or kill a user-mode process, and/or
488          * page-in an address region, queue a debugger worker thread.
489          */
490         if (ExpDebuggerWork == WinKdWorkerStart)
491         {
492              ExInitializeWorkItem(&ExpDebuggerWorkItem, ExpDebuggerWorker, NULL);
493              ExpDebuggerWork = WinKdWorkerInitialized;
494              ExQueueWorkItem(&ExpDebuggerWorkItem, DelayedWorkQueue);
495         }
496     }
497 }
498 
499 /*++
500  * @name ExpInitializeWorkerThreads
501  *
502  *     The ExpInitializeWorkerThreads routine initializes worker thread and
503  *     work queue support.
504  *
505  * @param None.
506  *
507  * @return None.
508  *
509  * @remarks This routine is only called once during system initialization.
510  *
511  *--*/
512 CODE_SEG("INIT")
513 VOID
514 NTAPI
515 ExpInitializeWorkerThreads(VOID)
516 {
517     ULONG WorkQueueType;
518     ULONG CriticalThreads, DelayedThreads;
519     HANDLE ThreadHandle;
520     PETHREAD Thread;
521     ULONG i;
522 
523     /* Setup the stack swap support */
524     ExInitializeFastMutex(&ExpWorkerSwapinMutex);
525     InitializeListHead(&ExpWorkerListHead);
526     ExpWorkersCanSwap = TRUE;
527 
528     /* Set the number of critical and delayed threads. We shouldn't hardcode */
529     DelayedThreads = EX_DELAYED_WORK_THREADS;
530     CriticalThreads = EX_CRITICAL_WORK_THREADS;
531 
532     /* Protect against greedy registry modifications */
533     ExpAdditionalDelayedWorkerThreads =
534         min(ExpAdditionalDelayedWorkerThreads, 16);
535     ExpAdditionalCriticalWorkerThreads =
536         min(ExpAdditionalCriticalWorkerThreads, 16);
537 
538     /* Calculate final count */
539     DelayedThreads += ExpAdditionalDelayedWorkerThreads;
540     CriticalThreads += ExpAdditionalCriticalWorkerThreads;
541 
542     /* Initialize the Array */
543     for (WorkQueueType = 0; WorkQueueType < MaximumWorkQueue; WorkQueueType++)
544     {
545         /* Clear the structure and initialize the queue */
546         RtlZeroMemory(&ExWorkerQueue[WorkQueueType], sizeof(EX_WORK_QUEUE));
547         KeInitializeQueue(&ExWorkerQueue[WorkQueueType].WorkerQueue, 0);
548     }
549 
550     /* Dynamic threads are only used for the critical queue */
551     ExWorkerQueue[CriticalWorkQueue].Info.MakeThreadsAsNecessary = TRUE;
552 
553     /* Initialize the balance set manager events */
554     KeInitializeEvent(&ExpThreadSetManagerEvent, SynchronizationEvent, FALSE);
555     KeInitializeEvent(&ExpThreadSetManagerShutdownEvent,
556                       NotificationEvent,
557                       FALSE);
558 
559     /* Create the built-in worker threads for the critical queue */
560     for (i = 0; i < CriticalThreads; i++)
561     {
562         /* Create the thread */
563         ExpCreateWorkerThread(CriticalWorkQueue, FALSE);
564         ExCriticalWorkerThreads++;
565     }
566 
567     /* Create the built-in worker threads for the delayed queue */
568     for (i = 0; i < DelayedThreads; i++)
569     {
570         /* Create the thread */
571         ExpCreateWorkerThread(DelayedWorkQueue, FALSE);
572         ExDelayedWorkerThreads++;
573     }
574 
575     /* Create the built-in worker thread for the hypercritical queue */
576     ExpCreateWorkerThread(HyperCriticalWorkQueue, FALSE);
577 
578     /* Create the balance set manager thread */
579     PsCreateSystemThread(&ThreadHandle,
580                          THREAD_ALL_ACCESS,
581                          NULL,
582                          0,
583                          NULL,
584                          ExpWorkerThreadBalanceManager,
585                          NULL);
586 
587     /* Get a pointer to it for the shutdown process */
588     ObReferenceObjectByHandle(ThreadHandle,
589                               THREAD_ALL_ACCESS,
590                               NULL,
591                               KernelMode,
592                               (PVOID*)&Thread,
593                               NULL);
594     ExpWorkerThreadBalanceManagerPtr = Thread;
595 
596     /* Close the handle and return */
597     ObCloseHandle(ThreadHandle, KernelMode);
598 }
599 
600 VOID
601 NTAPI
602 ExpSetSwappingKernelApc(IN PKAPC Apc,
603                         OUT PKNORMAL_ROUTINE *NormalRoutine,
604                         IN OUT PVOID *NormalContext,
605                         IN OUT PVOID *SystemArgument1,
606                         IN OUT PVOID *SystemArgument2)
607 {
608     PBOOLEAN AllowSwap;
609     PKEVENT Event = (PKEVENT)*SystemArgument1;
610 
611     /* Make sure it's an active worker */
612     if (PsGetCurrentThread()->ActiveExWorker)
613     {
614         /* Read the setting from the context flag */
615         AllowSwap = (PBOOLEAN)NormalContext;
616         KeSetKernelStackSwapEnable(*AllowSwap);
617     }
618 
619     /* Let caller know that we're done */
620     KeSetEvent(Event, 0, FALSE);
621 }
622 
623 VOID
624 NTAPI
625 ExSwapinWorkerThreads(IN BOOLEAN AllowSwap)
626 {
627     KEVENT Event;
628     PETHREAD CurrentThread = PsGetCurrentThread(), Thread;
629     PEPROCESS Process = PsInitialSystemProcess;
630     KAPC Apc;
631     PAGED_CODE();
632 
633     /* Initialize an event so we know when we're done */
634     KeInitializeEvent(&Event, NotificationEvent, FALSE);
635 
636     /* Lock this routine */
637     ExAcquireFastMutex(&ExpWorkerSwapinMutex);
638 
639     /* New threads cannot swap anymore */
640     ExpWorkersCanSwap = AllowSwap;
641 
642     /* Loop all threads in the system process */
643     Thread = PsGetNextProcessThread(Process, NULL);
644     while (Thread)
645     {
646         /* Skip threads with explicit permission to do this */
647         if (Thread->ExWorkerCanWaitUser) goto Next;
648 
649         /* Check if we reached ourselves */
650         if (Thread == CurrentThread)
651         {
652             /* Do it inline */
653             KeSetKernelStackSwapEnable(AllowSwap);
654         }
655         else
656         {
657             /* Queue an APC */
658             KeInitializeApc(&Apc,
659                             &Thread->Tcb,
660                             InsertApcEnvironment,
661                             ExpSetSwappingKernelApc,
662                             NULL,
663                             NULL,
664                             KernelMode,
665                             &AllowSwap);
666             if (KeInsertQueueApc(&Apc, &Event, NULL, 3))
667             {
668                 /* Wait for the APC to run */
669                 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
670                 KeClearEvent(&Event);
671             }
672         }
673 
674         /* Next thread */
675 Next:
676         Thread = PsGetNextProcessThread(Process, Thread);
677     }
678 
679     /* Release the lock */
680     ExReleaseFastMutex(&ExpWorkerSwapinMutex);
681 }
682 
683 /* PUBLIC FUNCTIONS **********************************************************/
684 
685 /*++
686  * @name ExQueueWorkItem
687  * @implemented NT4
688  *
689  *     The ExQueueWorkItem routine acquires rundown protection for
690  *     the specified descriptor.
691  *
692  * @param WorkItem
693  *        Pointer to an initialized Work Queue Item structure. This structure
694  *        must be located in nonpaged pool memory.
695  *
696  * @param QueueType
697  *        Type of the queue to use for this item. Can be one of the following:
698  *          - DelayedWorkQueue
699  *          - CriticalWorkQueue
700  *          - HyperCriticalWorkQueue
701  *
702  * @return None.
703  *
704  * @remarks This routine is obsolete. Use IoQueueWorkItem instead.
705  *
706  *          Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
707  *
708  *--*/
709 VOID
710 NTAPI
711 ExQueueWorkItem(IN PWORK_QUEUE_ITEM WorkItem,
712                 IN WORK_QUEUE_TYPE QueueType)
713 {
714     PEX_WORK_QUEUE WorkQueue = &ExWorkerQueue[QueueType];
715     ASSERT(QueueType < MaximumWorkQueue);
716     ASSERT(WorkItem->List.Flink == NULL);
717 
718     /* Don't try to trick us */
719     if ((ULONG_PTR)WorkItem->WorkerRoutine < MmUserProbeAddress)
720     {
721         /* Bugcheck the system */
722         KeBugCheckEx(WORKER_INVALID,
723                      1,
724                      (ULONG_PTR)WorkItem,
725                      (ULONG_PTR)WorkItem->WorkerRoutine,
726                      0);
727     }
728 
729     /* Insert the Queue */
730     KeInsertQueue(&WorkQueue->WorkerQueue, &WorkItem->List);
731     ASSERT(!WorkQueue->Info.QueueDisabled);
732 
733     /*
734      * Check if we need a new thread. Our decision is as follows:
735      *  - This queue type must support Dynamic Threads (duh!)
736      *  - It actually has to have unprocessed items
737      *  - We have CPUs which could be handling another thread
738      *  - We haven't abused our usage of dynamic threads.
739      */
740     if ((WorkQueue->Info.MakeThreadsAsNecessary) &&
741         (!IsListEmpty(&WorkQueue->WorkerQueue.EntryListHead)) &&
742         (WorkQueue->WorkerQueue.CurrentCount <
743          WorkQueue->WorkerQueue.MaximumCount) &&
744         (WorkQueue->DynamicThreadCount < 16))
745     {
746         /* Let the balance manager know about it */
747         DPRINT1("Requesting a new thread. CurrentCount: %lu. MaxCount: %lu\n",
748                 WorkQueue->WorkerQueue.CurrentCount,
749                 WorkQueue->WorkerQueue.MaximumCount);
750         KeSetEvent(&ExpThreadSetManagerEvent, 0, FALSE);
751     }
752 }
753 
754 /* EOF */
755