2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * PURPOSE: Work Item implementation
5 * FILE: lib/rtl/workitem.c
9 /* INCLUDES *****************************************************************/
16 /* FUNCTIONS ***************************************************************/
18 #define MAX_WORKERTHREADS 0x100
19 #define WORKERTHREAD_CREATION_THRESHOLD 0x5
21 typedef struct _RTLP_IOWORKERTHREAD
26 } RTLP_IOWORKERTHREAD
, *PRTLP_IOWORKERTHREAD
;
28 typedef struct _RTLP_WORKITEM
30 WORKERCALLBACKFUNC Function
;
34 } RTLP_WORKITEM
, *PRTLP_WORKITEM
;
36 static LONG ThreadPoolInitialized
= 0;
37 static RTL_CRITICAL_SECTION ThreadPoolLock
;
38 static PRTLP_IOWORKERTHREAD PersistentIoThread
;
39 static LIST_ENTRY ThreadPoolIOWorkerThreadsList
;
40 static HANDLE ThreadPoolCompletionPort
;
41 static LONG ThreadPoolWorkerThreads
;
42 static LONG ThreadPoolWorkerThreadsRequests
;
43 static LONG ThreadPoolWorkerThreadsLongRequests
;
44 static LONG ThreadPoolIOWorkerThreads
;
45 static LONG ThreadPoolIOWorkerThreadsRequests
;
46 static LONG ThreadPoolIOWorkerThreadsLongRequests
;
48 #define IsThreadPoolInitialized() ((volatile LONG)ThreadPoolInitialized == 1)
51 RtlpInitializeThreadPool(VOID
)
53 NTSTATUS Status
= STATUS_SUCCESS
;
58 InitStatus
= _InterlockedCompareExchange(&ThreadPoolInitialized
,
63 /* We're the first thread to initialize the thread pool */
65 InitializeListHead(&ThreadPoolIOWorkerThreadsList
);
67 PersistentIoThread
= NULL
;
69 ThreadPoolWorkerThreads
= 0;
70 ThreadPoolWorkerThreadsRequests
= 0;
71 ThreadPoolWorkerThreadsLongRequests
= 0;
72 ThreadPoolIOWorkerThreads
= 0;
73 ThreadPoolIOWorkerThreadsRequests
= 0;
74 ThreadPoolIOWorkerThreadsLongRequests
= 0;
76 /* Initialize the lock */
77 Status
= RtlInitializeCriticalSection(&ThreadPoolLock
);
78 if (!NT_SUCCESS(Status
))
81 /* Create the complection port */
82 Status
= NtCreateIoCompletion(&ThreadPoolCompletionPort
,
83 IO_COMPLETION_ALL_ACCESS
,
86 if (!NT_SUCCESS(Status
))
88 RtlDeleteCriticalSection(&ThreadPoolLock
);
93 /* Initialization done */
94 _InterlockedExchange(&ThreadPoolInitialized
,
98 else if (InitStatus
== 2)
100 LARGE_INTEGER Timeout
;
102 /* Another thread is currently initializing the thread pool!
103 Poll after a short period of time to see if the initialization
106 Timeout
.QuadPart
= -10000000LL; /* Wait for a second */
107 NtDelayExecution(FALSE
,
110 } while (InitStatus
!= 1);
116 RtlpGetImpersonationToken(OUT PHANDLE TokenHandle
)
120 Status
= NtOpenThreadToken(NtCurrentThread(),
124 if (Status
== STATUS_NO_TOKEN
|| Status
== STATUS_CANT_OPEN_ANONYMOUS
)
127 Status
= STATUS_SUCCESS
;
134 RtlpStartWorkerThread(PTHREAD_START_ROUTINE StartRoutine
)
138 LARGE_INTEGER Timeout
;
139 volatile LONG WorkerInitialized
= 0;
141 Timeout
.QuadPart
= -10000LL; /* Wait for 100ms */
143 /* Start the thread */
144 Status
= RtlCreateUserThread(NtCurrentProcess(),
151 (PVOID
)&WorkerInitialized
,
155 if (NT_SUCCESS(Status
))
157 /* Poll until the thread got a chance to initialize */
158 while (WorkerInitialized
== 0)
160 NtDelayExecution(FALSE
,
164 NtClose(ThreadHandle
);
172 RtlpExecuteWorkItem(IN OUT PVOID NormalContext
,
173 IN OUT PVOID SystemArgument1
,
174 IN OUT PVOID SystemArgument2
)
177 BOOLEAN Impersonated
= FALSE
;
178 RTLP_WORKITEM WorkItem
= *(volatile RTLP_WORKITEM
*)SystemArgument2
;
180 RtlFreeHeap(RtlGetProcessHeap(),
184 if (WorkItem
.TokenHandle
!= NULL
)
186 Status
= NtSetInformationThread(NtCurrentThread(),
187 ThreadImpersonationToken
,
188 &WorkItem
.TokenHandle
,
191 NtClose(WorkItem
.TokenHandle
);
193 if (NT_SUCCESS(Status
))
201 DPRINT("RtlpExecuteWorkItem: Function: 0x%p Context: 0x%p ImpersonationToken: 0x%p\n", WorkItem
.Function
, WorkItem
.Context
, WorkItem
.TokenHandle
);
203 /* Execute the function */
204 WorkItem
.Function(WorkItem
.Context
);
206 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
208 DPRINT1("Exception 0x%x while executing IO work item 0x%p\n", _SEH2_GetExceptionCode(), WorkItem
.Function
);
214 WorkItem
.TokenHandle
= NULL
;
215 Status
= NtSetInformationThread(NtCurrentThread(),
216 ThreadImpersonationToken
,
217 &WorkItem
.TokenHandle
,
219 if (!NT_SUCCESS(Status
))
221 DPRINT1("Failed to revert worker thread to self!!! Status: 0x%x\n", Status
);
225 /* update the requests counter */
226 _InterlockedDecrement(&ThreadPoolWorkerThreadsRequests
);
228 if (WorkItem
.Flags
& WT_EXECUTELONGFUNCTION
)
230 _InterlockedDecrement(&ThreadPoolWorkerThreadsLongRequests
);
236 RtlpQueueWorkerThread(IN OUT PRTLP_WORKITEM WorkItem
)
238 NTSTATUS Status
= STATUS_SUCCESS
;
240 _InterlockedIncrement(&ThreadPoolWorkerThreadsRequests
);
242 if (WorkItem
->Flags
& WT_EXECUTELONGFUNCTION
)
244 _InterlockedIncrement(&ThreadPoolWorkerThreadsLongRequests
);
247 if (WorkItem
->Flags
& WT_EXECUTEINPERSISTENTTHREAD
)
249 Status
= RtlpInitializeTimerThread();
251 if (NT_SUCCESS(Status
))
253 /* Queue an APC in the timer thread */
254 Status
= NtQueueApcThread(TimerThreadHandle
,
263 /* Queue an IO completion message */
264 Status
= NtSetIoCompletion(ThreadPoolCompletionPort
,
271 if (!NT_SUCCESS(Status
))
273 _InterlockedDecrement(&ThreadPoolWorkerThreadsRequests
);
275 if (WorkItem
->Flags
& WT_EXECUTELONGFUNCTION
)
277 _InterlockedDecrement(&ThreadPoolWorkerThreadsLongRequests
);
286 RtlpExecuteIoWorkItem(IN OUT PVOID NormalContext
,
287 IN OUT PVOID SystemArgument1
,
288 IN OUT PVOID SystemArgument2
)
291 BOOLEAN Impersonated
= FALSE
;
292 PRTLP_IOWORKERTHREAD IoThread
= (PRTLP_IOWORKERTHREAD
)NormalContext
;
293 RTLP_WORKITEM WorkItem
= *(volatile RTLP_WORKITEM
*)SystemArgument2
;
295 ASSERT(IoThread
!= NULL
);
297 RtlFreeHeap(RtlGetProcessHeap(),
301 if (WorkItem
.TokenHandle
!= NULL
)
303 Status
= NtSetInformationThread(NtCurrentThread(),
304 ThreadImpersonationToken
,
305 &WorkItem
.TokenHandle
,
308 NtClose(WorkItem
.TokenHandle
);
310 if (NT_SUCCESS(Status
))
318 DPRINT("RtlpExecuteIoWorkItem: Function: 0x%p Context: 0x%p ImpersonationToken: 0x%p\n", WorkItem
.Function
, WorkItem
.Context
, WorkItem
.TokenHandle
);
320 /* Execute the function */
321 WorkItem
.Function(WorkItem
.Context
);
323 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
325 DPRINT1("Exception 0x%x while executing IO work item 0x%p\n", _SEH2_GetExceptionCode(), WorkItem
.Function
);
331 WorkItem
.TokenHandle
= NULL
;
332 Status
= NtSetInformationThread(NtCurrentThread(),
333 ThreadImpersonationToken
,
334 &WorkItem
.TokenHandle
,
336 if (!NT_SUCCESS(Status
))
338 DPRINT1("Failed to revert worker thread to self!!! Status: 0x%x\n", Status
);
342 /* remove the long function flag */
343 if (WorkItem
.Flags
& WT_EXECUTELONGFUNCTION
)
345 Status
= RtlEnterCriticalSection(&ThreadPoolLock
);
346 if (NT_SUCCESS(Status
))
348 IoThread
->Flags
&= ~WT_EXECUTELONGFUNCTION
;
349 RtlLeaveCriticalSection(&ThreadPoolLock
);
353 /* update the requests counter */
354 _InterlockedDecrement(&ThreadPoolIOWorkerThreadsRequests
);
356 if (WorkItem
.Flags
& WT_EXECUTELONGFUNCTION
)
358 _InterlockedDecrement(&ThreadPoolIOWorkerThreadsLongRequests
);
363 RtlpQueueIoWorkerThread(IN OUT PRTLP_WORKITEM WorkItem
)
365 PLIST_ENTRY CurrentEntry
;
366 PRTLP_IOWORKERTHREAD IoThread
= NULL
;
367 NTSTATUS Status
= STATUS_SUCCESS
;
369 if (WorkItem
->Flags
& WT_EXECUTEINPERSISTENTIOTHREAD
)
371 if (PersistentIoThread
!= NULL
)
373 /* We already have a persistent IO worker thread */
374 IoThread
= PersistentIoThread
;
378 /* We're not aware of any persistent IO worker thread. Search for a unused
379 worker thread that doesn't have a long function queued */
380 CurrentEntry
= ThreadPoolIOWorkerThreadsList
.Flink
;
381 while (CurrentEntry
!= &ThreadPoolIOWorkerThreadsList
)
383 IoThread
= CONTAINING_RECORD(CurrentEntry
,
387 if (!(IoThread
->Flags
& WT_EXECUTELONGFUNCTION
))
390 CurrentEntry
= CurrentEntry
->Flink
;
393 if (CurrentEntry
!= &ThreadPoolIOWorkerThreadsList
)
395 /* Found a worker thread we can use. */
396 ASSERT(IoThread
!= NULL
);
398 IoThread
->Flags
|= WT_EXECUTEINPERSISTENTIOTHREAD
;
399 PersistentIoThread
= IoThread
;
403 DPRINT1("Failed to find a worker thread for the persistent IO thread!\n");
404 return STATUS_NO_MEMORY
;
410 /* Find a worker thread that is not currently executing a long function */
411 CurrentEntry
= ThreadPoolIOWorkerThreadsList
.Flink
;
412 while (CurrentEntry
!= &ThreadPoolIOWorkerThreadsList
)
414 IoThread
= CONTAINING_RECORD(CurrentEntry
,
418 if (!(IoThread
->Flags
& WT_EXECUTELONGFUNCTION
))
420 /* if we're trying to queue a long function then make sure we're not dealing
421 with the persistent thread */
422 if ((WorkItem
->Flags
& WT_EXECUTELONGFUNCTION
) && !(IoThread
->Flags
& WT_EXECUTEINPERSISTENTIOTHREAD
))
424 /* found a candidate */
429 CurrentEntry
= CurrentEntry
->Flink
;
432 if (CurrentEntry
== &ThreadPoolIOWorkerThreadsList
)
434 /* Couldn't find an appropriate thread, see if we can use the persistent thread (if it exists) for now */
435 if (ThreadPoolIOWorkerThreads
== 0)
437 DPRINT1("Failed to find a worker thread for the work item 0x%p!\n");
438 ASSERT(IsListEmpty(&ThreadPoolIOWorkerThreadsList
));
439 return STATUS_NO_MEMORY
;
443 /* pick the first worker thread */
444 CurrentEntry
= ThreadPoolIOWorkerThreadsList
.Flink
;
445 IoThread
= CONTAINING_RECORD(CurrentEntry
,
449 /* Since this might be the persistent worker thread, don't run as a
451 WorkItem
->Flags
&= ~WT_EXECUTELONGFUNCTION
;
455 /* Move the picked thread to the end of the list. Since we're always searching
456 from the beginning, this improves distribution of work items */
457 RemoveEntryList(&IoThread
->ListEntry
);
458 InsertTailList(&ThreadPoolIOWorkerThreadsList
,
459 &IoThread
->ListEntry
);
462 ASSERT(IoThread
!= NULL
);
464 _InterlockedIncrement(&ThreadPoolIOWorkerThreadsRequests
);
466 if (WorkItem
->Flags
& WT_EXECUTELONGFUNCTION
)
468 /* We're about to queue a long function, mark the thread */
469 IoThread
->Flags
|= WT_EXECUTELONGFUNCTION
;
471 _InterlockedIncrement(&ThreadPoolIOWorkerThreadsLongRequests
);
474 /* It's time to queue the work item */
475 Status
= NtQueueApcThread(IoThread
->ThreadHandle
,
476 RtlpExecuteIoWorkItem
,
480 if (!NT_SUCCESS(Status
))
482 DPRINT1("Failed to queue APC for work item 0x%p\n", WorkItem
->Function
);
483 _InterlockedDecrement(&ThreadPoolIOWorkerThreadsRequests
);
485 if (WorkItem
->Flags
& WT_EXECUTELONGFUNCTION
)
487 _InterlockedDecrement(&ThreadPoolIOWorkerThreadsLongRequests
);
495 RtlpIsIoPending(IN HANDLE ThreadHandle OPTIONAL
)
499 BOOLEAN CreatedHandle
= FALSE
;
500 BOOLEAN IsIoPending
= TRUE
;
502 if (ThreadHandle
== NULL
)
504 Status
= NtDuplicateObject(NtCurrentProcess(),
510 DUPLICATE_SAME_ACCESS
);
511 if (!NT_SUCCESS(Status
))
516 CreatedHandle
= TRUE
;
519 Status
= NtQueryInformationThread(ThreadHandle
,
524 if (NT_SUCCESS(Status
) && IoPending
== 0)
531 NtClose(ThreadHandle
);
539 RtlpIoWorkerThreadProc(IN PVOID Parameter
)
541 volatile RTLP_IOWORKERTHREAD ThreadInfo
;
542 LARGE_INTEGER Timeout
;
544 NTSTATUS Status
= STATUS_SUCCESS
;
546 if (_InterlockedIncrement(&ThreadPoolIOWorkerThreads
) > MAX_WORKERTHREADS
)
548 /* Oops, too many worker threads... */
552 /* Get a thread handle to ourselves */
553 Status
= NtDuplicateObject(NtCurrentProcess(),
556 (PHANDLE
)&ThreadInfo
.ThreadHandle
,
559 DUPLICATE_SAME_ACCESS
);
560 if (!NT_SUCCESS(Status
))
562 DPRINT1("Failed to create handle to own thread! Status: 0x%x\n", Status
);
565 _InterlockedDecrement(&ThreadPoolIOWorkerThreads
);
567 /* Signal initialization completion */
568 _InterlockedExchange((PLONG
)Parameter
,
571 RtlExitUserThread(Status
);
575 ThreadInfo
.Flags
= 0;
577 /* Insert the thread into the list */
578 InsertHeadList((PLIST_ENTRY
)&ThreadPoolIOWorkerThreadsList
,
579 (PLIST_ENTRY
)&ThreadInfo
.ListEntry
);
581 /* Signal initialization completion */
582 _InterlockedExchange((PLONG
)Parameter
,
587 Timeout
.QuadPart
= -50000000LL; /* Wait for 5 seconds by default */
592 /* Perform an alertable wait, the work items are going to be executed as APCs */
593 Status
= NtDelayExecution(TRUE
,
596 /* Loop as long as we executed an APC */
597 } while (Status
!= STATUS_SUCCESS
);
599 /* We timed out, let's see if we're allowed to terminate */
602 Status
= RtlEnterCriticalSection(&ThreadPoolLock
);
603 if (NT_SUCCESS(Status
))
605 if (ThreadInfo
.Flags
& WT_EXECUTEINPERSISTENTIOTHREAD
)
607 /* This thread is supposed to be persistent. Don't terminate! */
608 RtlLeaveCriticalSection(&ThreadPoolLock
);
610 Timeout
.QuadPart
= -0x7FFFFFFFFFFFFFFFLL
;
614 /* FIXME - figure out an effective method to determine if it's appropriate to
615 lower the number of threads. For now let's always terminate if there's
616 at least one thread and no queued items. */
617 Terminate
= ((volatile LONG
)ThreadPoolIOWorkerThreads
- (volatile LONG
)ThreadPoolIOWorkerThreadsLongRequests
>= WORKERTHREAD_CREATION_THRESHOLD
) &&
618 ((volatile LONG
)ThreadPoolIOWorkerThreadsRequests
== 0);
622 /* Prevent termination as long as IO is pending */
623 Terminate
= !RtlpIsIoPending(ThreadInfo
.ThreadHandle
);
628 /* Rundown the thread and unlink it from the list */
629 _InterlockedDecrement(&ThreadPoolIOWorkerThreads
);
630 RemoveEntryList((PLIST_ENTRY
)&ThreadInfo
.ListEntry
);
633 RtlLeaveCriticalSection(&ThreadPoolLock
);
637 /* Break the infinite loop and terminate */
638 Status
= STATUS_SUCCESS
;
644 DPRINT1("Failed to acquire the thread pool lock!!! Status: 0x%x\n", Status
);
649 NtClose(ThreadInfo
.ThreadHandle
);
650 RtlExitUserThread(Status
);
656 RtlpWorkerThreadProc(IN PVOID Parameter
)
658 LARGE_INTEGER Timeout
;
660 PVOID SystemArgument2
;
661 IO_STATUS_BLOCK IoStatusBlock
;
662 ULONG TimeoutCount
= 0;
663 PKNORMAL_ROUTINE ApcRoutine
;
664 NTSTATUS Status
= STATUS_SUCCESS
;
666 if (_InterlockedIncrement(&ThreadPoolWorkerThreads
) > MAX_WORKERTHREADS
)
668 /* Signal initialization completion */
669 _InterlockedExchange((PLONG
)Parameter
,
672 /* Oops, too many worker threads... */
673 RtlExitUserThread(Status
);
677 /* Signal initialization completion */
678 _InterlockedExchange((PLONG
)Parameter
,
683 Timeout
.QuadPart
= -50000000LL; /* Wait for 5 seconds by default */
685 /* Dequeue a completion message */
686 Status
= NtRemoveIoCompletion(ThreadPoolCompletionPort
,
692 if (Status
== STATUS_SUCCESS
)
698 /* Call the APC routine */
700 (PVOID
)IoStatusBlock
.Information
,
703 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
712 if (!NT_SUCCESS(RtlEnterCriticalSection(&ThreadPoolLock
)))
715 /* FIXME - this should be optimized, check if there's requests, etc */
717 if (Status
== STATUS_TIMEOUT
)
719 /* FIXME - we might want to optimize this */
720 if (TimeoutCount
++ > 2 &&
721 (volatile LONG
)ThreadPoolWorkerThreads
- (volatile LONG
)ThreadPoolWorkerThreadsLongRequests
>= WORKERTHREAD_CREATION_THRESHOLD
)
729 RtlLeaveCriticalSection(&ThreadPoolLock
);
733 /* Prevent termination as long as IO is pending */
734 Terminate
= !RtlpIsIoPending(NULL
);
739 _InterlockedDecrement(&ThreadPoolWorkerThreads
);
740 Status
= STATUS_SUCCESS
;
746 RtlExitUserThread(Status
);
756 RtlQueueWorkItem(IN WORKERCALLBACKFUNC Function
,
757 IN PVOID Context OPTIONAL
,
762 PRTLP_WORKITEM WorkItem
;
764 DPRINT("RtlQueueWorkItem(0x%p, 0x%p, 0x%x)\n", Function
, Context
, Flags
);
766 /* Initialize the thread pool if not already initialized */
767 if (!IsThreadPoolInitialized())
769 Status
= RtlpInitializeThreadPool();
771 if (!NT_SUCCESS(Status
))
775 /* Allocate a work item */
776 WorkItem
= RtlAllocateHeap(RtlGetProcessHeap(),
778 sizeof(RTLP_WORKITEM
));
779 if (WorkItem
== NULL
)
780 return STATUS_NO_MEMORY
;
782 WorkItem
->Function
= Function
;
783 WorkItem
->Context
= Context
;
784 WorkItem
->Flags
= Flags
;
786 if (Flags
& WT_TRANSFER_IMPERSONATION
)
788 Status
= RtlpGetImpersonationToken(&WorkItem
->TokenHandle
);
790 if (!NT_SUCCESS(Status
))
792 DPRINT1("Failed to get impersonation token! Status: 0x%x\n", Status
);
797 WorkItem
->TokenHandle
= NULL
;
799 Status
= RtlEnterCriticalSection(&ThreadPoolLock
);
800 if (NT_SUCCESS(Status
))
802 if (Flags
& (WT_EXECUTEINIOTHREAD
| WT_EXECUTEINUITHREAD
| WT_EXECUTEINPERSISTENTIOTHREAD
))
804 /* FIXME - We should optimize the algorithm used to determine whether to grow the thread pool! */
806 FreeWorkers
= ThreadPoolIOWorkerThreads
- ThreadPoolIOWorkerThreadsLongRequests
;
808 if (((Flags
& (WT_EXECUTEINPERSISTENTIOTHREAD
| WT_EXECUTELONGFUNCTION
)) == WT_EXECUTELONGFUNCTION
) &&
809 PersistentIoThread
!= NULL
)
811 /* We shouldn't queue a long function into the persistent IO thread */
815 /* See if it's a good idea to grow the pool */
816 if (ThreadPoolIOWorkerThreads
< MAX_WORKERTHREADS
&&
817 (FreeWorkers
<= 0 || ThreadPoolIOWorkerThreads
- ThreadPoolIOWorkerThreadsRequests
< WORKERTHREAD_CREATION_THRESHOLD
))
819 /* Grow the thread pool */
820 Status
= RtlpStartWorkerThread(RtlpIoWorkerThreadProc
);
822 if (!NT_SUCCESS(Status
) && (volatile LONG
)ThreadPoolIOWorkerThreads
!= 0)
824 /* We failed to create the thread, but there's at least one there so
825 we can at least queue the request */
826 Status
= STATUS_SUCCESS
;
830 if (NT_SUCCESS(Status
))
832 /* Queue a IO worker thread */
833 Status
= RtlpQueueIoWorkerThread(WorkItem
);
838 /* FIXME - We should optimize the algorithm used to determine whether to grow the thread pool! */
840 FreeWorkers
= ThreadPoolWorkerThreads
- ThreadPoolWorkerThreadsLongRequests
;
842 /* See if it's a good idea to grow the pool */
843 if (ThreadPoolWorkerThreads
< MAX_WORKERTHREADS
&&
844 (FreeWorkers
<= 0 || ThreadPoolWorkerThreads
- ThreadPoolWorkerThreadsRequests
< WORKERTHREAD_CREATION_THRESHOLD
))
846 /* Grow the thread pool */
847 Status
= RtlpStartWorkerThread(RtlpWorkerThreadProc
);
849 if (!NT_SUCCESS(Status
) && (volatile LONG
)ThreadPoolWorkerThreads
!= 0)
851 /* We failed to create the thread, but there's at least one there so
852 we can at least queue the request */
853 Status
= STATUS_SUCCESS
;
857 if (NT_SUCCESS(Status
))
859 /* Queue a normal worker thread */
860 Status
= RtlpQueueWorkerThread(WorkItem
);
864 RtlLeaveCriticalSection(&ThreadPoolLock
);
867 if (!NT_SUCCESS(Status
))
869 if (WorkItem
->TokenHandle
!= NULL
)
871 NtClose(WorkItem
->TokenHandle
);
875 RtlFreeHeap(RtlGetProcessHeap(),
888 RtlSetIoCompletionCallback(IN HANDLE FileHandle
,
889 IN PIO_APC_ROUTINE Callback
,
893 return STATUS_NOT_IMPLEMENTED
;