| /* | |
| * 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; | |
| } | |
| status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags & ~WT_EXECUTEONLYONCE ); | |
| 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; | |
| } |