xref: /reactos/ntoskrnl/ex/work.c (revision 5c7ce447)
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 // #ifdef _WINKD_
487         /*
488          * If WinDBG wants to attach or kill a user-mode process, and/or
489          * page-in an address region, queue a debugger worker thread.
490          */
491         if (ExpDebuggerWork == WinKdWorkerStart)
492         {
493              ExInitializeWorkItem(&ExpDebuggerWorkItem, ExpDebuggerWorker, NULL);
494              ExpDebuggerWork = WinKdWorkerInitialized;
495              ExQueueWorkItem(&ExpDebuggerWorkItem, DelayedWorkQueue);
496         }
497 // #endif /* _WINKD_ */
498     }
499 }
500 
501 /*++
502  * @name ExpInitializeWorkerThreads
503  *
504  *     The ExpInitializeWorkerThreads routine initializes worker thread and
505  *     work queue support.
506  *
507  * @param None.
508  *
509  * @return None.
510  *
511  * @remarks This routine is only called once during system initialization.
512  *
513  *--*/
514 CODE_SEG("INIT")
515 VOID
516 NTAPI
517 ExpInitializeWorkerThreads(VOID)
518 {
519     ULONG WorkQueueType;
520     ULONG CriticalThreads, DelayedThreads;
521     HANDLE ThreadHandle;
522     PETHREAD Thread;
523     ULONG i;
524 
525     /* Setup the stack swap support */
526     ExInitializeFastMutex(&ExpWorkerSwapinMutex);
527     InitializeListHead(&ExpWorkerListHead);
528     ExpWorkersCanSwap = TRUE;
529 
530     /* Set the number of critical and delayed threads. We shouldn't hardcode */
531     DelayedThreads = EX_DELAYED_WORK_THREADS;
532     CriticalThreads = EX_CRITICAL_WORK_THREADS;
533 
534     /* Protect against greedy registry modifications */
535     ExpAdditionalDelayedWorkerThreads =
536         min(ExpAdditionalDelayedWorkerThreads, 16);
537     ExpAdditionalCriticalWorkerThreads =
538         min(ExpAdditionalCriticalWorkerThreads, 16);
539 
540     /* Calculate final count */
541     DelayedThreads += ExpAdditionalDelayedWorkerThreads;
542     CriticalThreads += ExpAdditionalCriticalWorkerThreads;
543 
544     /* Initialize the Array */
545     for (WorkQueueType = 0; WorkQueueType < MaximumWorkQueue; WorkQueueType++)
546     {
547         /* Clear the structure and initialize the queue */
548         RtlZeroMemory(&ExWorkerQueue[WorkQueueType], sizeof(EX_WORK_QUEUE));
549         KeInitializeQueue(&ExWorkerQueue[WorkQueueType].WorkerQueue, 0);
550     }
551 
552     /* Dynamic threads are only used for the critical queue */
553     ExWorkerQueue[CriticalWorkQueue].Info.MakeThreadsAsNecessary = TRUE;
554 
555     /* Initialize the balance set manager events */
556     KeInitializeEvent(&ExpThreadSetManagerEvent, SynchronizationEvent, FALSE);
557     KeInitializeEvent(&ExpThreadSetManagerShutdownEvent,
558                       NotificationEvent,
559                       FALSE);
560 
561     /* Create the built-in worker threads for the critical queue */
562     for (i = 0; i < CriticalThreads; i++)
563     {
564         /* Create the thread */
565         ExpCreateWorkerThread(CriticalWorkQueue, FALSE);
566         ExCriticalWorkerThreads++;
567     }
568 
569     /* Create the built-in worker threads for the delayed queue */
570     for (i = 0; i < DelayedThreads; i++)
571     {
572         /* Create the thread */
573         ExpCreateWorkerThread(DelayedWorkQueue, FALSE);
574         ExDelayedWorkerThreads++;
575     }
576 
577     /* Create the built-in worker thread for the hypercritical queue */
578     ExpCreateWorkerThread(HyperCriticalWorkQueue, FALSE);
579 
580     /* Create the balance set manager thread */
581     PsCreateSystemThread(&ThreadHandle,
582                          THREAD_ALL_ACCESS,
583                          NULL,
584                          0,
585                          NULL,
586                          ExpWorkerThreadBalanceManager,
587                          NULL);
588 
589     /* Get a pointer to it for the shutdown process */
590     ObReferenceObjectByHandle(ThreadHandle,
591                               THREAD_ALL_ACCESS,
592                               NULL,
593                               KernelMode,
594                               (PVOID*)&Thread,
595                               NULL);
596     ExpWorkerThreadBalanceManagerPtr = Thread;
597 
598     /* Close the handle and return */
599     ObCloseHandle(ThreadHandle, KernelMode);
600 }
601 
602 VOID
603 NTAPI
604 ExpSetSwappingKernelApc(IN PKAPC Apc,
605                         OUT PKNORMAL_ROUTINE *NormalRoutine,
606                         IN OUT PVOID *NormalContext,
607                         IN OUT PVOID *SystemArgument1,
608                         IN OUT PVOID *SystemArgument2)
609 {
610     PBOOLEAN AllowSwap;
611     PKEVENT Event = (PKEVENT)*SystemArgument1;
612 
613     /* Make sure it's an active worker */
614     if (PsGetCurrentThread()->ActiveExWorker)
615     {
616         /* Read the setting from the context flag */
617         AllowSwap = (PBOOLEAN)NormalContext;
618         KeSetKernelStackSwapEnable(*AllowSwap);
619     }
620 
621     /* Let caller know that we're done */
622     KeSetEvent(Event, 0, FALSE);
623 }
624 
625 VOID
626 NTAPI
627 ExSwapinWorkerThreads(IN BOOLEAN AllowSwap)
628 {
629     KEVENT Event;
630     PETHREAD CurrentThread = PsGetCurrentThread(), Thread;
631     PEPROCESS Process = PsInitialSystemProcess;
632     KAPC Apc;
633     PAGED_CODE();
634 
635     /* Initialize an event so we know when we're done */
636     KeInitializeEvent(&Event, NotificationEvent, FALSE);
637 
638     /* Lock this routine */
639     ExAcquireFastMutex(&ExpWorkerSwapinMutex);
640 
641     /* New threads cannot swap anymore */
642     ExpWorkersCanSwap = AllowSwap;
643 
644     /* Loop all threads in the system process */
645     Thread = PsGetNextProcessThread(Process, NULL);
646     while (Thread)
647     {
648         /* Skip threads with explicit permission to do this */
649         if (Thread->ExWorkerCanWaitUser) goto Next;
650 
651         /* Check if we reached ourselves */
652         if (Thread == CurrentThread)
653         {
654             /* Do it inline */
655             KeSetKernelStackSwapEnable(AllowSwap);
656         }
657         else
658         {
659             /* Queue an APC */
660             KeInitializeApc(&Apc,
661                             &Thread->Tcb,
662                             InsertApcEnvironment,
663                             ExpSetSwappingKernelApc,
664                             NULL,
665                             NULL,
666                             KernelMode,
667                             &AllowSwap);
668             if (KeInsertQueueApc(&Apc, &Event, NULL, 3))
669             {
670                 /* Wait for the APC to run */
671                 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
672                 KeClearEvent(&Event);
673             }
674         }
675 
676         /* Next thread */
677 Next:
678         Thread = PsGetNextProcessThread(Process, Thread);
679     }
680 
681     /* Release the lock */
682     ExReleaseFastMutex(&ExpWorkerSwapinMutex);
683 }
684 
685 /* PUBLIC FUNCTIONS **********************************************************/
686 
687 /*++
688  * @name ExQueueWorkItem
689  * @implemented NT4
690  *
691  *     The ExQueueWorkItem routine acquires rundown protection for
692  *     the specified descriptor.
693  *
694  * @param WorkItem
695  *        Pointer to an initialized Work Queue Item structure. This structure
696  *        must be located in nonpaged pool memory.
697  *
698  * @param QueueType
699  *        Type of the queue to use for this item. Can be one of the following:
700  *          - DelayedWorkQueue
701  *          - CriticalWorkQueue
702  *          - HyperCriticalWorkQueue
703  *
704  * @return None.
705  *
706  * @remarks This routine is obsolete. Use IoQueueWorkItem instead.
707  *
708  *          Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
709  *
710  *--*/
711 VOID
712 NTAPI
713 ExQueueWorkItem(IN PWORK_QUEUE_ITEM WorkItem,
714                 IN WORK_QUEUE_TYPE QueueType)
715 {
716     PEX_WORK_QUEUE WorkQueue = &ExWorkerQueue[QueueType];
717     ASSERT(QueueType < MaximumWorkQueue);
718     ASSERT(WorkItem->List.Flink == NULL);
719 
720     /* Don't try to trick us */
721     if ((ULONG_PTR)WorkItem->WorkerRoutine < MmUserProbeAddress)
722     {
723         /* Bugcheck the system */
724         KeBugCheckEx(WORKER_INVALID,
725                      1,
726                      (ULONG_PTR)WorkItem,
727                      (ULONG_PTR)WorkItem->WorkerRoutine,
728                      0);
729     }
730 
731     /* Insert the Queue */
732     KeInsertQueue(&WorkQueue->WorkerQueue, &WorkItem->List);
733     ASSERT(!WorkQueue->Info.QueueDisabled);
734 
735     /*
736      * Check if we need a new thread. Our decision is as follows:
737      *  - This queue type must support Dynamic Threads (duh!)
738      *  - It actually has to have unprocessed items
739      *  - We have CPUs which could be handling another thread
740      *  - We haven't abused our usage of dynamic threads.
741      */
742     if ((WorkQueue->Info.MakeThreadsAsNecessary) &&
743         (!IsListEmpty(&WorkQueue->WorkerQueue.EntryListHead)) &&
744         (WorkQueue->WorkerQueue.CurrentCount <
745          WorkQueue->WorkerQueue.MaximumCount) &&
746         (WorkQueue->DynamicThreadCount < 16))
747     {
748         /* Let the balance manager know about it */
749         DPRINT1("Requesting a new thread. CurrentCount: %lu. MaxCount: %lu\n",
750                 WorkQueue->WorkerQueue.CurrentCount,
751                 WorkQueue->WorkerQueue.MaximumCount);
752         KeSetEvent(&ExpThreadSetManagerEvent, 0, FALSE);
753     }
754 }
755 
756 /* EOF */
757