vbscript: Implemented Tan.
[wine.git] / dlls / ntdll / threadpool.c
blobf66f6abe6832bee45a47043d16040b4b09a60c2b
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 /* threadpool_cs must be held while modifying the following elements */
43 static struct list work_item_list = LIST_INIT(work_item_list);
44 static LONG num_workers;
45 static LONG num_busy_workers;
46 static LONG num_items_processed;
48 static RTL_CONDITION_VARIABLE threadpool_cond = RTL_CONDITION_VARIABLE_INIT;
50 static RTL_CRITICAL_SECTION threadpool_cs;
51 static RTL_CRITICAL_SECTION_DEBUG critsect_debug =
53 0, 0, &threadpool_cs,
54 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
55 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_cs") }
57 static RTL_CRITICAL_SECTION threadpool_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
59 static HANDLE compl_port = NULL;
60 static RTL_CRITICAL_SECTION threadpool_compl_cs;
61 static RTL_CRITICAL_SECTION_DEBUG critsect_compl_debug =
63 0, 0, &threadpool_compl_cs,
64 { &critsect_compl_debug.ProcessLocksList, &critsect_compl_debug.ProcessLocksList },
65 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_compl_cs") }
67 static RTL_CRITICAL_SECTION threadpool_compl_cs = { &critsect_compl_debug, -1, 0, 0, 0, 0 };
69 struct work_item
71 struct list entry;
72 PRTL_WORK_ITEM_ROUTINE function;
73 PVOID context;
76 static inline LONG interlocked_inc( PLONG dest )
78 return interlocked_xchg_add( dest, 1 ) + 1;
81 static inline LONG interlocked_dec( PLONG dest )
83 return interlocked_xchg_add( dest, -1 ) - 1;
86 static void WINAPI worker_thread_proc(void * param)
88 struct list *item;
89 struct work_item *work_item_ptr, work_item;
90 LARGE_INTEGER timeout;
91 timeout.QuadPart = -(WORKER_TIMEOUT * (ULONGLONG)10000);
93 RtlEnterCriticalSection( &threadpool_cs );
94 num_workers++;
96 for (;;)
98 if ((item = list_head( &work_item_list )))
100 work_item_ptr = LIST_ENTRY( item, struct work_item, entry );
101 list_remove( &work_item_ptr->entry );
102 num_busy_workers++;
103 num_items_processed++;
104 RtlLeaveCriticalSection( &threadpool_cs );
106 /* copy item to stack and do the work */
107 work_item = *work_item_ptr;
108 RtlFreeHeap( GetProcessHeap(), 0, work_item_ptr );
109 TRACE("executing %p(%p)\n", work_item.function, work_item.context);
110 work_item.function( work_item.context );
112 RtlEnterCriticalSection( &threadpool_cs );
113 num_busy_workers--;
115 else if (RtlSleepConditionVariableCS( &threadpool_cond, &threadpool_cs, &timeout ) != STATUS_SUCCESS)
116 break;
119 num_workers--;
120 RtlLeaveCriticalSection( &threadpool_cs );
121 RtlExitUserThread( 0 );
123 /* never reached */
126 /***********************************************************************
127 * RtlQueueWorkItem (NTDLL.@)
129 * Queues a work item into a thread in the thread pool.
131 * PARAMS
132 * Function [I] Work function to execute.
133 * Context [I] Context to pass to the work function when it is executed.
134 * Flags [I] Flags. See notes.
136 * RETURNS
137 * Success: STATUS_SUCCESS.
138 * Failure: Any NTSTATUS code.
140 * NOTES
141 * Flags can be one or more of the following:
142 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
143 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
144 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
145 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
146 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
148 NTSTATUS WINAPI RtlQueueWorkItem(PRTL_WORK_ITEM_ROUTINE Function, PVOID Context, ULONG Flags)
150 HANDLE thread;
151 NTSTATUS status;
152 LONG items_processed;
153 struct work_item *work_item = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(struct work_item));
155 if (!work_item)
156 return STATUS_NO_MEMORY;
158 work_item->function = Function;
159 work_item->context = Context;
161 if (Flags & ~WT_EXECUTELONGFUNCTION)
162 FIXME("Flags 0x%x not supported\n", Flags);
164 RtlEnterCriticalSection( &threadpool_cs );
165 list_add_tail( &work_item_list, &work_item->entry );
166 status = (num_workers > num_busy_workers) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
167 items_processed = num_items_processed;
168 RtlLeaveCriticalSection( &threadpool_cs );
170 /* FIXME: tune this algorithm to not be as aggressive with creating threads
171 * if WT_EXECUTELONGFUNCTION isn't specified */
172 if (status == STATUS_SUCCESS)
173 RtlWakeConditionVariable( &threadpool_cond );
174 else
176 status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
177 worker_thread_proc, NULL, &thread, NULL );
179 /* NOTE: we don't care if we couldn't create the thread if there is at
180 * least one other available to process the request */
181 if (status == STATUS_SUCCESS)
182 NtClose( thread );
183 else
185 RtlEnterCriticalSection( &threadpool_cs );
186 if (num_workers > 0 || num_items_processed != items_processed)
187 status = STATUS_SUCCESS;
188 else
189 list_remove( &work_item->entry );
190 RtlLeaveCriticalSection( &threadpool_cs );
192 if (status != STATUS_SUCCESS)
193 RtlFreeHeap( GetProcessHeap(), 0, work_item );
197 return status;
200 /***********************************************************************
201 * iocp_poller - get completion events and run callbacks
203 static DWORD CALLBACK iocp_poller(LPVOID Arg)
205 HANDLE cport = Arg;
207 while( TRUE )
209 PRTL_OVERLAPPED_COMPLETION_ROUTINE callback;
210 LPVOID overlapped;
211 IO_STATUS_BLOCK iosb;
212 NTSTATUS res = NtRemoveIoCompletion( cport, (PULONG_PTR)&callback, (PULONG_PTR)&overlapped, &iosb, NULL );
213 if (res)
215 ERR("NtRemoveIoCompletion failed: 0x%x\n", res);
217 else
219 DWORD transferred = 0;
220 DWORD err = 0;
222 if (iosb.u.Status == STATUS_SUCCESS)
223 transferred = iosb.Information;
224 else
225 err = RtlNtStatusToDosError(iosb.u.Status);
227 callback( err, transferred, overlapped );
230 return 0;
233 /***********************************************************************
234 * RtlSetIoCompletionCallback (NTDLL.@)
236 * Binds a handle to a thread pool's completion port, and possibly
237 * starts a non-I/O thread to monitor this port and call functions back.
239 * PARAMS
240 * FileHandle [I] Handle to bind to a completion port.
241 * Function [I] Callback function to call on I/O completions.
242 * Flags [I] Not used.
244 * RETURNS
245 * Success: STATUS_SUCCESS.
246 * Failure: Any NTSTATUS code.
249 NTSTATUS WINAPI RtlSetIoCompletionCallback(HANDLE FileHandle, PRTL_OVERLAPPED_COMPLETION_ROUTINE Function, ULONG Flags)
251 IO_STATUS_BLOCK iosb;
252 FILE_COMPLETION_INFORMATION info;
254 if (Flags) FIXME("Unknown value Flags=0x%x\n", Flags);
256 if (!compl_port)
258 NTSTATUS res = STATUS_SUCCESS;
260 RtlEnterCriticalSection(&threadpool_compl_cs);
261 if (!compl_port)
263 HANDLE cport;
265 res = NtCreateIoCompletion( &cport, IO_COMPLETION_ALL_ACCESS, NULL, 0 );
266 if (!res)
268 /* FIXME native can start additional threads in case of e.g. hung callback function. */
269 res = RtlQueueWorkItem( iocp_poller, cport, WT_EXECUTEDEFAULT );
270 if (!res)
271 compl_port = cport;
272 else
273 NtClose( cport );
276 RtlLeaveCriticalSection(&threadpool_compl_cs);
277 if (res) return res;
280 info.CompletionPort = compl_port;
281 info.CompletionKey = (ULONG_PTR)Function;
283 return NtSetInformationFile( FileHandle, &iosb, &info, sizeof(info), FileCompletionInformation );
286 static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
288 if (timeout == INFINITE) return NULL;
289 pTime->QuadPart = (ULONGLONG)timeout * -10000;
290 return pTime;
293 struct wait_work_item
295 HANDLE Object;
296 HANDLE CancelEvent;
297 WAITORTIMERCALLBACK Callback;
298 PVOID Context;
299 ULONG Milliseconds;
300 ULONG Flags;
301 HANDLE CompletionEvent;
302 LONG DeleteCount;
303 BOOLEAN CallbackInProgress;
306 static void delete_wait_work_item(struct wait_work_item *wait_work_item)
308 NtClose( wait_work_item->CancelEvent );
309 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
312 static DWORD CALLBACK wait_thread_proc(LPVOID Arg)
314 struct wait_work_item *wait_work_item = Arg;
315 NTSTATUS status;
316 BOOLEAN alertable = (wait_work_item->Flags & WT_EXECUTEINIOTHREAD) != 0;
317 HANDLE handles[2] = { wait_work_item->Object, wait_work_item->CancelEvent };
318 LARGE_INTEGER timeout;
319 HANDLE completion_event;
321 TRACE("\n");
323 while (TRUE)
325 status = NtWaitForMultipleObjects( 2, handles, FALSE, alertable,
326 get_nt_timeout( &timeout, wait_work_item->Milliseconds ) );
327 if (status == STATUS_WAIT_0 || status == STATUS_TIMEOUT)
329 BOOLEAN TimerOrWaitFired;
331 if (status == STATUS_WAIT_0)
333 TRACE( "object %p signaled, calling callback %p with context %p\n",
334 wait_work_item->Object, wait_work_item->Callback,
335 wait_work_item->Context );
336 TimerOrWaitFired = FALSE;
338 else
340 TRACE( "wait for object %p timed out, calling callback %p with context %p\n",
341 wait_work_item->Object, wait_work_item->Callback,
342 wait_work_item->Context );
343 TimerOrWaitFired = TRUE;
345 wait_work_item->CallbackInProgress = TRUE;
346 wait_work_item->Callback( wait_work_item->Context, TimerOrWaitFired );
347 wait_work_item->CallbackInProgress = FALSE;
349 if (wait_work_item->Flags & WT_EXECUTEONLYONCE)
350 break;
352 else
353 break;
356 completion_event = wait_work_item->CompletionEvent;
357 if (completion_event) NtSetEvent( completion_event, NULL );
359 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
360 delete_wait_work_item( wait_work_item );
362 return 0;
365 /***********************************************************************
366 * RtlRegisterWait (NTDLL.@)
368 * Registers a wait for a handle to become signaled.
370 * PARAMS
371 * NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it.
372 * Object [I] Object to wait to become signaled.
373 * Callback [I] Callback function to execute when the wait times out or the handle is signaled.
374 * Context [I] Context to pass to the callback function when it is executed.
375 * Milliseconds [I] Number of milliseconds to wait before timing out.
376 * Flags [I] Flags. See notes.
378 * RETURNS
379 * Success: STATUS_SUCCESS.
380 * Failure: Any NTSTATUS code.
382 * NOTES
383 * Flags can be one or more of the following:
384 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
385 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
386 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
387 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
388 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
390 NTSTATUS WINAPI RtlRegisterWait(PHANDLE NewWaitObject, HANDLE Object,
391 RTL_WAITORTIMERCALLBACKFUNC Callback,
392 PVOID Context, ULONG Milliseconds, ULONG Flags)
394 struct wait_work_item *wait_work_item;
395 NTSTATUS status;
397 TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags );
399 wait_work_item = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*wait_work_item) );
400 if (!wait_work_item)
401 return STATUS_NO_MEMORY;
403 wait_work_item->Object = Object;
404 wait_work_item->Callback = Callback;
405 wait_work_item->Context = Context;
406 wait_work_item->Milliseconds = Milliseconds;
407 wait_work_item->Flags = Flags;
408 wait_work_item->CallbackInProgress = FALSE;
409 wait_work_item->DeleteCount = 0;
410 wait_work_item->CompletionEvent = NULL;
412 status = NtCreateEvent( &wait_work_item->CancelEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE );
413 if (status != STATUS_SUCCESS)
415 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
416 return status;
419 Flags = Flags & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD |
420 WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION);
421 status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags );
422 if (status != STATUS_SUCCESS)
424 delete_wait_work_item( wait_work_item );
425 return status;
428 *NewWaitObject = wait_work_item;
429 return status;
432 /***********************************************************************
433 * RtlDeregisterWaitEx (NTDLL.@)
435 * Cancels a wait operation and frees the resources associated with calling
436 * RtlRegisterWait().
438 * PARAMS
439 * WaitObject [I] Handle to the wait object to free.
441 * RETURNS
442 * Success: STATUS_SUCCESS.
443 * Failure: Any NTSTATUS code.
445 NTSTATUS WINAPI RtlDeregisterWaitEx(HANDLE WaitHandle, HANDLE CompletionEvent)
447 struct wait_work_item *wait_work_item = WaitHandle;
448 NTSTATUS status = STATUS_SUCCESS;
450 TRACE( "(%p)\n", WaitHandle );
452 NtSetEvent( wait_work_item->CancelEvent, NULL );
453 if (wait_work_item->CallbackInProgress)
455 if (CompletionEvent != NULL)
457 if (CompletionEvent == INVALID_HANDLE_VALUE)
459 status = NtCreateEvent( &CompletionEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE );
460 if (status != STATUS_SUCCESS)
461 return status;
462 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
463 if (wait_work_item->CallbackInProgress)
464 NtWaitForSingleObject( CompletionEvent, FALSE, NULL );
465 NtClose( CompletionEvent );
467 else
469 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
470 if (wait_work_item->CallbackInProgress)
471 status = STATUS_PENDING;
474 else
475 status = STATUS_PENDING;
478 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
480 status = STATUS_SUCCESS;
481 delete_wait_work_item( wait_work_item );
484 return status;
487 /***********************************************************************
488 * RtlDeregisterWait (NTDLL.@)
490 * Cancels a wait operation and frees the resources associated with calling
491 * RtlRegisterWait().
493 * PARAMS
494 * WaitObject [I] Handle to the wait object to free.
496 * RETURNS
497 * Success: STATUS_SUCCESS.
498 * Failure: Any NTSTATUS code.
500 NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle)
502 return RtlDeregisterWaitEx(WaitHandle, NULL);
506 /************************** Timer Queue Impl **************************/
508 struct timer_queue;
509 struct queue_timer
511 struct timer_queue *q;
512 struct list entry;
513 ULONG runcount; /* number of callbacks pending execution */
514 RTL_WAITORTIMERCALLBACKFUNC callback;
515 PVOID param;
516 DWORD period;
517 ULONG flags;
518 ULONGLONG expire;
519 BOOL destroy; /* timer should be deleted; once set, never unset */
520 HANDLE event; /* removal event */
523 struct timer_queue
525 DWORD magic;
526 RTL_CRITICAL_SECTION cs;
527 struct list timers; /* sorted by expiration time */
528 BOOL quit; /* queue should be deleted; once set, never unset */
529 HANDLE event;
530 HANDLE thread;
533 #define EXPIRE_NEVER (~(ULONGLONG) 0)
534 #define TIMER_QUEUE_MAGIC 0x516d6954 /* TimQ */
536 static void queue_remove_timer(struct queue_timer *t)
538 /* We MUST hold the queue cs while calling this function. This ensures
539 that we cannot queue another callback for this timer. The runcount
540 being zero makes sure we don't have any already queued. */
541 struct timer_queue *q = t->q;
543 assert(t->runcount == 0);
544 assert(t->destroy);
546 list_remove(&t->entry);
547 if (t->event)
548 NtSetEvent(t->event, NULL);
549 RtlFreeHeap(GetProcessHeap(), 0, t);
551 if (q->quit && list_empty(&q->timers))
552 NtSetEvent(q->event, NULL);
555 static void timer_cleanup_callback(struct queue_timer *t)
557 struct timer_queue *q = t->q;
558 RtlEnterCriticalSection(&q->cs);
560 assert(0 < t->runcount);
561 --t->runcount;
563 if (t->destroy && t->runcount == 0)
564 queue_remove_timer(t);
566 RtlLeaveCriticalSection(&q->cs);
569 static DWORD WINAPI timer_callback_wrapper(LPVOID p)
571 struct queue_timer *t = p;
572 t->callback(t->param, TRUE);
573 timer_cleanup_callback(t);
574 return 0;
577 static inline ULONGLONG queue_current_time(void)
579 LARGE_INTEGER now, freq;
580 NtQueryPerformanceCounter(&now, &freq);
581 return now.QuadPart * 1000 / freq.QuadPart;
584 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
585 BOOL set_event)
587 /* We MUST hold the queue cs while calling this function. */
588 struct timer_queue *q = t->q;
589 struct list *ptr = &q->timers;
591 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
593 if (time != EXPIRE_NEVER)
594 LIST_FOR_EACH(ptr, &q->timers)
596 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
597 if (time < cur->expire)
598 break;
600 list_add_before(ptr, &t->entry);
602 t->expire = time;
604 /* If we insert at the head of the list, we need to expire sooner
605 than expected. */
606 if (set_event && &t->entry == list_head(&q->timers))
607 NtSetEvent(q->event, NULL);
610 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
611 BOOL set_event)
613 /* We MUST hold the queue cs while calling this function. */
614 list_remove(&t->entry);
615 queue_add_timer(t, time, set_event);
618 static void queue_timer_expire(struct timer_queue *q)
620 struct queue_timer *t = NULL;
622 RtlEnterCriticalSection(&q->cs);
623 if (list_head(&q->timers))
625 ULONGLONG now, next;
626 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
627 if (!t->destroy && t->expire <= ((now = queue_current_time())))
629 ++t->runcount;
630 if (t->period)
632 next = t->expire + t->period;
633 /* avoid trigger cascade if overloaded / hibernated */
634 if (next < now)
635 next = now + t->period;
637 else
638 next = EXPIRE_NEVER;
639 queue_move_timer(t, next, FALSE);
641 else
642 t = NULL;
644 RtlLeaveCriticalSection(&q->cs);
646 if (t)
648 if (t->flags & WT_EXECUTEINTIMERTHREAD)
649 timer_callback_wrapper(t);
650 else
652 ULONG flags
653 = (t->flags
654 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
655 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
656 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
657 if (status != STATUS_SUCCESS)
658 timer_cleanup_callback(t);
663 static ULONG queue_get_timeout(struct timer_queue *q)
665 struct queue_timer *t;
666 ULONG timeout = INFINITE;
668 RtlEnterCriticalSection(&q->cs);
669 if (list_head(&q->timers))
671 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
672 assert(!t->destroy || t->expire == EXPIRE_NEVER);
674 if (t->expire != EXPIRE_NEVER)
676 ULONGLONG time = queue_current_time();
677 timeout = t->expire < time ? 0 : t->expire - time;
680 RtlLeaveCriticalSection(&q->cs);
682 return timeout;
685 static void WINAPI timer_queue_thread_proc(LPVOID p)
687 struct timer_queue *q = p;
688 ULONG timeout_ms;
690 timeout_ms = INFINITE;
691 for (;;)
693 LARGE_INTEGER timeout;
694 NTSTATUS status;
695 BOOL done = FALSE;
697 status = NtWaitForSingleObject(
698 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
700 if (status == STATUS_WAIT_0)
702 /* There are two possible ways to trigger the event. Either
703 we are quitting and the last timer got removed, or a new
704 timer got put at the head of the list so we need to adjust
705 our timeout. */
706 RtlEnterCriticalSection(&q->cs);
707 if (q->quit && list_empty(&q->timers))
708 done = TRUE;
709 RtlLeaveCriticalSection(&q->cs);
711 else if (status == STATUS_TIMEOUT)
712 queue_timer_expire(q);
714 if (done)
715 break;
717 timeout_ms = queue_get_timeout(q);
720 NtClose(q->event);
721 RtlDeleteCriticalSection(&q->cs);
722 q->magic = 0;
723 RtlFreeHeap(GetProcessHeap(), 0, q);
726 static void queue_destroy_timer(struct queue_timer *t)
728 /* We MUST hold the queue cs while calling this function. */
729 t->destroy = TRUE;
730 if (t->runcount == 0)
731 /* Ensure a timer is promptly removed. If callbacks are pending,
732 it will be removed after the last one finishes by the callback
733 cleanup wrapper. */
734 queue_remove_timer(t);
735 else
736 /* Make sure no destroyed timer masks an active timer at the head
737 of the sorted list. */
738 queue_move_timer(t, EXPIRE_NEVER, FALSE);
741 /***********************************************************************
742 * RtlCreateTimerQueue (NTDLL.@)
744 * Creates a timer queue object and returns a handle to it.
746 * PARAMS
747 * NewTimerQueue [O] The newly created queue.
749 * RETURNS
750 * Success: STATUS_SUCCESS.
751 * Failure: Any NTSTATUS code.
753 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
755 NTSTATUS status;
756 struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
757 if (!q)
758 return STATUS_NO_MEMORY;
760 RtlInitializeCriticalSection(&q->cs);
761 list_init(&q->timers);
762 q->quit = FALSE;
763 q->magic = TIMER_QUEUE_MAGIC;
764 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
765 if (status != STATUS_SUCCESS)
767 RtlFreeHeap(GetProcessHeap(), 0, q);
768 return status;
770 status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
771 timer_queue_thread_proc, q, &q->thread, NULL);
772 if (status != STATUS_SUCCESS)
774 NtClose(q->event);
775 RtlFreeHeap(GetProcessHeap(), 0, q);
776 return status;
779 *NewTimerQueue = q;
780 return STATUS_SUCCESS;
783 /***********************************************************************
784 * RtlDeleteTimerQueueEx (NTDLL.@)
786 * Deletes a timer queue object.
788 * PARAMS
789 * TimerQueue [I] The timer queue to destroy.
790 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
791 * wait until all timers are finished firing before
792 * returning. Otherwise, return immediately and set the
793 * event when all timers are done.
795 * RETURNS
796 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
797 * Failure: Any NTSTATUS code.
799 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
801 struct timer_queue *q = TimerQueue;
802 struct queue_timer *t, *temp;
803 HANDLE thread;
804 NTSTATUS status;
806 if (!q || q->magic != TIMER_QUEUE_MAGIC)
807 return STATUS_INVALID_HANDLE;
809 thread = q->thread;
811 RtlEnterCriticalSection(&q->cs);
812 q->quit = TRUE;
813 if (list_head(&q->timers))
814 /* When the last timer is removed, it will signal the timer thread to
815 exit... */
816 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
817 queue_destroy_timer(t);
818 else
819 /* However if we have none, we must do it ourselves. */
820 NtSetEvent(q->event, NULL);
821 RtlLeaveCriticalSection(&q->cs);
823 if (CompletionEvent == INVALID_HANDLE_VALUE)
825 NtWaitForSingleObject(thread, FALSE, NULL);
826 status = STATUS_SUCCESS;
828 else
830 if (CompletionEvent)
832 FIXME("asynchronous return on completion event unimplemented\n");
833 NtWaitForSingleObject(thread, FALSE, NULL);
834 NtSetEvent(CompletionEvent, NULL);
836 status = STATUS_PENDING;
839 NtClose(thread);
840 return status;
843 static struct timer_queue *default_timer_queue;
845 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
847 if (TimerQueue)
848 return TimerQueue;
849 else
851 if (!default_timer_queue)
853 HANDLE q;
854 NTSTATUS status = RtlCreateTimerQueue(&q);
855 if (status == STATUS_SUCCESS)
857 PVOID p = interlocked_cmpxchg_ptr(
858 (void **) &default_timer_queue, q, NULL);
859 if (p)
860 /* Got beat to the punch. */
861 RtlDeleteTimerQueueEx(q, NULL);
864 return default_timer_queue;
868 /***********************************************************************
869 * RtlCreateTimer (NTDLL.@)
871 * Creates a new timer associated with the given queue.
873 * PARAMS
874 * NewTimer [O] The newly created timer.
875 * TimerQueue [I] The queue to hold the timer.
876 * Callback [I] The callback to fire.
877 * Parameter [I] The argument for the callback.
878 * DueTime [I] The delay, in milliseconds, before first firing the
879 * timer.
880 * Period [I] The period, in milliseconds, at which to fire the timer
881 * after the first callback. If zero, the timer will only
882 * fire once. It still needs to be deleted with
883 * RtlDeleteTimer.
884 * Flags [I] Flags controlling the execution of the callback. In
885 * addition to the WT_* thread pool flags (see
886 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
887 * WT_EXECUTEONLYONCE are supported.
889 * RETURNS
890 * Success: STATUS_SUCCESS.
891 * Failure: Any NTSTATUS code.
893 NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
894 RTL_WAITORTIMERCALLBACKFUNC Callback,
895 PVOID Parameter, DWORD DueTime, DWORD Period,
896 ULONG Flags)
898 NTSTATUS status;
899 struct queue_timer *t;
900 struct timer_queue *q = get_timer_queue(TimerQueue);
902 if (!q) return STATUS_NO_MEMORY;
903 if (q->magic != TIMER_QUEUE_MAGIC) return STATUS_INVALID_HANDLE;
905 t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t);
906 if (!t)
907 return STATUS_NO_MEMORY;
909 t->q = q;
910 t->runcount = 0;
911 t->callback = Callback;
912 t->param = Parameter;
913 t->period = Period;
914 t->flags = Flags;
915 t->destroy = FALSE;
916 t->event = NULL;
918 status = STATUS_SUCCESS;
919 RtlEnterCriticalSection(&q->cs);
920 if (q->quit)
921 status = STATUS_INVALID_HANDLE;
922 else
923 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
924 RtlLeaveCriticalSection(&q->cs);
926 if (status == STATUS_SUCCESS)
927 *NewTimer = t;
928 else
929 RtlFreeHeap(GetProcessHeap(), 0, t);
931 return status;
934 /***********************************************************************
935 * RtlUpdateTimer (NTDLL.@)
937 * Changes the time at which a timer expires.
939 * PARAMS
940 * TimerQueue [I] The queue that holds the timer.
941 * Timer [I] The timer to update.
942 * DueTime [I] The delay, in milliseconds, before next firing the timer.
943 * Period [I] The period, in milliseconds, at which to fire the timer
944 * after the first callback. If zero, the timer will not
945 * refire once. It still needs to be deleted with
946 * RtlDeleteTimer.
948 * RETURNS
949 * Success: STATUS_SUCCESS.
950 * Failure: Any NTSTATUS code.
952 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
953 DWORD DueTime, DWORD Period)
955 struct queue_timer *t = Timer;
956 struct timer_queue *q = t->q;
958 RtlEnterCriticalSection(&q->cs);
959 /* Can't change a timer if it was once-only or destroyed. */
960 if (t->expire != EXPIRE_NEVER)
962 t->period = Period;
963 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
965 RtlLeaveCriticalSection(&q->cs);
967 return STATUS_SUCCESS;
970 /***********************************************************************
971 * RtlDeleteTimer (NTDLL.@)
973 * Cancels a timer-queue timer.
975 * PARAMS
976 * TimerQueue [I] The queue that holds the timer.
977 * Timer [I] The timer to update.
978 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
979 * wait until the timer is finished firing all pending
980 * callbacks before returning. Otherwise, return
981 * immediately and set the timer is done.
983 * RETURNS
984 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
985 or if the completion event is NULL.
986 * Failure: Any NTSTATUS code.
988 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
989 HANDLE CompletionEvent)
991 struct queue_timer *t = Timer;
992 struct timer_queue *q;
993 NTSTATUS status = STATUS_PENDING;
994 HANDLE event = NULL;
996 if (!Timer)
997 return STATUS_INVALID_PARAMETER_1;
998 q = t->q;
999 if (CompletionEvent == INVALID_HANDLE_VALUE)
1001 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
1002 if (status == STATUS_SUCCESS)
1003 status = STATUS_PENDING;
1005 else if (CompletionEvent)
1006 event = CompletionEvent;
1008 RtlEnterCriticalSection(&q->cs);
1009 t->event = event;
1010 if (t->runcount == 0 && event)
1011 status = STATUS_SUCCESS;
1012 queue_destroy_timer(t);
1013 RtlLeaveCriticalSection(&q->cs);
1015 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
1017 if (status == STATUS_PENDING)
1019 NtWaitForSingleObject(event, FALSE, NULL);
1020 status = STATUS_SUCCESS;
1022 NtClose(event);
1025 return status;