hhctrl.ocx: Add a missing Release call.
[wine/multimedia.git] / dlls / ntdll / threadpool.c
blob547e6eb83efc18d6a22a2e95c2b939a05d84a06f
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) ? TRUE : FALSE;
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 RTL_CRITICAL_SECTION cs;
557 struct list timers; /* sorted by expiration time */
558 BOOL quit; /* queue should be deleted; once set, never unset */
559 HANDLE event;
560 HANDLE thread;
563 #define EXPIRE_NEVER (~(ULONGLONG) 0)
565 static void queue_remove_timer(struct queue_timer *t)
567 /* We MUST hold the queue cs while calling this function. This ensures
568 that we cannot queue another callback for this timer. The runcount
569 being zero makes sure we don't have any already queued. */
570 struct timer_queue *q = t->q;
572 assert(t->runcount == 0);
573 assert(t->destroy);
575 list_remove(&t->entry);
576 if (t->event)
577 NtSetEvent(t->event, NULL);
578 RtlFreeHeap(GetProcessHeap(), 0, t);
580 if (q->quit && list_empty(&q->timers))
581 NtSetEvent(q->event, NULL);
584 static void timer_cleanup_callback(struct queue_timer *t)
586 struct timer_queue *q = t->q;
587 RtlEnterCriticalSection(&q->cs);
589 assert(0 < t->runcount);
590 --t->runcount;
592 if (t->destroy && t->runcount == 0)
593 queue_remove_timer(t);
595 RtlLeaveCriticalSection(&q->cs);
598 static DWORD WINAPI timer_callback_wrapper(LPVOID p)
600 struct queue_timer *t = p;
601 t->callback(t->param, TRUE);
602 timer_cleanup_callback(t);
603 return 0;
606 static inline ULONGLONG queue_current_time(void)
608 LARGE_INTEGER now;
609 NtQuerySystemTime(&now);
610 return now.QuadPart / 10000;
613 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
614 BOOL set_event)
616 /* We MUST hold the queue cs while calling this function. */
617 struct timer_queue *q = t->q;
618 struct list *ptr = &q->timers;
620 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
622 if (time != EXPIRE_NEVER)
623 LIST_FOR_EACH(ptr, &q->timers)
625 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
626 if (time < cur->expire)
627 break;
629 list_add_before(ptr, &t->entry);
631 t->expire = time;
633 /* If we insert at the head of the list, we need to expire sooner
634 than expected. */
635 if (set_event && &t->entry == list_head(&q->timers))
636 NtSetEvent(q->event, NULL);
639 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
640 BOOL set_event)
642 /* We MUST hold the queue cs while calling this function. */
643 list_remove(&t->entry);
644 queue_add_timer(t, time, set_event);
647 static void queue_timer_expire(struct timer_queue *q)
649 struct queue_timer *t = NULL;
651 RtlEnterCriticalSection(&q->cs);
652 if (list_head(&q->timers))
654 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
655 if (!t->destroy && t->expire <= queue_current_time())
657 ++t->runcount;
658 queue_move_timer(
659 t, t->period ? queue_current_time() + t->period : EXPIRE_NEVER,
660 FALSE);
662 else
663 t = NULL;
665 RtlLeaveCriticalSection(&q->cs);
667 if (t)
669 if (t->flags & WT_EXECUTEINTIMERTHREAD)
670 timer_callback_wrapper(t);
671 else
673 ULONG flags
674 = (t->flags
675 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
676 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
677 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
678 if (status != STATUS_SUCCESS)
679 timer_cleanup_callback(t);
684 static ULONG queue_get_timeout(struct timer_queue *q)
686 struct queue_timer *t;
687 ULONG timeout = INFINITE;
689 RtlEnterCriticalSection(&q->cs);
690 if (list_head(&q->timers))
692 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
693 assert(!t->destroy || t->expire == EXPIRE_NEVER);
695 if (t->expire != EXPIRE_NEVER)
697 ULONGLONG time = queue_current_time();
698 timeout = t->expire < time ? 0 : t->expire - time;
701 RtlLeaveCriticalSection(&q->cs);
703 return timeout;
706 static void WINAPI timer_queue_thread_proc(LPVOID p)
708 struct timer_queue *q = p;
709 ULONG timeout_ms;
711 timeout_ms = INFINITE;
712 for (;;)
714 LARGE_INTEGER timeout;
715 NTSTATUS status;
716 BOOL done = FALSE;
718 status = NtWaitForSingleObject(
719 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
721 if (status == STATUS_WAIT_0)
723 /* There are two possible ways to trigger the event. Either
724 we are quitting and the last timer got removed, or a new
725 timer got put at the head of the list so we need to adjust
726 our timeout. */
727 RtlEnterCriticalSection(&q->cs);
728 if (q->quit && list_empty(&q->timers))
729 done = TRUE;
730 RtlLeaveCriticalSection(&q->cs);
732 else if (status == STATUS_TIMEOUT)
733 queue_timer_expire(q);
735 if (done)
736 break;
738 timeout_ms = queue_get_timeout(q);
741 NtClose(q->event);
742 RtlDeleteCriticalSection(&q->cs);
743 RtlFreeHeap(GetProcessHeap(), 0, q);
746 static void queue_destroy_timer(struct queue_timer *t)
748 /* We MUST hold the queue cs while calling this function. */
749 t->destroy = TRUE;
750 if (t->runcount == 0)
751 /* Ensure a timer is promptly removed. If callbacks are pending,
752 it will be removed after the last one finishes by the callback
753 cleanup wrapper. */
754 queue_remove_timer(t);
755 else
756 /* Make sure no destroyed timer masks an active timer at the head
757 of the sorted list. */
758 queue_move_timer(t, EXPIRE_NEVER, FALSE);
761 /***********************************************************************
762 * RtlCreateTimerQueue (NTDLL.@)
764 * Creates a timer queue object and returns a handle to it.
766 * PARAMS
767 * NewTimerQueue [O] The newly created queue.
769 * RETURNS
770 * Success: STATUS_SUCCESS.
771 * Failure: Any NTSTATUS code.
773 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
775 NTSTATUS status;
776 struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
777 if (!q)
778 return STATUS_NO_MEMORY;
780 RtlInitializeCriticalSection(&q->cs);
781 list_init(&q->timers);
782 q->quit = FALSE;
783 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
784 if (status != STATUS_SUCCESS)
786 RtlFreeHeap(GetProcessHeap(), 0, q);
787 return status;
789 status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
790 timer_queue_thread_proc, q, &q->thread, NULL);
791 if (status != STATUS_SUCCESS)
793 NtClose(q->event);
794 RtlFreeHeap(GetProcessHeap(), 0, q);
795 return status;
798 *NewTimerQueue = q;
799 return STATUS_SUCCESS;
802 /***********************************************************************
803 * RtlDeleteTimerQueueEx (NTDLL.@)
805 * Deletes a timer queue object.
807 * PARAMS
808 * TimerQueue [I] The timer queue to destroy.
809 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
810 * wait until all timers are finished firing before
811 * returning. Otherwise, return immediately and set the
812 * event when all timers are done.
814 * RETURNS
815 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
816 * Failure: Any NTSTATUS code.
818 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
820 struct timer_queue *q = TimerQueue;
821 struct queue_timer *t, *temp;
822 HANDLE thread;
823 NTSTATUS status;
825 if (!q)
826 return STATUS_INVALID_HANDLE;
828 thread = q->thread;
830 RtlEnterCriticalSection(&q->cs);
831 q->quit = TRUE;
832 if (list_head(&q->timers))
833 /* When the last timer is removed, it will signal the timer thread to
834 exit... */
835 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
836 queue_destroy_timer(t);
837 else
838 /* However if we have none, we must do it ourselves. */
839 NtSetEvent(q->event, NULL);
840 RtlLeaveCriticalSection(&q->cs);
842 if (CompletionEvent == INVALID_HANDLE_VALUE)
844 NtWaitForSingleObject(thread, FALSE, NULL);
845 status = STATUS_SUCCESS;
847 else
849 if (CompletionEvent)
851 FIXME("asynchronous return on completion event unimplemented\n");
852 NtWaitForSingleObject(thread, FALSE, NULL);
853 NtSetEvent(CompletionEvent, NULL);
855 status = STATUS_PENDING;
858 NtClose(thread);
859 return status;
862 static struct timer_queue *default_timer_queue;
864 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
866 if (TimerQueue)
867 return TimerQueue;
868 else
870 if (!default_timer_queue)
872 HANDLE q;
873 NTSTATUS status = RtlCreateTimerQueue(&q);
874 if (status == STATUS_SUCCESS)
876 PVOID p = interlocked_cmpxchg_ptr(
877 (void **) &default_timer_queue, q, NULL);
878 if (p)
879 /* Got beat to the punch. */
880 RtlDeleteTimerQueueEx(p, NULL);
883 return default_timer_queue;
887 /***********************************************************************
888 * RtlCreateTimer (NTDLL.@)
890 * Creates a new timer associated with the given queue.
892 * PARAMS
893 * NewTimer [O] The newly created timer.
894 * TimerQueue [I] The queue to hold the timer.
895 * Callback [I] The callback to fire.
896 * Parameter [I] The argument for the callback.
897 * DueTime [I] The delay, in milliseconds, before first firing the
898 * timer.
899 * Period [I] The period, in milliseconds, at which to fire the timer
900 * after the first callback. If zero, the timer will only
901 * fire once. It still needs to be deleted with
902 * RtlDeleteTimer.
903 * Flags [I] Flags controling the execution of the callback. In
904 * addition to the WT_* thread pool flags (see
905 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
906 * WT_EXECUTEONLYONCE are supported.
908 * RETURNS
909 * Success: STATUS_SUCCESS.
910 * Failure: Any NTSTATUS code.
912 NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
913 RTL_WAITORTIMERCALLBACKFUNC Callback,
914 PVOID Parameter, DWORD DueTime, DWORD Period,
915 ULONG Flags)
917 NTSTATUS status;
918 struct queue_timer *t;
919 struct timer_queue *q = get_timer_queue(TimerQueue);
920 if (!q)
921 return STATUS_NO_MEMORY;
923 t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t);
924 if (!t)
925 return STATUS_NO_MEMORY;
927 t->q = q;
928 t->runcount = 0;
929 t->callback = Callback;
930 t->param = Parameter;
931 t->period = Period;
932 t->flags = Flags;
933 t->destroy = FALSE;
934 t->event = NULL;
936 status = STATUS_SUCCESS;
937 RtlEnterCriticalSection(&q->cs);
938 if (q->quit)
939 status = STATUS_INVALID_HANDLE;
940 else
941 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
942 RtlLeaveCriticalSection(&q->cs);
944 if (status == STATUS_SUCCESS)
945 *NewTimer = t;
946 else
947 RtlFreeHeap(GetProcessHeap(), 0, t);
949 return status;
952 /***********************************************************************
953 * RtlUpdateTimer (NTDLL.@)
955 * Changes the time at which a timer expires.
957 * PARAMS
958 * TimerQueue [I] The queue that holds the timer.
959 * Timer [I] The timer to update.
960 * DueTime [I] The delay, in milliseconds, before next firing the timer.
961 * Period [I] The period, in milliseconds, at which to fire the timer
962 * after the first callback. If zero, the timer will not
963 * refire once. It still needs to be deleted with
964 * RtlDeleteTimer.
966 * RETURNS
967 * Success: STATUS_SUCCESS.
968 * Failure: Any NTSTATUS code.
970 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
971 DWORD DueTime, DWORD Period)
973 struct queue_timer *t = Timer;
974 struct timer_queue *q = t->q;
976 RtlEnterCriticalSection(&q->cs);
977 /* Can't change a timer if it was once-only or destroyed. */
978 if (t->expire != EXPIRE_NEVER)
980 t->period = Period;
981 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
983 RtlLeaveCriticalSection(&q->cs);
985 return STATUS_SUCCESS;
988 /***********************************************************************
989 * RtlDeleteTimer (NTDLL.@)
991 * Cancels a timer-queue timer.
993 * PARAMS
994 * TimerQueue [I] The queue that holds the timer.
995 * Timer [I] The timer to update.
996 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
997 * wait until the timer is finished firing all pending
998 * callbacks before returning. Otherwise, return
999 * immediately and set the timer is done.
1001 * RETURNS
1002 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
1003 or if the completion event is NULL.
1004 * Failure: Any NTSTATUS code.
1006 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
1007 HANDLE CompletionEvent)
1009 struct queue_timer *t = Timer;
1010 struct timer_queue *q;
1011 NTSTATUS status = STATUS_PENDING;
1012 HANDLE event = NULL;
1014 if (!Timer)
1015 return STATUS_INVALID_PARAMETER_1;
1016 q = t->q;
1017 if (CompletionEvent == INVALID_HANDLE_VALUE)
1018 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
1019 else if (CompletionEvent)
1020 event = CompletionEvent;
1022 RtlEnterCriticalSection(&q->cs);
1023 t->event = event;
1024 if (t->runcount == 0 && event)
1025 status = STATUS_SUCCESS;
1026 queue_destroy_timer(t);
1027 RtlLeaveCriticalSection(&q->cs);
1029 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
1031 if (status == STATUS_PENDING)
1032 NtWaitForSingleObject(event, FALSE, NULL);
1033 NtClose(event);
1036 return status;