push 9758e6fe7ae8fbab538c98c718d6619029bb3457
[wine/hacks.git] / dlls / ntdll / threadpool.c
bloba60adfa6573192951dfab1723dc04e66d03c6753
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, LONG_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 );
263 /***********************************************************************
264 * RtlSetIoCompletionCallback (NTDLL.@)
266 * Binds a handle to a thread pool's completion port, and possibly
267 * starts a non-I/O thread to monitor this port and call functions back.
269 * PARAMS
270 * FileHandle [I] Handle to bind to a completion port.
271 * Function [I] Callback function to call on I/O completions.
272 * Flags [I] Not used.
274 * RETURNS
275 * Success: STATUS_SUCCESS.
276 * Failure: Any NTSTATUS code.
279 NTSTATUS WINAPI RtlSetIoCompletionCallback(HANDLE FileHandle, PRTL_OVERLAPPED_COMPLETION_ROUTINE Function, ULONG Flags)
281 IO_STATUS_BLOCK iosb;
282 FILE_COMPLETION_INFORMATION info;
284 if (Flags) FIXME("Unknown value Flags=0x%x\n", Flags);
286 if (!compl_port)
288 NTSTATUS res = STATUS_SUCCESS;
290 RtlEnterCriticalSection(&threadpool_compl_cs);
291 if (!compl_port)
293 HANDLE cport;
295 res = NtCreateIoCompletion( &cport, IO_COMPLETION_ALL_ACCESS, NULL, 0 );
296 if (!res)
298 /* FIXME native can start additional threads in case of e.g. hung callback function. */
299 res = RtlQueueWorkItem( iocp_poller, NULL, WT_EXECUTEDEFAULT );
300 if (!res)
301 compl_port = cport;
302 else
303 NtClose( cport );
306 RtlLeaveCriticalSection(&threadpool_compl_cs);
307 if (res) return res;
310 info.CompletionPort = compl_port;
311 info.CompletionKey = (ULONG_PTR)Function;
313 return NtSetInformationFile( FileHandle, &iosb, &info, sizeof(info), FileCompletionInformation );
316 static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
318 if (timeout == INFINITE) return NULL;
319 pTime->QuadPart = (ULONGLONG)timeout * -10000;
320 return pTime;
323 struct wait_work_item
325 HANDLE Object;
326 HANDLE CancelEvent;
327 WAITORTIMERCALLBACK Callback;
328 PVOID Context;
329 ULONG Milliseconds;
330 ULONG Flags;
331 HANDLE CompletionEvent;
332 LONG DeleteCount;
333 BOOLEAN CallbackInProgress;
336 static void delete_wait_work_item(struct wait_work_item *wait_work_item)
338 NtClose( wait_work_item->CancelEvent );
339 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
342 static DWORD CALLBACK wait_thread_proc(LPVOID Arg)
344 struct wait_work_item *wait_work_item = Arg;
345 NTSTATUS status;
346 BOOLEAN alertable = (wait_work_item->Flags & WT_EXECUTEINIOTHREAD) ? TRUE : FALSE;
347 HANDLE handles[2] = { wait_work_item->Object, wait_work_item->CancelEvent };
348 LARGE_INTEGER timeout;
349 HANDLE completion_event;
351 TRACE("\n");
353 while (TRUE)
355 status = NtWaitForMultipleObjects( 2, handles, FALSE, alertable,
356 get_nt_timeout( &timeout, wait_work_item->Milliseconds ) );
357 if (status == STATUS_WAIT_0 || status == STATUS_TIMEOUT)
359 BOOLEAN TimerOrWaitFired;
361 if (status == STATUS_WAIT_0)
363 TRACE( "object %p signaled, calling callback %p with context %p\n",
364 wait_work_item->Object, wait_work_item->Callback,
365 wait_work_item->Context );
366 TimerOrWaitFired = FALSE;
368 else
370 TRACE( "wait for object %p timed out, calling callback %p with context %p\n",
371 wait_work_item->Object, wait_work_item->Callback,
372 wait_work_item->Context );
373 TimerOrWaitFired = TRUE;
375 wait_work_item->CallbackInProgress = TRUE;
376 wait_work_item->Callback( wait_work_item->Context, TimerOrWaitFired );
377 wait_work_item->CallbackInProgress = FALSE;
379 if (wait_work_item->Flags & WT_EXECUTEONLYONCE)
380 break;
382 else
383 break;
386 completion_event = wait_work_item->CompletionEvent;
387 if (completion_event) NtSetEvent( completion_event, NULL );
389 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
390 delete_wait_work_item( wait_work_item );
392 return 0;
395 /***********************************************************************
396 * RtlRegisterWait (NTDLL.@)
398 * Registers a wait for a handle to become signaled.
400 * PARAMS
401 * NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it.
402 * Object [I] Object to wait to become signaled.
403 * Callback [I] Callback function to execute when the wait times out or the handle is signaled.
404 * Context [I] Context to pass to the callback function when it is executed.
405 * Milliseconds [I] Number of milliseconds to wait before timing out.
406 * Flags [I] Flags. See notes.
408 * RETURNS
409 * Success: STATUS_SUCCESS.
410 * Failure: Any NTSTATUS code.
412 * NOTES
413 * Flags can be one or more of the following:
414 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
415 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
416 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
417 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
418 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
420 NTSTATUS WINAPI RtlRegisterWait(PHANDLE NewWaitObject, HANDLE Object,
421 RTL_WAITORTIMERCALLBACKFUNC Callback,
422 PVOID Context, ULONG Milliseconds, ULONG Flags)
424 struct wait_work_item *wait_work_item;
425 NTSTATUS status;
427 TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags );
429 wait_work_item = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*wait_work_item) );
430 if (!wait_work_item)
431 return STATUS_NO_MEMORY;
433 wait_work_item->Object = Object;
434 wait_work_item->Callback = Callback;
435 wait_work_item->Context = Context;
436 wait_work_item->Milliseconds = Milliseconds;
437 wait_work_item->Flags = Flags;
438 wait_work_item->CallbackInProgress = FALSE;
439 wait_work_item->DeleteCount = 0;
440 wait_work_item->CompletionEvent = NULL;
442 status = NtCreateEvent( &wait_work_item->CancelEvent, EVENT_ALL_ACCESS, NULL, TRUE, FALSE );
443 if (status != STATUS_SUCCESS)
445 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
446 return status;
449 status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags & ~WT_EXECUTEONLYONCE );
450 if (status != STATUS_SUCCESS)
452 delete_wait_work_item( wait_work_item );
453 return status;
456 *NewWaitObject = wait_work_item;
457 return status;
460 /***********************************************************************
461 * RtlDeregisterWaitEx (NTDLL.@)
463 * Cancels a wait operation and frees the resources associated with calling
464 * RtlRegisterWait().
466 * PARAMS
467 * WaitObject [I] Handle to the wait object to free.
469 * RETURNS
470 * Success: STATUS_SUCCESS.
471 * Failure: Any NTSTATUS code.
473 NTSTATUS WINAPI RtlDeregisterWaitEx(HANDLE WaitHandle, HANDLE CompletionEvent)
475 struct wait_work_item *wait_work_item = WaitHandle;
476 NTSTATUS status = STATUS_SUCCESS;
478 TRACE( "(%p)\n", WaitHandle );
480 NtSetEvent( wait_work_item->CancelEvent, NULL );
481 if (wait_work_item->CallbackInProgress)
483 if (CompletionEvent != NULL)
485 if (CompletionEvent == INVALID_HANDLE_VALUE)
487 status = NtCreateEvent( &CompletionEvent, EVENT_ALL_ACCESS, NULL, TRUE, FALSE );
488 if (status != STATUS_SUCCESS)
489 return status;
490 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
491 if (wait_work_item->CallbackInProgress)
492 NtWaitForSingleObject( CompletionEvent, FALSE, NULL );
493 NtClose( CompletionEvent );
495 else
497 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
498 if (wait_work_item->CallbackInProgress)
499 status = STATUS_PENDING;
502 else
503 status = STATUS_PENDING;
506 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
508 status = STATUS_SUCCESS;
509 delete_wait_work_item( wait_work_item );
512 return status;
515 /***********************************************************************
516 * RtlDeregisterWait (NTDLL.@)
518 * Cancels a wait operation and frees the resources associated with calling
519 * RtlRegisterWait().
521 * PARAMS
522 * WaitObject [I] Handle to the wait object to free.
524 * RETURNS
525 * Success: STATUS_SUCCESS.
526 * Failure: Any NTSTATUS code.
528 NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle)
530 return RtlDeregisterWaitEx(WaitHandle, NULL);
534 /************************** Timer Queue Impl **************************/
536 struct timer_queue;
537 struct queue_timer
539 struct timer_queue *q;
540 struct list entry;
541 ULONG runcount; /* number of callbacks pending execution */
542 RTL_WAITORTIMERCALLBACKFUNC callback;
543 PVOID param;
544 DWORD period;
545 ULONG flags;
546 ULONGLONG expire;
547 BOOL destroy; /* timer should be deleted; once set, never unset */
548 HANDLE event; /* removal event */
551 struct timer_queue
553 RTL_CRITICAL_SECTION cs;
554 struct list timers; /* sorted by expiration time */
555 BOOL quit; /* queue should be deleted; once set, never unset */
556 HANDLE event;
557 HANDLE thread;
560 #define EXPIRE_NEVER (~(ULONGLONG) 0)
562 static void queue_remove_timer(struct queue_timer *t)
564 /* We MUST hold the queue cs while calling this function. This ensures
565 that we cannot queue another callback for this timer. The runcount
566 being zero makes sure we don't have any already queued. */
567 struct timer_queue *q = t->q;
569 assert(t->runcount == 0);
570 assert(t->destroy);
572 list_remove(&t->entry);
573 if (t->event)
574 NtSetEvent(t->event, NULL);
575 RtlFreeHeap(GetProcessHeap(), 0, t);
577 if (q->quit && list_count(&q->timers) == 0)
578 NtSetEvent(q->event, NULL);
581 static void timer_cleanup_callback(struct queue_timer *t)
583 struct timer_queue *q = t->q;
584 RtlEnterCriticalSection(&q->cs);
586 assert(0 < t->runcount);
587 --t->runcount;
589 if (t->destroy && t->runcount == 0)
590 queue_remove_timer(t);
592 RtlLeaveCriticalSection(&q->cs);
595 static DWORD WINAPI timer_callback_wrapper(LPVOID p)
597 struct queue_timer *t = p;
598 t->callback(t->param, TRUE);
599 timer_cleanup_callback(t);
600 return 0;
603 static inline ULONGLONG queue_current_time(void)
605 LARGE_INTEGER now;
606 NtQuerySystemTime(&now);
607 return now.QuadPart / 10000;
610 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
611 BOOL set_event)
613 /* We MUST hold the queue cs while calling this function. */
614 struct timer_queue *q = t->q;
615 struct list *ptr = &q->timers;
617 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
619 if (time != EXPIRE_NEVER)
620 LIST_FOR_EACH(ptr, &q->timers)
622 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
623 if (time < cur->expire)
624 break;
626 list_add_before(ptr, &t->entry);
628 t->expire = time;
630 /* If we insert at the head of the list, we need to expire sooner
631 than expected. */
632 if (set_event && &t->entry == list_head(&q->timers))
633 NtSetEvent(q->event, NULL);
636 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
637 BOOL set_event)
639 /* We MUST hold the queue cs while calling this function. */
640 list_remove(&t->entry);
641 queue_add_timer(t, time, set_event);
644 static void queue_timer_expire(struct timer_queue *q)
646 struct queue_timer *t;
648 RtlEnterCriticalSection(&q->cs);
649 if (list_head(&q->timers))
651 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
652 if (!t->destroy && t->expire <= queue_current_time())
654 ++t->runcount;
655 queue_move_timer(
656 t, t->period ? queue_current_time() + t->period : EXPIRE_NEVER,
657 FALSE);
660 else
661 t = NULL;
662 RtlLeaveCriticalSection(&q->cs);
664 if (t)
666 if (t->flags & WT_EXECUTEINTIMERTHREAD)
667 timer_callback_wrapper(t);
668 else
670 ULONG flags
671 = (t->flags
672 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
673 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
674 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
675 if (status != STATUS_SUCCESS)
676 timer_cleanup_callback(t);
681 static ULONG queue_get_timeout(struct timer_queue *q)
683 struct queue_timer *t;
684 ULONG timeout = INFINITE;
686 RtlEnterCriticalSection(&q->cs);
687 if (list_head(&q->timers))
689 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
690 assert(!t->destroy || t->expire == EXPIRE_NEVER);
692 if (t->expire != EXPIRE_NEVER)
694 ULONGLONG time = queue_current_time();
695 timeout = t->expire < time ? 0 : t->expire - time;
698 RtlLeaveCriticalSection(&q->cs);
700 return timeout;
703 static void WINAPI timer_queue_thread_proc(LPVOID p)
705 struct timer_queue *q = p;
706 ULONG timeout_ms;
708 timeout_ms = INFINITE;
709 for (;;)
711 LARGE_INTEGER timeout;
712 NTSTATUS status;
713 BOOL done = FALSE;
715 status = NtWaitForSingleObject(
716 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
718 if (status == STATUS_WAIT_0)
720 /* There are two possible ways to trigger the event. Either
721 we are quitting and the last timer got removed, or a new
722 timer got put at the head of the list so we need to adjust
723 our timeout. */
724 RtlEnterCriticalSection(&q->cs);
725 if (q->quit && list_count(&q->timers) == 0)
726 done = TRUE;
727 RtlLeaveCriticalSection(&q->cs);
729 else if (status == STATUS_TIMEOUT)
730 queue_timer_expire(q);
732 if (done)
733 break;
735 timeout_ms = queue_get_timeout(q);
738 NtClose(q->event);
739 RtlDeleteCriticalSection(&q->cs);
740 RtlFreeHeap(GetProcessHeap(), 0, q);
743 static void queue_destroy_timer(struct queue_timer *t)
745 /* We MUST hold the queue cs while calling this function. */
746 t->destroy = TRUE;
747 if (t->runcount == 0)
748 /* Ensure a timer is promptly removed. If callbacks are pending,
749 it will be removed after the last one finishes by the callback
750 cleanup wrapper. */
751 queue_remove_timer(t);
752 else
753 /* Make sure no destroyed timer masks an active timer at the head
754 of the sorted list. */
755 queue_move_timer(t, EXPIRE_NEVER, FALSE);
758 /***********************************************************************
759 * RtlCreateTimerQueue (NTDLL.@)
761 * Creates a timer queue object and returns a handle to it.
763 * PARAMS
764 * NewTimerQueue [O] The newly created queue.
766 * RETURNS
767 * Success: STATUS_SUCCESS.
768 * Failure: Any NTSTATUS code.
770 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
772 NTSTATUS status;
773 struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
774 if (!q)
775 return STATUS_NO_MEMORY;
777 RtlInitializeCriticalSection(&q->cs);
778 list_init(&q->timers);
779 q->quit = FALSE;
780 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, FALSE, FALSE);
781 if (status != STATUS_SUCCESS)
783 RtlFreeHeap(GetProcessHeap(), 0, q);
784 return status;
786 status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
787 timer_queue_thread_proc, q, &q->thread, NULL);
788 if (status != STATUS_SUCCESS)
790 NtClose(q->event);
791 RtlFreeHeap(GetProcessHeap(), 0, q);
792 return status;
795 *NewTimerQueue = q;
796 return STATUS_SUCCESS;
799 /***********************************************************************
800 * RtlDeleteTimerQueueEx (NTDLL.@)
802 * Deletes a timer queue object.
804 * PARAMS
805 * TimerQueue [I] The timer queue to destroy.
806 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
807 * wait until all timers are finished firing before
808 * returning. Otherwise, return immediately and set the
809 * event when all timers are done.
811 * RETURNS
812 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
813 * Failure: Any NTSTATUS code.
815 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
817 struct timer_queue *q = TimerQueue;
818 struct queue_timer *t, *temp;
819 HANDLE thread = q->thread;
820 NTSTATUS status;
822 RtlEnterCriticalSection(&q->cs);
823 q->quit = TRUE;
824 if (list_head(&q->timers))
825 /* When the last timer is removed, it will signal the timer thread to
826 exit... */
827 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
828 queue_destroy_timer(t);
829 else
830 /* However if we have none, we must do it ourselves. */
831 NtSetEvent(q->event, NULL);
832 RtlLeaveCriticalSection(&q->cs);
834 if (CompletionEvent == INVALID_HANDLE_VALUE)
836 NtWaitForSingleObject(thread, FALSE, NULL);
837 status = STATUS_SUCCESS;
839 else
841 if (CompletionEvent)
843 FIXME("asynchronous return on completion event unimplemented\n");
844 NtWaitForSingleObject(thread, FALSE, NULL);
845 NtSetEvent(CompletionEvent, NULL);
847 status = STATUS_PENDING;
850 NtClose(thread);
851 return status;
854 /***********************************************************************
855 * RtlCreateTimer (NTDLL.@)
857 * Creates a new timer associated with the given queue.
859 * PARAMS
860 * NewTimer [O] The newly created timer.
861 * TimerQueue [I] The queue to hold the timer.
862 * Callback [I] The callback to fire.
863 * Parameter [I] The argument for the callback.
864 * DueTime [I] The delay, in milliseconds, before first firing the
865 * timer.
866 * Period [I] The period, in milliseconds, at which to fire the timer
867 * after the first callback. If zero, the timer will only
868 * fire once. It still needs to be deleted with
869 * RtlDeleteTimer.
870 * Flags [I] Flags controling the execution of the callback. In
871 * addition to the WT_* thread pool flags (see
872 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
873 * WT_EXECUTEONLYONCE are supported.
875 * RETURNS
876 * Success: STATUS_SUCCESS.
877 * Failure: Any NTSTATUS code.
879 NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
880 RTL_WAITORTIMERCALLBACKFUNC Callback,
881 PVOID Parameter, DWORD DueTime, DWORD Period,
882 ULONG Flags)
884 NTSTATUS status;
885 struct timer_queue *q = TimerQueue;
886 struct queue_timer *t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t);
887 if (!t)
888 return STATUS_NO_MEMORY;
890 t->q = q;
891 t->runcount = 0;
892 t->callback = Callback;
893 t->param = Parameter;
894 t->period = Period;
895 t->flags = Flags;
896 t->destroy = FALSE;
897 t->event = NULL;
899 status = STATUS_SUCCESS;
900 RtlEnterCriticalSection(&q->cs);
901 if (q->quit)
902 status = STATUS_INVALID_HANDLE;
903 else
904 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
905 RtlLeaveCriticalSection(&q->cs);
907 if (status == STATUS_SUCCESS)
908 *NewTimer = t;
909 else
910 RtlFreeHeap(GetProcessHeap(), 0, t);
912 return status;
915 /***********************************************************************
916 * RtlUpdateTimer (NTDLL.@)
918 * Changes the time at which a timer expires.
920 * PARAMS
921 * TimerQueue [I] The queue that holds the timer.
922 * Timer [I] The timer to update.
923 * DueTime [I] The delay, in milliseconds, before next firing the timer.
924 * Period [I] The period, in milliseconds, at which to fire the timer
925 * after the first callback. If zero, the timer will not
926 * refire once. It still needs to be deleted with
927 * RtlDeleteTimer.
929 * RETURNS
930 * Success: STATUS_SUCCESS.
931 * Failure: Any NTSTATUS code.
933 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
934 DWORD DueTime, DWORD Period)
936 struct timer_queue *q = TimerQueue;
937 struct queue_timer *t = Timer;
939 RtlEnterCriticalSection(&q->cs);
940 /* Can't change a timer if it was once-only or destroyed. */
941 if (t->expire != EXPIRE_NEVER)
943 t->period = Period;
944 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
946 RtlLeaveCriticalSection(&q->cs);
948 return STATUS_SUCCESS;
951 /***********************************************************************
952 * RtlDeleteTimer (NTDLL.@)
954 * Cancels a timer-queue timer.
956 * PARAMS
957 * TimerQueue [I] The queue that holds the timer.
958 * Timer [I] The timer to update.
959 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
960 * wait until the timer is finished firing all pending
961 * callbacks before returning. Otherwise, return
962 * immediately and set the timer is done.
964 * RETURNS
965 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
966 or if the completion event is NULL.
967 * Failure: Any NTSTATUS code.
969 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
970 HANDLE CompletionEvent)
972 struct timer_queue *q = TimerQueue;
973 struct queue_timer *t = Timer;
974 NTSTATUS status = STATUS_PENDING;
975 HANDLE event = NULL;
977 if (CompletionEvent == INVALID_HANDLE_VALUE)
978 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, FALSE, FALSE);
979 else if (CompletionEvent)
980 event = CompletionEvent;
982 RtlEnterCriticalSection(&q->cs);
983 t->event = event;
984 if (t->runcount == 0 && event)
985 status = STATUS_SUCCESS;
986 queue_destroy_timer(t);
987 RtlLeaveCriticalSection(&q->cs);
989 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
991 if (status == STATUS_PENDING)
992 NtWaitForSingleObject(event, FALSE, NULL);
993 NtClose(event);
996 return status;