|  | /* | 
|  | * Thread pooling | 
|  | * | 
|  | * Copyright (c) 2006 Robert Shearman | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, write to the Free Software | 
|  | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "wine/port.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <stdarg.h> | 
|  | #include <limits.h> | 
|  |  | 
|  | #define NONAMELESSUNION | 
|  | #include "ntstatus.h" | 
|  | #define WIN32_NO_STATUS | 
|  | #include "winternl.h" | 
|  |  | 
|  | #include "wine/debug.h" | 
|  | #include "wine/list.h" | 
|  |  | 
|  | #include "ntdll_misc.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(threadpool); | 
|  |  | 
|  | #define WORKER_TIMEOUT 30000 /* 30 seconds */ | 
|  |  | 
|  | static LONG num_workers; | 
|  | static LONG num_work_items; | 
|  | static LONG num_busy_workers; | 
|  |  | 
|  | static struct list work_item_list = LIST_INIT(work_item_list); | 
|  | static HANDLE work_item_event; | 
|  |  | 
|  | static RTL_CRITICAL_SECTION threadpool_cs; | 
|  | static RTL_CRITICAL_SECTION_DEBUG critsect_debug = | 
|  | { | 
|  | 0, 0, &threadpool_cs, | 
|  | { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, | 
|  | 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_cs") } | 
|  | }; | 
|  | static RTL_CRITICAL_SECTION threadpool_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; | 
|  |  | 
|  | static HANDLE compl_port = NULL; | 
|  | static RTL_CRITICAL_SECTION threadpool_compl_cs; | 
|  | static RTL_CRITICAL_SECTION_DEBUG critsect_compl_debug = | 
|  | { | 
|  | 0, 0, &threadpool_compl_cs, | 
|  | { &critsect_compl_debug.ProcessLocksList, &critsect_compl_debug.ProcessLocksList }, | 
|  | 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_compl_cs") } | 
|  | }; | 
|  | static RTL_CRITICAL_SECTION threadpool_compl_cs = { &critsect_compl_debug, -1, 0, 0, 0, 0 }; | 
|  |  | 
|  | struct work_item | 
|  | { | 
|  | struct list entry; | 
|  | PRTL_WORK_ITEM_ROUTINE function; | 
|  | PVOID context; | 
|  | }; | 
|  |  | 
|  | static inline LONG interlocked_inc( PLONG dest ) | 
|  | { | 
|  | return interlocked_xchg_add( dest, 1 ) + 1; | 
|  | } | 
|  |  | 
|  | static inline LONG interlocked_dec( PLONG dest ) | 
|  | { | 
|  | return interlocked_xchg_add( dest, -1 ) - 1; | 
|  | } | 
|  |  | 
|  | static void WINAPI worker_thread_proc(void * param) | 
|  | { | 
|  | interlocked_inc(&num_workers); | 
|  |  | 
|  | /* free the work item memory sooner to reduce memory usage */ | 
|  | while (TRUE) | 
|  | { | 
|  | if (num_work_items > 0) | 
|  | { | 
|  | struct list *item; | 
|  | RtlEnterCriticalSection(&threadpool_cs); | 
|  | item = list_head(&work_item_list); | 
|  | if (item) | 
|  | { | 
|  | struct work_item *work_item_ptr = LIST_ENTRY(item, struct work_item, entry); | 
|  | struct work_item work_item; | 
|  | list_remove(&work_item_ptr->entry); | 
|  | interlocked_dec(&num_work_items); | 
|  |  | 
|  | RtlLeaveCriticalSection(&threadpool_cs); | 
|  |  | 
|  | work_item = *work_item_ptr; | 
|  | RtlFreeHeap(GetProcessHeap(), 0, work_item_ptr); | 
|  |  | 
|  | TRACE("executing %p(%p)\n", work_item.function, work_item.context); | 
|  |  | 
|  | interlocked_inc(&num_busy_workers); | 
|  |  | 
|  | /* do the work */ | 
|  | work_item.function(work_item.context); | 
|  |  | 
|  | interlocked_dec(&num_busy_workers); | 
|  | } | 
|  | else | 
|  | RtlLeaveCriticalSection(&threadpool_cs); | 
|  | } | 
|  | else | 
|  | { | 
|  | NTSTATUS status; | 
|  | LARGE_INTEGER timeout; | 
|  | timeout.QuadPart = -(WORKER_TIMEOUT * (ULONGLONG)10000); | 
|  | status = NtWaitForSingleObject(work_item_event, FALSE, &timeout); | 
|  | if (status != STATUS_WAIT_0) | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | interlocked_dec(&num_workers); | 
|  |  | 
|  | RtlExitUserThread(0); | 
|  |  | 
|  | /* never reached */ | 
|  | } | 
|  |  | 
|  | static NTSTATUS add_work_item_to_queue(struct work_item *work_item) | 
|  | { | 
|  | NTSTATUS status; | 
|  |  | 
|  | RtlEnterCriticalSection(&threadpool_cs); | 
|  | list_add_tail(&work_item_list, &work_item->entry); | 
|  | num_work_items++; | 
|  | RtlLeaveCriticalSection(&threadpool_cs); | 
|  |  | 
|  | if (!work_item_event) | 
|  | { | 
|  | HANDLE sem; | 
|  | status = NtCreateSemaphore(&sem, SEMAPHORE_ALL_ACCESS, NULL, 1, INT_MAX); | 
|  | if (interlocked_cmpxchg_ptr( &work_item_event, sem, 0 )) | 
|  | NtClose(sem);  /* somebody beat us to it */ | 
|  | } | 
|  | else | 
|  | status = NtReleaseSemaphore(work_item_event, 1, NULL); | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlQueueWorkItem   (NTDLL.@) | 
|  | * | 
|  | * Queues a work item into a thread in the thread pool. | 
|  | * | 
|  | * PARAMS | 
|  | *  Function [I] Work function to execute. | 
|  | *  Context  [I] Context to pass to the work function when it is executed. | 
|  | *  Flags    [I] Flags. See notes. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | * | 
|  | * NOTES | 
|  | *  Flags can be one or more of the following: | 
|  | *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread. | 
|  | *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread. | 
|  | *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent. | 
|  | *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time. | 
|  | *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlQueueWorkItem(PRTL_WORK_ITEM_ROUTINE Function, PVOID Context, ULONG Flags) | 
|  | { | 
|  | HANDLE thread; | 
|  | NTSTATUS status; | 
|  | struct work_item *work_item = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(struct work_item)); | 
|  |  | 
|  | if (!work_item) | 
|  | return STATUS_NO_MEMORY; | 
|  |  | 
|  | work_item->function = Function; | 
|  | work_item->context = Context; | 
|  |  | 
|  | if (Flags & ~WT_EXECUTELONGFUNCTION) | 
|  | FIXME("Flags 0x%x not supported\n", Flags); | 
|  |  | 
|  | status = add_work_item_to_queue(work_item); | 
|  |  | 
|  | /* FIXME: tune this algorithm to not be as aggressive with creating threads | 
|  | * if WT_EXECUTELONGFUNCTION isn't specified */ | 
|  | if ((status == STATUS_SUCCESS) && | 
|  | ((num_workers == 0) || (num_workers == num_busy_workers))) | 
|  | { | 
|  | status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE, | 
|  | NULL, 0, 0, | 
|  | worker_thread_proc, NULL, &thread, NULL ); | 
|  | if (status == STATUS_SUCCESS) | 
|  | NtClose( thread ); | 
|  |  | 
|  | /* NOTE: we don't care if we couldn't create the thread if there is at | 
|  | * least one other available to process the request */ | 
|  | if ((num_workers > 0) && (status != STATUS_SUCCESS)) | 
|  | status = STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | if (status != STATUS_SUCCESS) | 
|  | { | 
|  | RtlEnterCriticalSection(&threadpool_cs); | 
|  |  | 
|  | interlocked_dec(&num_work_items); | 
|  | list_remove(&work_item->entry); | 
|  | RtlFreeHeap(GetProcessHeap(), 0, work_item); | 
|  |  | 
|  | RtlLeaveCriticalSection(&threadpool_cs); | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | return STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | * iocp_poller - get completion events and run callbacks | 
|  | */ | 
|  | static DWORD CALLBACK iocp_poller(LPVOID Arg) | 
|  | { | 
|  | while( TRUE ) | 
|  | { | 
|  | PRTL_OVERLAPPED_COMPLETION_ROUTINE callback; | 
|  | LPVOID overlapped; | 
|  | IO_STATUS_BLOCK iosb; | 
|  | NTSTATUS res = NtRemoveIoCompletion( compl_port, (PULONG_PTR)&callback, (PULONG_PTR)&overlapped, &iosb, NULL ); | 
|  | if (res) | 
|  | { | 
|  | ERR("NtRemoveIoCompletion failed: 0x%x\n", res); | 
|  | } | 
|  | else | 
|  | { | 
|  | DWORD transferred = 0; | 
|  | DWORD err = 0; | 
|  |  | 
|  | if (iosb.u.Status == STATUS_SUCCESS) | 
|  | transferred = iosb.Information; | 
|  | else | 
|  | err = RtlNtStatusToDosError(iosb.u.Status); | 
|  |  | 
|  | callback( err, transferred, overlapped ); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlSetIoCompletionCallback  (NTDLL.@) | 
|  | * | 
|  | * Binds a handle to a thread pool's completion port, and possibly | 
|  | * starts a non-I/O thread to monitor this port and call functions back. | 
|  | * | 
|  | * PARAMS | 
|  | *  FileHandle [I] Handle to bind to a completion port. | 
|  | *  Function   [I] Callback function to call on I/O completions. | 
|  | *  Flags      [I] Not used. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | * | 
|  | */ | 
|  | NTSTATUS WINAPI RtlSetIoCompletionCallback(HANDLE FileHandle, PRTL_OVERLAPPED_COMPLETION_ROUTINE Function, ULONG Flags) | 
|  | { | 
|  | IO_STATUS_BLOCK iosb; | 
|  | FILE_COMPLETION_INFORMATION info; | 
|  |  | 
|  | if (Flags) FIXME("Unknown value Flags=0x%x\n", Flags); | 
|  |  | 
|  | if (!compl_port) | 
|  | { | 
|  | NTSTATUS res = STATUS_SUCCESS; | 
|  |  | 
|  | RtlEnterCriticalSection(&threadpool_compl_cs); | 
|  | if (!compl_port) | 
|  | { | 
|  | HANDLE cport; | 
|  |  | 
|  | res = NtCreateIoCompletion( &cport, IO_COMPLETION_ALL_ACCESS, NULL, 0 ); | 
|  | if (!res) | 
|  | { | 
|  | /* FIXME native can start additional threads in case of e.g. hung callback function. */ | 
|  | res = RtlQueueWorkItem( iocp_poller, NULL, WT_EXECUTEDEFAULT ); | 
|  | if (!res) | 
|  | compl_port = cport; | 
|  | else | 
|  | NtClose( cport ); | 
|  | } | 
|  | } | 
|  | RtlLeaveCriticalSection(&threadpool_compl_cs); | 
|  | if (res) return res; | 
|  | } | 
|  |  | 
|  | info.CompletionPort = compl_port; | 
|  | info.CompletionKey = (ULONG_PTR)Function; | 
|  |  | 
|  | return NtSetInformationFile( FileHandle, &iosb, &info, sizeof(info), FileCompletionInformation ); | 
|  | } | 
|  |  | 
|  | static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout ) | 
|  | { | 
|  | if (timeout == INFINITE) return NULL; | 
|  | pTime->QuadPart = (ULONGLONG)timeout * -10000; | 
|  | return pTime; | 
|  | } | 
|  |  | 
|  | struct wait_work_item | 
|  | { | 
|  | HANDLE Object; | 
|  | HANDLE CancelEvent; | 
|  | WAITORTIMERCALLBACK Callback; | 
|  | PVOID Context; | 
|  | ULONG Milliseconds; | 
|  | ULONG Flags; | 
|  | HANDLE CompletionEvent; | 
|  | LONG DeleteCount; | 
|  | BOOLEAN CallbackInProgress; | 
|  | }; | 
|  |  | 
|  | static void delete_wait_work_item(struct wait_work_item *wait_work_item) | 
|  | { | 
|  | NtClose( wait_work_item->CancelEvent ); | 
|  | RtlFreeHeap( GetProcessHeap(), 0, wait_work_item ); | 
|  | } | 
|  |  | 
|  | static DWORD CALLBACK wait_thread_proc(LPVOID Arg) | 
|  | { | 
|  | struct wait_work_item *wait_work_item = Arg; | 
|  | NTSTATUS status; | 
|  | BOOLEAN alertable = (wait_work_item->Flags & WT_EXECUTEINIOTHREAD) ? TRUE : FALSE; | 
|  | HANDLE handles[2] = { wait_work_item->Object, wait_work_item->CancelEvent }; | 
|  | LARGE_INTEGER timeout; | 
|  | HANDLE completion_event; | 
|  |  | 
|  | TRACE("\n"); | 
|  |  | 
|  | while (TRUE) | 
|  | { | 
|  | status = NtWaitForMultipleObjects( 2, handles, FALSE, alertable, | 
|  | get_nt_timeout( &timeout, wait_work_item->Milliseconds ) ); | 
|  | if (status == STATUS_WAIT_0 || status == STATUS_TIMEOUT) | 
|  | { | 
|  | BOOLEAN TimerOrWaitFired; | 
|  |  | 
|  | if (status == STATUS_WAIT_0) | 
|  | { | 
|  | TRACE( "object %p signaled, calling callback %p with context %p\n", | 
|  | wait_work_item->Object, wait_work_item->Callback, | 
|  | wait_work_item->Context ); | 
|  | TimerOrWaitFired = FALSE; | 
|  | } | 
|  | else | 
|  | { | 
|  | TRACE( "wait for object %p timed out, calling callback %p with context %p\n", | 
|  | wait_work_item->Object, wait_work_item->Callback, | 
|  | wait_work_item->Context ); | 
|  | TimerOrWaitFired = TRUE; | 
|  | } | 
|  | wait_work_item->CallbackInProgress = TRUE; | 
|  | wait_work_item->Callback( wait_work_item->Context, TimerOrWaitFired ); | 
|  | wait_work_item->CallbackInProgress = FALSE; | 
|  |  | 
|  | if (wait_work_item->Flags & WT_EXECUTEONLYONCE) | 
|  | break; | 
|  | } | 
|  | else | 
|  | break; | 
|  | } | 
|  |  | 
|  | completion_event = wait_work_item->CompletionEvent; | 
|  | if (completion_event) NtSetEvent( completion_event, NULL ); | 
|  |  | 
|  | if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 ) | 
|  | delete_wait_work_item( wait_work_item ); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlRegisterWait   (NTDLL.@) | 
|  | * | 
|  | * Registers a wait for a handle to become signaled. | 
|  | * | 
|  | * PARAMS | 
|  | *  NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it. | 
|  | *  Object   [I] Object to wait to become signaled. | 
|  | *  Callback [I] Callback function to execute when the wait times out or the handle is signaled. | 
|  | *  Context  [I] Context to pass to the callback function when it is executed. | 
|  | *  Milliseconds [I] Number of milliseconds to wait before timing out. | 
|  | *  Flags    [I] Flags. See notes. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | * | 
|  | * NOTES | 
|  | *  Flags can be one or more of the following: | 
|  | *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread. | 
|  | *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread. | 
|  | *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent. | 
|  | *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time. | 
|  | *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlRegisterWait(PHANDLE NewWaitObject, HANDLE Object, | 
|  | RTL_WAITORTIMERCALLBACKFUNC Callback, | 
|  | PVOID Context, ULONG Milliseconds, ULONG Flags) | 
|  | { | 
|  | struct wait_work_item *wait_work_item; | 
|  | NTSTATUS status; | 
|  |  | 
|  | TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags ); | 
|  |  | 
|  | wait_work_item = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*wait_work_item) ); | 
|  | if (!wait_work_item) | 
|  | return STATUS_NO_MEMORY; | 
|  |  | 
|  | wait_work_item->Object = Object; | 
|  | wait_work_item->Callback = Callback; | 
|  | wait_work_item->Context = Context; | 
|  | wait_work_item->Milliseconds = Milliseconds; | 
|  | wait_work_item->Flags = Flags; | 
|  | wait_work_item->CallbackInProgress = FALSE; | 
|  | wait_work_item->DeleteCount = 0; | 
|  | wait_work_item->CompletionEvent = NULL; | 
|  |  | 
|  | status = NtCreateEvent( &wait_work_item->CancelEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE ); | 
|  | if (status != STATUS_SUCCESS) | 
|  | { | 
|  | RtlFreeHeap( GetProcessHeap(), 0, wait_work_item ); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | Flags = Flags & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD | | 
|  | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION); | 
|  | status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags ); | 
|  | if (status != STATUS_SUCCESS) | 
|  | { | 
|  | delete_wait_work_item( wait_work_item ); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | *NewWaitObject = wait_work_item; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlDeregisterWaitEx   (NTDLL.@) | 
|  | * | 
|  | * Cancels a wait operation and frees the resources associated with calling | 
|  | * RtlRegisterWait(). | 
|  | * | 
|  | * PARAMS | 
|  | *  WaitObject [I] Handle to the wait object to free. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlDeregisterWaitEx(HANDLE WaitHandle, HANDLE CompletionEvent) | 
|  | { | 
|  | struct wait_work_item *wait_work_item = WaitHandle; | 
|  | NTSTATUS status = STATUS_SUCCESS; | 
|  |  | 
|  | TRACE( "(%p)\n", WaitHandle ); | 
|  |  | 
|  | NtSetEvent( wait_work_item->CancelEvent, NULL ); | 
|  | if (wait_work_item->CallbackInProgress) | 
|  | { | 
|  | if (CompletionEvent != NULL) | 
|  | { | 
|  | if (CompletionEvent == INVALID_HANDLE_VALUE) | 
|  | { | 
|  | status = NtCreateEvent( &CompletionEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE ); | 
|  | if (status != STATUS_SUCCESS) | 
|  | return status; | 
|  | interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent ); | 
|  | if (wait_work_item->CallbackInProgress) | 
|  | NtWaitForSingleObject( CompletionEvent, FALSE, NULL ); | 
|  | NtClose( CompletionEvent ); | 
|  | } | 
|  | else | 
|  | { | 
|  | interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent ); | 
|  | if (wait_work_item->CallbackInProgress) | 
|  | status = STATUS_PENDING; | 
|  | } | 
|  | } | 
|  | else | 
|  | status = STATUS_PENDING; | 
|  | } | 
|  |  | 
|  | if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 ) | 
|  | { | 
|  | status = STATUS_SUCCESS; | 
|  | delete_wait_work_item( wait_work_item ); | 
|  | } | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlDeregisterWait   (NTDLL.@) | 
|  | * | 
|  | * Cancels a wait operation and frees the resources associated with calling | 
|  | * RtlRegisterWait(). | 
|  | * | 
|  | * PARAMS | 
|  | *  WaitObject [I] Handle to the wait object to free. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle) | 
|  | { | 
|  | return RtlDeregisterWaitEx(WaitHandle, NULL); | 
|  | } | 
|  |  | 
|  |  | 
|  | /************************** Timer Queue Impl **************************/ | 
|  |  | 
|  | struct timer_queue; | 
|  | struct queue_timer | 
|  | { | 
|  | struct timer_queue *q; | 
|  | struct list entry; | 
|  | ULONG runcount;             /* number of callbacks pending execution */ | 
|  | RTL_WAITORTIMERCALLBACKFUNC callback; | 
|  | PVOID param; | 
|  | DWORD period; | 
|  | ULONG flags; | 
|  | ULONGLONG expire; | 
|  | BOOL destroy;      /* timer should be deleted; once set, never unset */ | 
|  | HANDLE event;      /* removal event */ | 
|  | }; | 
|  |  | 
|  | struct timer_queue | 
|  | { | 
|  | RTL_CRITICAL_SECTION cs; | 
|  | struct list timers;          /* sorted by expiration time */ | 
|  | BOOL quit;         /* queue should be deleted; once set, never unset */ | 
|  | HANDLE event; | 
|  | HANDLE thread; | 
|  | }; | 
|  |  | 
|  | #define EXPIRE_NEVER (~(ULONGLONG) 0) | 
|  |  | 
|  | static void queue_remove_timer(struct queue_timer *t) | 
|  | { | 
|  | /* We MUST hold the queue cs while calling this function.  This ensures | 
|  | that we cannot queue another callback for this timer.  The runcount | 
|  | being zero makes sure we don't have any already queued.  */ | 
|  | struct timer_queue *q = t->q; | 
|  |  | 
|  | assert(t->runcount == 0); | 
|  | assert(t->destroy); | 
|  |  | 
|  | list_remove(&t->entry); | 
|  | if (t->event) | 
|  | NtSetEvent(t->event, NULL); | 
|  | RtlFreeHeap(GetProcessHeap(), 0, t); | 
|  |  | 
|  | if (q->quit && list_count(&q->timers) == 0) | 
|  | NtSetEvent(q->event, NULL); | 
|  | } | 
|  |  | 
|  | static void timer_cleanup_callback(struct queue_timer *t) | 
|  | { | 
|  | struct timer_queue *q = t->q; | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  |  | 
|  | assert(0 < t->runcount); | 
|  | --t->runcount; | 
|  |  | 
|  | if (t->destroy && t->runcount == 0) | 
|  | queue_remove_timer(t); | 
|  |  | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  | } | 
|  |  | 
|  | static DWORD WINAPI timer_callback_wrapper(LPVOID p) | 
|  | { | 
|  | struct queue_timer *t = p; | 
|  | t->callback(t->param, TRUE); | 
|  | timer_cleanup_callback(t); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static inline ULONGLONG queue_current_time(void) | 
|  | { | 
|  | LARGE_INTEGER now; | 
|  | NtQuerySystemTime(&now); | 
|  | return now.QuadPart / 10000; | 
|  | } | 
|  |  | 
|  | static void queue_add_timer(struct queue_timer *t, ULONGLONG time, | 
|  | BOOL set_event) | 
|  | { | 
|  | /* We MUST hold the queue cs while calling this function.  */ | 
|  | struct timer_queue *q = t->q; | 
|  | struct list *ptr = &q->timers; | 
|  |  | 
|  | assert(!q->quit || (t->destroy && time == EXPIRE_NEVER)); | 
|  |  | 
|  | if (time != EXPIRE_NEVER) | 
|  | LIST_FOR_EACH(ptr, &q->timers) | 
|  | { | 
|  | struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry); | 
|  | if (time < cur->expire) | 
|  | break; | 
|  | } | 
|  | list_add_before(ptr, &t->entry); | 
|  |  | 
|  | t->expire = time; | 
|  |  | 
|  | /* If we insert at the head of the list, we need to expire sooner | 
|  | than expected.  */ | 
|  | if (set_event && &t->entry == list_head(&q->timers)) | 
|  | NtSetEvent(q->event, NULL); | 
|  | } | 
|  |  | 
|  | static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time, | 
|  | BOOL set_event) | 
|  | { | 
|  | /* We MUST hold the queue cs while calling this function.  */ | 
|  | list_remove(&t->entry); | 
|  | queue_add_timer(t, time, set_event); | 
|  | } | 
|  |  | 
|  | static void queue_timer_expire(struct timer_queue *q) | 
|  | { | 
|  | struct queue_timer *t = NULL; | 
|  |  | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | if (list_head(&q->timers)) | 
|  | { | 
|  | t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry); | 
|  | if (!t->destroy && t->expire <= queue_current_time()) | 
|  | { | 
|  | ++t->runcount; | 
|  | queue_move_timer( | 
|  | t, t->period ? queue_current_time() + t->period : EXPIRE_NEVER, | 
|  | FALSE); | 
|  | } | 
|  | else | 
|  | t = NULL; | 
|  | } | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  |  | 
|  | if (t) | 
|  | { | 
|  | if (t->flags & WT_EXECUTEINTIMERTHREAD) | 
|  | timer_callback_wrapper(t); | 
|  | else | 
|  | { | 
|  | ULONG flags | 
|  | = (t->flags | 
|  | & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD | 
|  | | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION)); | 
|  | NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags); | 
|  | if (status != STATUS_SUCCESS) | 
|  | timer_cleanup_callback(t); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static ULONG queue_get_timeout(struct timer_queue *q) | 
|  | { | 
|  | struct queue_timer *t; | 
|  | ULONG timeout = INFINITE; | 
|  |  | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | if (list_head(&q->timers)) | 
|  | { | 
|  | t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry); | 
|  | assert(!t->destroy || t->expire == EXPIRE_NEVER); | 
|  |  | 
|  | if (t->expire != EXPIRE_NEVER) | 
|  | { | 
|  | ULONGLONG time = queue_current_time(); | 
|  | timeout = t->expire < time ? 0 : t->expire - time; | 
|  | } | 
|  | } | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  |  | 
|  | return timeout; | 
|  | } | 
|  |  | 
|  | static void WINAPI timer_queue_thread_proc(LPVOID p) | 
|  | { | 
|  | struct timer_queue *q = p; | 
|  | ULONG timeout_ms; | 
|  |  | 
|  | timeout_ms = INFINITE; | 
|  | for (;;) | 
|  | { | 
|  | LARGE_INTEGER timeout; | 
|  | NTSTATUS status; | 
|  | BOOL done = FALSE; | 
|  |  | 
|  | status = NtWaitForSingleObject( | 
|  | q->event, FALSE, get_nt_timeout(&timeout, timeout_ms)); | 
|  |  | 
|  | if (status == STATUS_WAIT_0) | 
|  | { | 
|  | /* There are two possible ways to trigger the event.  Either | 
|  | we are quitting and the last timer got removed, or a new | 
|  | timer got put at the head of the list so we need to adjust | 
|  | our timeout.  */ | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | if (q->quit && list_count(&q->timers) == 0) | 
|  | done = TRUE; | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  | } | 
|  | else if (status == STATUS_TIMEOUT) | 
|  | queue_timer_expire(q); | 
|  |  | 
|  | if (done) | 
|  | break; | 
|  |  | 
|  | timeout_ms = queue_get_timeout(q); | 
|  | } | 
|  |  | 
|  | NtClose(q->event); | 
|  | RtlDeleteCriticalSection(&q->cs); | 
|  | RtlFreeHeap(GetProcessHeap(), 0, q); | 
|  | } | 
|  |  | 
|  | static void queue_destroy_timer(struct queue_timer *t) | 
|  | { | 
|  | /* We MUST hold the queue cs while calling this function.  */ | 
|  | t->destroy = TRUE; | 
|  | if (t->runcount == 0) | 
|  | /* Ensure a timer is promptly removed.  If callbacks are pending, | 
|  | it will be removed after the last one finishes by the callback | 
|  | cleanup wrapper.  */ | 
|  | queue_remove_timer(t); | 
|  | else | 
|  | /* Make sure no destroyed timer masks an active timer at the head | 
|  | of the sorted list.  */ | 
|  | queue_move_timer(t, EXPIRE_NEVER, FALSE); | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlCreateTimerQueue   (NTDLL.@) | 
|  | * | 
|  | * Creates a timer queue object and returns a handle to it. | 
|  | * | 
|  | * PARAMS | 
|  | *  NewTimerQueue [O] The newly created queue. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue) | 
|  | { | 
|  | NTSTATUS status; | 
|  | struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q); | 
|  | if (!q) | 
|  | return STATUS_NO_MEMORY; | 
|  |  | 
|  | RtlInitializeCriticalSection(&q->cs); | 
|  | list_init(&q->timers); | 
|  | q->quit = FALSE; | 
|  | status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE); | 
|  | if (status != STATUS_SUCCESS) | 
|  | { | 
|  | RtlFreeHeap(GetProcessHeap(), 0, q); | 
|  | return status; | 
|  | } | 
|  | status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0, | 
|  | timer_queue_thread_proc, q, &q->thread, NULL); | 
|  | if (status != STATUS_SUCCESS) | 
|  | { | 
|  | NtClose(q->event); | 
|  | RtlFreeHeap(GetProcessHeap(), 0, q); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | *NewTimerQueue = q; | 
|  | return STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlDeleteTimerQueueEx   (NTDLL.@) | 
|  | * | 
|  | * Deletes a timer queue object. | 
|  | * | 
|  | * PARAMS | 
|  | *  TimerQueue      [I] The timer queue to destroy. | 
|  | *  CompletionEvent [I] If NULL, return immediately.  If INVALID_HANDLE_VALUE, | 
|  | *                      wait until all timers are finished firing before | 
|  | *                      returning.  Otherwise, return immediately and set the | 
|  | *                      event when all timers are done. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent) | 
|  | { | 
|  | struct timer_queue *q = TimerQueue; | 
|  | struct queue_timer *t, *temp; | 
|  | HANDLE thread; | 
|  | NTSTATUS status; | 
|  |  | 
|  | if (!q) | 
|  | return STATUS_INVALID_HANDLE; | 
|  |  | 
|  | thread = q->thread; | 
|  |  | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | q->quit = TRUE; | 
|  | if (list_head(&q->timers)) | 
|  | /* When the last timer is removed, it will signal the timer thread to | 
|  | exit...  */ | 
|  | LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry) | 
|  | queue_destroy_timer(t); | 
|  | else | 
|  | /* However if we have none, we must do it ourselves.  */ | 
|  | NtSetEvent(q->event, NULL); | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  |  | 
|  | if (CompletionEvent == INVALID_HANDLE_VALUE) | 
|  | { | 
|  | NtWaitForSingleObject(thread, FALSE, NULL); | 
|  | status = STATUS_SUCCESS; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (CompletionEvent) | 
|  | { | 
|  | FIXME("asynchronous return on completion event unimplemented\n"); | 
|  | NtWaitForSingleObject(thread, FALSE, NULL); | 
|  | NtSetEvent(CompletionEvent, NULL); | 
|  | } | 
|  | status = STATUS_PENDING; | 
|  | } | 
|  |  | 
|  | NtClose(thread); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | static struct timer_queue *default_timer_queue; | 
|  |  | 
|  | static struct timer_queue *get_timer_queue(HANDLE TimerQueue) | 
|  | { | 
|  | if (TimerQueue) | 
|  | return TimerQueue; | 
|  | else | 
|  | { | 
|  | if (!default_timer_queue) | 
|  | { | 
|  | HANDLE q; | 
|  | NTSTATUS status = RtlCreateTimerQueue(&q); | 
|  | if (status == STATUS_SUCCESS) | 
|  | { | 
|  | PVOID p = interlocked_cmpxchg_ptr( | 
|  | (void **) &default_timer_queue, q, NULL); | 
|  | if (p) | 
|  | /* Got beat to the punch.  */ | 
|  | RtlDeleteTimerQueueEx(p, NULL); | 
|  | } | 
|  | } | 
|  | return default_timer_queue; | 
|  | } | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlCreateTimer   (NTDLL.@) | 
|  | * | 
|  | * Creates a new timer associated with the given queue. | 
|  | * | 
|  | * PARAMS | 
|  | *  NewTimer   [O] The newly created timer. | 
|  | *  TimerQueue [I] The queue to hold the timer. | 
|  | *  Callback   [I] The callback to fire. | 
|  | *  Parameter  [I] The argument for the callback. | 
|  | *  DueTime    [I] The delay, in milliseconds, before first firing the | 
|  | *                 timer. | 
|  | *  Period     [I] The period, in milliseconds, at which to fire the timer | 
|  | *                 after the first callback.  If zero, the timer will only | 
|  | *                 fire once.  It still needs to be deleted with | 
|  | *                 RtlDeleteTimer. | 
|  | * Flags       [I] Flags controling the execution of the callback.  In | 
|  | *                 addition to the WT_* thread pool flags (see | 
|  | *                 RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and | 
|  | *                 WT_EXECUTEONLYONCE are supported. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue, | 
|  | RTL_WAITORTIMERCALLBACKFUNC Callback, | 
|  | PVOID Parameter, DWORD DueTime, DWORD Period, | 
|  | ULONG Flags) | 
|  | { | 
|  | NTSTATUS status; | 
|  | struct queue_timer *t; | 
|  | struct timer_queue *q = get_timer_queue(TimerQueue); | 
|  | if (!q) | 
|  | return STATUS_NO_MEMORY; | 
|  |  | 
|  | t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t); | 
|  | if (!t) | 
|  | return STATUS_NO_MEMORY; | 
|  |  | 
|  | t->q = q; | 
|  | t->runcount = 0; | 
|  | t->callback = Callback; | 
|  | t->param = Parameter; | 
|  | t->period = Period; | 
|  | t->flags = Flags; | 
|  | t->destroy = FALSE; | 
|  | t->event = NULL; | 
|  |  | 
|  | status = STATUS_SUCCESS; | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | if (q->quit) | 
|  | status = STATUS_INVALID_HANDLE; | 
|  | else | 
|  | queue_add_timer(t, queue_current_time() + DueTime, TRUE); | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  |  | 
|  | if (status == STATUS_SUCCESS) | 
|  | *NewTimer = t; | 
|  | else | 
|  | RtlFreeHeap(GetProcessHeap(), 0, t); | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlUpdateTimer   (NTDLL.@) | 
|  | * | 
|  | * Changes the time at which a timer expires. | 
|  | * | 
|  | * PARAMS | 
|  | *  TimerQueue [I] The queue that holds the timer. | 
|  | *  Timer      [I] The timer to update. | 
|  | *  DueTime    [I] The delay, in milliseconds, before next firing the timer. | 
|  | *  Period     [I] The period, in milliseconds, at which to fire the timer | 
|  | *                 after the first callback.  If zero, the timer will not | 
|  | *                 refire once.  It still needs to be deleted with | 
|  | *                 RtlDeleteTimer. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer, | 
|  | DWORD DueTime, DWORD Period) | 
|  | { | 
|  | struct queue_timer *t = Timer; | 
|  | struct timer_queue *q = t->q; | 
|  |  | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | /* Can't change a timer if it was once-only or destroyed.  */ | 
|  | if (t->expire != EXPIRE_NEVER) | 
|  | { | 
|  | t->period = Period; | 
|  | queue_move_timer(t, queue_current_time() + DueTime, TRUE); | 
|  | } | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  |  | 
|  | return STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              RtlDeleteTimer   (NTDLL.@) | 
|  | * | 
|  | * Cancels a timer-queue timer. | 
|  | * | 
|  | * PARAMS | 
|  | *  TimerQueue      [I] The queue that holds the timer. | 
|  | *  Timer           [I] The timer to update. | 
|  | *  CompletionEvent [I] If NULL, return immediately.  If INVALID_HANDLE_VALUE, | 
|  | *                      wait until the timer is finished firing all pending | 
|  | *                      callbacks before returning.  Otherwise, return | 
|  | *                      immediately and set the timer is done. | 
|  | * | 
|  | * RETURNS | 
|  | *  Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not, | 
|  | or if the completion event is NULL. | 
|  | *  Failure: Any NTSTATUS code. | 
|  | */ | 
|  | NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer, | 
|  | HANDLE CompletionEvent) | 
|  | { | 
|  | struct queue_timer *t = Timer; | 
|  | struct timer_queue *q; | 
|  | NTSTATUS status = STATUS_PENDING; | 
|  | HANDLE event = NULL; | 
|  |  | 
|  | if (!Timer) | 
|  | return STATUS_INVALID_PARAMETER_1; | 
|  | q = t->q; | 
|  | if (CompletionEvent == INVALID_HANDLE_VALUE) | 
|  | status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE); | 
|  | else if (CompletionEvent) | 
|  | event = CompletionEvent; | 
|  |  | 
|  | RtlEnterCriticalSection(&q->cs); | 
|  | t->event = event; | 
|  | if (t->runcount == 0 && event) | 
|  | status = STATUS_SUCCESS; | 
|  | queue_destroy_timer(t); | 
|  | RtlLeaveCriticalSection(&q->cs); | 
|  |  | 
|  | if (CompletionEvent == INVALID_HANDLE_VALUE && event) | 
|  | { | 
|  | if (status == STATUS_PENDING) | 
|  | NtWaitForSingleObject(event, FALSE, NULL); | 
|  | NtClose(event); | 
|  | } | 
|  |  | 
|  | return status; | 
|  | } |