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