d3dx9_36/tests: Avoid preprocessor checks to ensure it compiles.
[wine/multimedia.git] / dlls / ntdll / threadpool.c
blob4edd069e40a3332a769d5570f43ad96ee45bb380
1 /*
2 * Thread pooling
4 * Copyright (c) 2006 Robert Shearman
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include "config.h"
22 #include "wine/port.h"
24 #include <assert.h>
25 #include <stdarg.h>
26 #include <limits.h>
28 #define NONAMELESSUNION
29 #include "ntstatus.h"
30 #define WIN32_NO_STATUS
31 #include "winternl.h"
33 #include "wine/debug.h"
34 #include "wine/list.h"
36 #include "ntdll_misc.h"
38 WINE_DEFAULT_DEBUG_CHANNEL(threadpool);
40 #define WORKER_TIMEOUT 30000 /* 30 seconds */
42 static LONG num_workers;
43 static LONG num_work_items;
44 static LONG num_busy_workers;
46 static struct list work_item_list = LIST_INIT(work_item_list);
47 static HANDLE work_item_event;
49 static RTL_CRITICAL_SECTION threadpool_cs;
50 static RTL_CRITICAL_SECTION_DEBUG critsect_debug =
52 0, 0, &threadpool_cs,
53 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
54 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_cs") }
56 static RTL_CRITICAL_SECTION threadpool_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
58 static HANDLE compl_port = NULL;
59 static RTL_CRITICAL_SECTION threadpool_compl_cs;
60 static RTL_CRITICAL_SECTION_DEBUG critsect_compl_debug =
62 0, 0, &threadpool_compl_cs,
63 { &critsect_compl_debug.ProcessLocksList, &critsect_compl_debug.ProcessLocksList },
64 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_compl_cs") }
66 static RTL_CRITICAL_SECTION threadpool_compl_cs = { &critsect_compl_debug, -1, 0, 0, 0, 0 };
68 struct work_item
70 struct list entry;
71 PRTL_WORK_ITEM_ROUTINE function;
72 PVOID context;
75 static inline LONG interlocked_inc( PLONG dest )
77 return interlocked_xchg_add( dest, 1 ) + 1;
80 static inline LONG interlocked_dec( PLONG dest )
82 return interlocked_xchg_add( dest, -1 ) - 1;
85 static void WINAPI worker_thread_proc(void * param)
87 interlocked_inc(&num_workers);
89 /* free the work item memory sooner to reduce memory usage */
90 while (TRUE)
92 if (num_work_items > 0)
94 struct list *item;
95 RtlEnterCriticalSection(&threadpool_cs);
96 item = list_head(&work_item_list);
97 if (item)
99 struct work_item *work_item_ptr = LIST_ENTRY(item, struct work_item, entry);
100 struct work_item work_item;
101 list_remove(&work_item_ptr->entry);
102 interlocked_dec(&num_work_items);
104 RtlLeaveCriticalSection(&threadpool_cs);
106 work_item = *work_item_ptr;
107 RtlFreeHeap(GetProcessHeap(), 0, work_item_ptr);
109 TRACE("executing %p(%p)\n", work_item.function, work_item.context);
111 interlocked_inc(&num_busy_workers);
113 /* do the work */
114 work_item.function(work_item.context);
116 interlocked_dec(&num_busy_workers);
118 else
119 RtlLeaveCriticalSection(&threadpool_cs);
121 else
123 NTSTATUS status;
124 LARGE_INTEGER timeout;
125 timeout.QuadPart = -(WORKER_TIMEOUT * (ULONGLONG)10000);
126 status = NtWaitForSingleObject(work_item_event, FALSE, &timeout);
127 if (status != STATUS_WAIT_0)
128 break;
132 interlocked_dec(&num_workers);
134 RtlExitUserThread(0);
136 /* never reached */
139 static NTSTATUS add_work_item_to_queue(struct work_item *work_item)
141 NTSTATUS status;
143 RtlEnterCriticalSection(&threadpool_cs);
144 list_add_tail(&work_item_list, &work_item->entry);
145 num_work_items++;
146 RtlLeaveCriticalSection(&threadpool_cs);
148 if (!work_item_event)
150 HANDLE sem;
151 status = NtCreateSemaphore(&sem, SEMAPHORE_ALL_ACCESS, NULL, 1, INT_MAX);
152 if (interlocked_cmpxchg_ptr( &work_item_event, sem, 0 ))
153 NtClose(sem); /* somebody beat us to it */
155 else
156 status = NtReleaseSemaphore(work_item_event, 1, NULL);
158 return status;
161 /***********************************************************************
162 * RtlQueueWorkItem (NTDLL.@)
164 * Queues a work item into a thread in the thread pool.
166 * PARAMS
167 * Function [I] Work function to execute.
168 * Context [I] Context to pass to the work function when it is executed.
169 * Flags [I] Flags. See notes.
171 * RETURNS
172 * Success: STATUS_SUCCESS.
173 * Failure: Any NTSTATUS code.
175 * NOTES
176 * Flags can be one or more of the following:
177 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
178 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
179 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
180 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
181 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
183 NTSTATUS WINAPI RtlQueueWorkItem(PRTL_WORK_ITEM_ROUTINE Function, PVOID Context, ULONG Flags)
185 HANDLE thread;
186 NTSTATUS status;
187 struct work_item *work_item = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(struct work_item));
189 if (!work_item)
190 return STATUS_NO_MEMORY;
192 work_item->function = Function;
193 work_item->context = Context;
195 if (Flags & ~WT_EXECUTELONGFUNCTION)
196 FIXME("Flags 0x%x not supported\n", Flags);
198 status = add_work_item_to_queue(work_item);
200 /* FIXME: tune this algorithm to not be as aggressive with creating threads
201 * if WT_EXECUTELONGFUNCTION isn't specified */
202 if ((status == STATUS_SUCCESS) &&
203 ((num_workers == 0) || (num_workers == num_busy_workers)))
205 status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE,
206 NULL, 0, 0,
207 worker_thread_proc, NULL, &thread, NULL );
208 if (status == STATUS_SUCCESS)
209 NtClose( thread );
211 /* NOTE: we don't care if we couldn't create the thread if there is at
212 * least one other available to process the request */
213 if ((num_workers > 0) && (status != STATUS_SUCCESS))
214 status = STATUS_SUCCESS;
217 if (status != STATUS_SUCCESS)
219 RtlEnterCriticalSection(&threadpool_cs);
221 interlocked_dec(&num_work_items);
222 list_remove(&work_item->entry);
223 RtlFreeHeap(GetProcessHeap(), 0, work_item);
225 RtlLeaveCriticalSection(&threadpool_cs);
227 return status;
230 return STATUS_SUCCESS;
233 /***********************************************************************
234 * iocp_poller - get completion events and run callbacks
236 static DWORD CALLBACK iocp_poller(LPVOID Arg)
238 while( TRUE )
240 PRTL_OVERLAPPED_COMPLETION_ROUTINE callback;
241 LPVOID overlapped;
242 IO_STATUS_BLOCK iosb;
243 NTSTATUS res = NtRemoveIoCompletion( compl_port, (PULONG_PTR)&callback, (PULONG_PTR)&overlapped, &iosb, NULL );
244 if (res)
246 ERR("NtRemoveIoCompletion failed: 0x%x\n", res);
248 else
250 DWORD transferred = 0;
251 DWORD err = 0;
253 if (iosb.u.Status == STATUS_SUCCESS)
254 transferred = iosb.Information;
255 else
256 err = RtlNtStatusToDosError(iosb.u.Status);
258 callback( err, transferred, overlapped );
261 return 0;
264 /***********************************************************************
265 * RtlSetIoCompletionCallback (NTDLL.@)
267 * Binds a handle to a thread pool's completion port, and possibly
268 * starts a non-I/O thread to monitor this port and call functions back.
270 * PARAMS
271 * FileHandle [I] Handle to bind to a completion port.
272 * Function [I] Callback function to call on I/O completions.
273 * Flags [I] Not used.
275 * RETURNS
276 * Success: STATUS_SUCCESS.
277 * Failure: Any NTSTATUS code.
280 NTSTATUS WINAPI RtlSetIoCompletionCallback(HANDLE FileHandle, PRTL_OVERLAPPED_COMPLETION_ROUTINE Function, ULONG Flags)
282 IO_STATUS_BLOCK iosb;
283 FILE_COMPLETION_INFORMATION info;
285 if (Flags) FIXME("Unknown value Flags=0x%x\n", Flags);
287 if (!compl_port)
289 NTSTATUS res = STATUS_SUCCESS;
291 RtlEnterCriticalSection(&threadpool_compl_cs);
292 if (!compl_port)
294 HANDLE cport;
296 res = NtCreateIoCompletion( &cport, IO_COMPLETION_ALL_ACCESS, NULL, 0 );
297 if (!res)
299 /* FIXME native can start additional threads in case of e.g. hung callback function. */
300 res = RtlQueueWorkItem( iocp_poller, NULL, WT_EXECUTEDEFAULT );
301 if (!res)
302 compl_port = cport;
303 else
304 NtClose( cport );
307 RtlLeaveCriticalSection(&threadpool_compl_cs);
308 if (res) return res;
311 info.CompletionPort = compl_port;
312 info.CompletionKey = (ULONG_PTR)Function;
314 return NtSetInformationFile( FileHandle, &iosb, &info, sizeof(info), FileCompletionInformation );
317 static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
319 if (timeout == INFINITE) return NULL;
320 pTime->QuadPart = (ULONGLONG)timeout * -10000;
321 return pTime;
324 struct wait_work_item
326 HANDLE Object;
327 HANDLE CancelEvent;
328 WAITORTIMERCALLBACK Callback;
329 PVOID Context;
330 ULONG Milliseconds;
331 ULONG Flags;
332 HANDLE CompletionEvent;
333 LONG DeleteCount;
334 BOOLEAN CallbackInProgress;
337 static void delete_wait_work_item(struct wait_work_item *wait_work_item)
339 NtClose( wait_work_item->CancelEvent );
340 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
343 static DWORD CALLBACK wait_thread_proc(LPVOID Arg)
345 struct wait_work_item *wait_work_item = Arg;
346 NTSTATUS status;
347 BOOLEAN alertable = (wait_work_item->Flags & WT_EXECUTEINIOTHREAD) != 0;
348 HANDLE handles[2] = { wait_work_item->Object, wait_work_item->CancelEvent };
349 LARGE_INTEGER timeout;
350 HANDLE completion_event;
352 TRACE("\n");
354 while (TRUE)
356 status = NtWaitForMultipleObjects( 2, handles, FALSE, alertable,
357 get_nt_timeout( &timeout, wait_work_item->Milliseconds ) );
358 if (status == STATUS_WAIT_0 || status == STATUS_TIMEOUT)
360 BOOLEAN TimerOrWaitFired;
362 if (status == STATUS_WAIT_0)
364 TRACE( "object %p signaled, calling callback %p with context %p\n",
365 wait_work_item->Object, wait_work_item->Callback,
366 wait_work_item->Context );
367 TimerOrWaitFired = FALSE;
369 else
371 TRACE( "wait for object %p timed out, calling callback %p with context %p\n",
372 wait_work_item->Object, wait_work_item->Callback,
373 wait_work_item->Context );
374 TimerOrWaitFired = TRUE;
376 wait_work_item->CallbackInProgress = TRUE;
377 wait_work_item->Callback( wait_work_item->Context, TimerOrWaitFired );
378 wait_work_item->CallbackInProgress = FALSE;
380 if (wait_work_item->Flags & WT_EXECUTEONLYONCE)
381 break;
383 else
384 break;
387 completion_event = wait_work_item->CompletionEvent;
388 if (completion_event) NtSetEvent( completion_event, NULL );
390 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
391 delete_wait_work_item( wait_work_item );
393 return 0;
396 /***********************************************************************
397 * RtlRegisterWait (NTDLL.@)
399 * Registers a wait for a handle to become signaled.
401 * PARAMS
402 * NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it.
403 * Object [I] Object to wait to become signaled.
404 * Callback [I] Callback function to execute when the wait times out or the handle is signaled.
405 * Context [I] Context to pass to the callback function when it is executed.
406 * Milliseconds [I] Number of milliseconds to wait before timing out.
407 * Flags [I] Flags. See notes.
409 * RETURNS
410 * Success: STATUS_SUCCESS.
411 * Failure: Any NTSTATUS code.
413 * NOTES
414 * Flags can be one or more of the following:
415 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
416 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
417 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
418 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
419 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
421 NTSTATUS WINAPI RtlRegisterWait(PHANDLE NewWaitObject, HANDLE Object,
422 RTL_WAITORTIMERCALLBACKFUNC Callback,
423 PVOID Context, ULONG Milliseconds, ULONG Flags)
425 struct wait_work_item *wait_work_item;
426 NTSTATUS status;
428 TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags );
430 wait_work_item = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*wait_work_item) );
431 if (!wait_work_item)
432 return STATUS_NO_MEMORY;
434 wait_work_item->Object = Object;
435 wait_work_item->Callback = Callback;
436 wait_work_item->Context = Context;
437 wait_work_item->Milliseconds = Milliseconds;
438 wait_work_item->Flags = Flags;
439 wait_work_item->CallbackInProgress = FALSE;
440 wait_work_item->DeleteCount = 0;
441 wait_work_item->CompletionEvent = NULL;
443 status = NtCreateEvent( &wait_work_item->CancelEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE );
444 if (status != STATUS_SUCCESS)
446 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
447 return status;
450 Flags = Flags & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD |
451 WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION);
452 status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags );
453 if (status != STATUS_SUCCESS)
455 delete_wait_work_item( wait_work_item );
456 return status;
459 *NewWaitObject = wait_work_item;
460 return status;
463 /***********************************************************************
464 * RtlDeregisterWaitEx (NTDLL.@)
466 * Cancels a wait operation and frees the resources associated with calling
467 * RtlRegisterWait().
469 * PARAMS
470 * WaitObject [I] Handle to the wait object to free.
472 * RETURNS
473 * Success: STATUS_SUCCESS.
474 * Failure: Any NTSTATUS code.
476 NTSTATUS WINAPI RtlDeregisterWaitEx(HANDLE WaitHandle, HANDLE CompletionEvent)
478 struct wait_work_item *wait_work_item = WaitHandle;
479 NTSTATUS status = STATUS_SUCCESS;
481 TRACE( "(%p)\n", WaitHandle );
483 NtSetEvent( wait_work_item->CancelEvent, NULL );
484 if (wait_work_item->CallbackInProgress)
486 if (CompletionEvent != NULL)
488 if (CompletionEvent == INVALID_HANDLE_VALUE)
490 status = NtCreateEvent( &CompletionEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE );
491 if (status != STATUS_SUCCESS)
492 return status;
493 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
494 if (wait_work_item->CallbackInProgress)
495 NtWaitForSingleObject( CompletionEvent, FALSE, NULL );
496 NtClose( CompletionEvent );
498 else
500 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
501 if (wait_work_item->CallbackInProgress)
502 status = STATUS_PENDING;
505 else
506 status = STATUS_PENDING;
509 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
511 status = STATUS_SUCCESS;
512 delete_wait_work_item( wait_work_item );
515 return status;
518 /***********************************************************************
519 * RtlDeregisterWait (NTDLL.@)
521 * Cancels a wait operation and frees the resources associated with calling
522 * RtlRegisterWait().
524 * PARAMS
525 * WaitObject [I] Handle to the wait object to free.
527 * RETURNS
528 * Success: STATUS_SUCCESS.
529 * Failure: Any NTSTATUS code.
531 NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle)
533 return RtlDeregisterWaitEx(WaitHandle, NULL);
537 /************************** Timer Queue Impl **************************/
539 struct timer_queue;
540 struct queue_timer
542 struct timer_queue *q;
543 struct list entry;
544 ULONG runcount; /* number of callbacks pending execution */
545 RTL_WAITORTIMERCALLBACKFUNC callback;
546 PVOID param;
547 DWORD period;
548 ULONG flags;
549 ULONGLONG expire;
550 BOOL destroy; /* timer should be deleted; once set, never unset */
551 HANDLE event; /* removal event */
554 struct timer_queue
556 DWORD magic;
557 RTL_CRITICAL_SECTION cs;
558 struct list timers; /* sorted by expiration time */
559 BOOL quit; /* queue should be deleted; once set, never unset */
560 HANDLE event;
561 HANDLE thread;
564 #define EXPIRE_NEVER (~(ULONGLONG) 0)
565 #define TIMER_QUEUE_MAGIC 0x516d6954 /* TimQ */
567 static void queue_remove_timer(struct queue_timer *t)
569 /* We MUST hold the queue cs while calling this function. This ensures
570 that we cannot queue another callback for this timer. The runcount
571 being zero makes sure we don't have any already queued. */
572 struct timer_queue *q = t->q;
574 assert(t->runcount == 0);
575 assert(t->destroy);
577 list_remove(&t->entry);
578 if (t->event)
579 NtSetEvent(t->event, NULL);
580 RtlFreeHeap(GetProcessHeap(), 0, t);
582 if (q->quit && list_empty(&q->timers))
583 NtSetEvent(q->event, NULL);
586 static void timer_cleanup_callback(struct queue_timer *t)
588 struct timer_queue *q = t->q;
589 RtlEnterCriticalSection(&q->cs);
591 assert(0 < t->runcount);
592 --t->runcount;
594 if (t->destroy && t->runcount == 0)
595 queue_remove_timer(t);
597 RtlLeaveCriticalSection(&q->cs);
600 static DWORD WINAPI timer_callback_wrapper(LPVOID p)
602 struct queue_timer *t = p;
603 t->callback(t->param, TRUE);
604 timer_cleanup_callback(t);
605 return 0;
608 static inline ULONGLONG queue_current_time(void)
610 LARGE_INTEGER now, freq;
611 NtQueryPerformanceCounter(&now, &freq);
612 return now.QuadPart * 1000 / freq.QuadPart;
615 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
616 BOOL set_event)
618 /* We MUST hold the queue cs while calling this function. */
619 struct timer_queue *q = t->q;
620 struct list *ptr = &q->timers;
622 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
624 if (time != EXPIRE_NEVER)
625 LIST_FOR_EACH(ptr, &q->timers)
627 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
628 if (time < cur->expire)
629 break;
631 list_add_before(ptr, &t->entry);
633 t->expire = time;
635 /* If we insert at the head of the list, we need to expire sooner
636 than expected. */
637 if (set_event && &t->entry == list_head(&q->timers))
638 NtSetEvent(q->event, NULL);
641 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
642 BOOL set_event)
644 /* We MUST hold the queue cs while calling this function. */
645 list_remove(&t->entry);
646 queue_add_timer(t, time, set_event);
649 static void queue_timer_expire(struct timer_queue *q)
651 struct queue_timer *t = NULL;
653 RtlEnterCriticalSection(&q->cs);
654 if (list_head(&q->timers))
656 ULONGLONG now, next;
657 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
658 if (!t->destroy && t->expire <= ((now = queue_current_time())))
660 ++t->runcount;
661 if (t->period)
663 next = t->expire + t->period;
664 /* avoid trigger cascade if overloaded / hibernated */
665 if (next < now)
666 next = now + t->period;
668 else
669 next = EXPIRE_NEVER;
670 queue_move_timer(t, next, FALSE);
672 else
673 t = NULL;
675 RtlLeaveCriticalSection(&q->cs);
677 if (t)
679 if (t->flags & WT_EXECUTEINTIMERTHREAD)
680 timer_callback_wrapper(t);
681 else
683 ULONG flags
684 = (t->flags
685 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
686 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
687 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
688 if (status != STATUS_SUCCESS)
689 timer_cleanup_callback(t);
694 static ULONG queue_get_timeout(struct timer_queue *q)
696 struct queue_timer *t;
697 ULONG timeout = INFINITE;
699 RtlEnterCriticalSection(&q->cs);
700 if (list_head(&q->timers))
702 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
703 assert(!t->destroy || t->expire == EXPIRE_NEVER);
705 if (t->expire != EXPIRE_NEVER)
707 ULONGLONG time = queue_current_time();
708 timeout = t->expire < time ? 0 : t->expire - time;
711 RtlLeaveCriticalSection(&q->cs);
713 return timeout;
716 static void WINAPI timer_queue_thread_proc(LPVOID p)
718 struct timer_queue *q = p;
719 ULONG timeout_ms;
721 timeout_ms = INFINITE;
722 for (;;)
724 LARGE_INTEGER timeout;
725 NTSTATUS status;
726 BOOL done = FALSE;
728 status = NtWaitForSingleObject(
729 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
731 if (status == STATUS_WAIT_0)
733 /* There are two possible ways to trigger the event. Either
734 we are quitting and the last timer got removed, or a new
735 timer got put at the head of the list so we need to adjust
736 our timeout. */
737 RtlEnterCriticalSection(&q->cs);
738 if (q->quit && list_empty(&q->timers))
739 done = TRUE;
740 RtlLeaveCriticalSection(&q->cs);
742 else if (status == STATUS_TIMEOUT)
743 queue_timer_expire(q);
745 if (done)
746 break;
748 timeout_ms = queue_get_timeout(q);
751 NtClose(q->event);
752 RtlDeleteCriticalSection(&q->cs);
753 q->magic = 0;
754 RtlFreeHeap(GetProcessHeap(), 0, q);
757 static void queue_destroy_timer(struct queue_timer *t)
759 /* We MUST hold the queue cs while calling this function. */
760 t->destroy = TRUE;
761 if (t->runcount == 0)
762 /* Ensure a timer is promptly removed. If callbacks are pending,
763 it will be removed after the last one finishes by the callback
764 cleanup wrapper. */
765 queue_remove_timer(t);
766 else
767 /* Make sure no destroyed timer masks an active timer at the head
768 of the sorted list. */
769 queue_move_timer(t, EXPIRE_NEVER, FALSE);
772 /***********************************************************************
773 * RtlCreateTimerQueue (NTDLL.@)
775 * Creates a timer queue object and returns a handle to it.
777 * PARAMS
778 * NewTimerQueue [O] The newly created queue.
780 * RETURNS
781 * Success: STATUS_SUCCESS.
782 * Failure: Any NTSTATUS code.
784 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
786 NTSTATUS status;
787 struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
788 if (!q)
789 return STATUS_NO_MEMORY;
791 RtlInitializeCriticalSection(&q->cs);
792 list_init(&q->timers);
793 q->quit = FALSE;
794 q->magic = TIMER_QUEUE_MAGIC;
795 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
796 if (status != STATUS_SUCCESS)
798 RtlFreeHeap(GetProcessHeap(), 0, q);
799 return status;
801 status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
802 timer_queue_thread_proc, q, &q->thread, NULL);
803 if (status != STATUS_SUCCESS)
805 NtClose(q->event);
806 RtlFreeHeap(GetProcessHeap(), 0, q);
807 return status;
810 *NewTimerQueue = q;
811 return STATUS_SUCCESS;
814 /***********************************************************************
815 * RtlDeleteTimerQueueEx (NTDLL.@)
817 * Deletes a timer queue object.
819 * PARAMS
820 * TimerQueue [I] The timer queue to destroy.
821 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
822 * wait until all timers are finished firing before
823 * returning. Otherwise, return immediately and set the
824 * event when all timers are done.
826 * RETURNS
827 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
828 * Failure: Any NTSTATUS code.
830 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
832 struct timer_queue *q = TimerQueue;
833 struct queue_timer *t, *temp;
834 HANDLE thread;
835 NTSTATUS status;
837 if (!q || q->magic != TIMER_QUEUE_MAGIC)
838 return STATUS_INVALID_HANDLE;
840 thread = q->thread;
842 RtlEnterCriticalSection(&q->cs);
843 q->quit = TRUE;
844 if (list_head(&q->timers))
845 /* When the last timer is removed, it will signal the timer thread to
846 exit... */
847 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
848 queue_destroy_timer(t);
849 else
850 /* However if we have none, we must do it ourselves. */
851 NtSetEvent(q->event, NULL);
852 RtlLeaveCriticalSection(&q->cs);
854 if (CompletionEvent == INVALID_HANDLE_VALUE)
856 NtWaitForSingleObject(thread, FALSE, NULL);
857 status = STATUS_SUCCESS;
859 else
861 if (CompletionEvent)
863 FIXME("asynchronous return on completion event unimplemented\n");
864 NtWaitForSingleObject(thread, FALSE, NULL);
865 NtSetEvent(CompletionEvent, NULL);
867 status = STATUS_PENDING;
870 NtClose(thread);
871 return status;
874 static struct timer_queue *default_timer_queue;
876 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
878 if (TimerQueue)
879 return TimerQueue;
880 else
882 if (!default_timer_queue)
884 HANDLE q;
885 NTSTATUS status = RtlCreateTimerQueue(&q);
886 if (status == STATUS_SUCCESS)
888 PVOID p = interlocked_cmpxchg_ptr(
889 (void **) &default_timer_queue, q, NULL);
890 if (p)
891 /* Got beat to the punch. */
892 RtlDeleteTimerQueueEx(p, NULL);
895 return default_timer_queue;
899 /***********************************************************************
900 * RtlCreateTimer (NTDLL.@)
902 * Creates a new timer associated with the given queue.
904 * PARAMS
905 * NewTimer [O] The newly created timer.
906 * TimerQueue [I] The queue to hold the timer.
907 * Callback [I] The callback to fire.
908 * Parameter [I] The argument for the callback.
909 * DueTime [I] The delay, in milliseconds, before first firing the
910 * timer.
911 * Period [I] The period, in milliseconds, at which to fire the timer
912 * after the first callback. If zero, the timer will only
913 * fire once. It still needs to be deleted with
914 * RtlDeleteTimer.
915 * Flags [I] Flags controlling the execution of the callback. In
916 * addition to the WT_* thread pool flags (see
917 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
918 * WT_EXECUTEONLYONCE are supported.
920 * RETURNS
921 * Success: STATUS_SUCCESS.
922 * Failure: Any NTSTATUS code.
924 NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
925 RTL_WAITORTIMERCALLBACKFUNC Callback,
926 PVOID Parameter, DWORD DueTime, DWORD Period,
927 ULONG Flags)
929 NTSTATUS status;
930 struct queue_timer *t;
931 struct timer_queue *q = get_timer_queue(TimerQueue);
933 if (!q) return STATUS_NO_MEMORY;
934 if (q->magic != TIMER_QUEUE_MAGIC) return STATUS_INVALID_HANDLE;
936 t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t);
937 if (!t)
938 return STATUS_NO_MEMORY;
940 t->q = q;
941 t->runcount = 0;
942 t->callback = Callback;
943 t->param = Parameter;
944 t->period = Period;
945 t->flags = Flags;
946 t->destroy = FALSE;
947 t->event = NULL;
949 status = STATUS_SUCCESS;
950 RtlEnterCriticalSection(&q->cs);
951 if (q->quit)
952 status = STATUS_INVALID_HANDLE;
953 else
954 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
955 RtlLeaveCriticalSection(&q->cs);
957 if (status == STATUS_SUCCESS)
958 *NewTimer = t;
959 else
960 RtlFreeHeap(GetProcessHeap(), 0, t);
962 return status;
965 /***********************************************************************
966 * RtlUpdateTimer (NTDLL.@)
968 * Changes the time at which a timer expires.
970 * PARAMS
971 * TimerQueue [I] The queue that holds the timer.
972 * Timer [I] The timer to update.
973 * DueTime [I] The delay, in milliseconds, before next firing the timer.
974 * Period [I] The period, in milliseconds, at which to fire the timer
975 * after the first callback. If zero, the timer will not
976 * refire once. It still needs to be deleted with
977 * RtlDeleteTimer.
979 * RETURNS
980 * Success: STATUS_SUCCESS.
981 * Failure: Any NTSTATUS code.
983 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
984 DWORD DueTime, DWORD Period)
986 struct queue_timer *t = Timer;
987 struct timer_queue *q = t->q;
989 RtlEnterCriticalSection(&q->cs);
990 /* Can't change a timer if it was once-only or destroyed. */
991 if (t->expire != EXPIRE_NEVER)
993 t->period = Period;
994 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
996 RtlLeaveCriticalSection(&q->cs);
998 return STATUS_SUCCESS;
1001 /***********************************************************************
1002 * RtlDeleteTimer (NTDLL.@)
1004 * Cancels a timer-queue timer.
1006 * PARAMS
1007 * TimerQueue [I] The queue that holds the timer.
1008 * Timer [I] The timer to update.
1009 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
1010 * wait until the timer is finished firing all pending
1011 * callbacks before returning. Otherwise, return
1012 * immediately and set the timer is done.
1014 * RETURNS
1015 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
1016 or if the completion event is NULL.
1017 * Failure: Any NTSTATUS code.
1019 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
1020 HANDLE CompletionEvent)
1022 struct queue_timer *t = Timer;
1023 struct timer_queue *q;
1024 NTSTATUS status = STATUS_PENDING;
1025 HANDLE event = NULL;
1027 if (!Timer)
1028 return STATUS_INVALID_PARAMETER_1;
1029 q = t->q;
1030 if (CompletionEvent == INVALID_HANDLE_VALUE)
1032 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
1033 if (status == STATUS_SUCCESS)
1034 status = STATUS_PENDING;
1036 else if (CompletionEvent)
1037 event = CompletionEvent;
1039 RtlEnterCriticalSection(&q->cs);
1040 t->event = event;
1041 if (t->runcount == 0 && event)
1042 status = STATUS_SUCCESS;
1043 queue_destroy_timer(t);
1044 RtlLeaveCriticalSection(&q->cs);
1046 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
1048 if (status == STATUS_PENDING)
1050 NtWaitForSingleObject(event, FALSE, NULL);
1051 status = STATUS_SUCCESS;
1053 NtClose(event);
1056 return status;