ntdll: Fix possible race-condition in iocp poller code.
[wine.git] / dlls / ntdll / threadpool.c
blobe2fc6a5989ae6d849e915f7d46e42efa9e03932b
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 HANDLE cport = Arg;
240 while( TRUE )
242 PRTL_OVERLAPPED_COMPLETION_ROUTINE callback;
243 LPVOID overlapped;
244 IO_STATUS_BLOCK iosb;
245 NTSTATUS res = NtRemoveIoCompletion( cport, (PULONG_PTR)&callback, (PULONG_PTR)&overlapped, &iosb, NULL );
246 if (res)
248 ERR("NtRemoveIoCompletion failed: 0x%x\n", res);
250 else
252 DWORD transferred = 0;
253 DWORD err = 0;
255 if (iosb.u.Status == STATUS_SUCCESS)
256 transferred = iosb.Information;
257 else
258 err = RtlNtStatusToDosError(iosb.u.Status);
260 callback( err, transferred, overlapped );
263 return 0;
266 /***********************************************************************
267 * RtlSetIoCompletionCallback (NTDLL.@)
269 * Binds a handle to a thread pool's completion port, and possibly
270 * starts a non-I/O thread to monitor this port and call functions back.
272 * PARAMS
273 * FileHandle [I] Handle to bind to a completion port.
274 * Function [I] Callback function to call on I/O completions.
275 * Flags [I] Not used.
277 * RETURNS
278 * Success: STATUS_SUCCESS.
279 * Failure: Any NTSTATUS code.
282 NTSTATUS WINAPI RtlSetIoCompletionCallback(HANDLE FileHandle, PRTL_OVERLAPPED_COMPLETION_ROUTINE Function, ULONG Flags)
284 IO_STATUS_BLOCK iosb;
285 FILE_COMPLETION_INFORMATION info;
287 if (Flags) FIXME("Unknown value Flags=0x%x\n", Flags);
289 if (!compl_port)
291 NTSTATUS res = STATUS_SUCCESS;
293 RtlEnterCriticalSection(&threadpool_compl_cs);
294 if (!compl_port)
296 HANDLE cport;
298 res = NtCreateIoCompletion( &cport, IO_COMPLETION_ALL_ACCESS, NULL, 0 );
299 if (!res)
301 /* FIXME native can start additional threads in case of e.g. hung callback function. */
302 res = RtlQueueWorkItem( iocp_poller, cport, WT_EXECUTEDEFAULT );
303 if (!res)
304 compl_port = cport;
305 else
306 NtClose( cport );
309 RtlLeaveCriticalSection(&threadpool_compl_cs);
310 if (res) return res;
313 info.CompletionPort = compl_port;
314 info.CompletionKey = (ULONG_PTR)Function;
316 return NtSetInformationFile( FileHandle, &iosb, &info, sizeof(info), FileCompletionInformation );
319 static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
321 if (timeout == INFINITE) return NULL;
322 pTime->QuadPart = (ULONGLONG)timeout * -10000;
323 return pTime;
326 struct wait_work_item
328 HANDLE Object;
329 HANDLE CancelEvent;
330 WAITORTIMERCALLBACK Callback;
331 PVOID Context;
332 ULONG Milliseconds;
333 ULONG Flags;
334 HANDLE CompletionEvent;
335 LONG DeleteCount;
336 BOOLEAN CallbackInProgress;
339 static void delete_wait_work_item(struct wait_work_item *wait_work_item)
341 NtClose( wait_work_item->CancelEvent );
342 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
345 static DWORD CALLBACK wait_thread_proc(LPVOID Arg)
347 struct wait_work_item *wait_work_item = Arg;
348 NTSTATUS status;
349 BOOLEAN alertable = (wait_work_item->Flags & WT_EXECUTEINIOTHREAD) != 0;
350 HANDLE handles[2] = { wait_work_item->Object, wait_work_item->CancelEvent };
351 LARGE_INTEGER timeout;
352 HANDLE completion_event;
354 TRACE("\n");
356 while (TRUE)
358 status = NtWaitForMultipleObjects( 2, handles, FALSE, alertable,
359 get_nt_timeout( &timeout, wait_work_item->Milliseconds ) );
360 if (status == STATUS_WAIT_0 || status == STATUS_TIMEOUT)
362 BOOLEAN TimerOrWaitFired;
364 if (status == STATUS_WAIT_0)
366 TRACE( "object %p signaled, calling callback %p with context %p\n",
367 wait_work_item->Object, wait_work_item->Callback,
368 wait_work_item->Context );
369 TimerOrWaitFired = FALSE;
371 else
373 TRACE( "wait for object %p timed out, calling callback %p with context %p\n",
374 wait_work_item->Object, wait_work_item->Callback,
375 wait_work_item->Context );
376 TimerOrWaitFired = TRUE;
378 wait_work_item->CallbackInProgress = TRUE;
379 wait_work_item->Callback( wait_work_item->Context, TimerOrWaitFired );
380 wait_work_item->CallbackInProgress = FALSE;
382 if (wait_work_item->Flags & WT_EXECUTEONLYONCE)
383 break;
385 else
386 break;
389 completion_event = wait_work_item->CompletionEvent;
390 if (completion_event) NtSetEvent( completion_event, NULL );
392 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
393 delete_wait_work_item( wait_work_item );
395 return 0;
398 /***********************************************************************
399 * RtlRegisterWait (NTDLL.@)
401 * Registers a wait for a handle to become signaled.
403 * PARAMS
404 * NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it.
405 * Object [I] Object to wait to become signaled.
406 * Callback [I] Callback function to execute when the wait times out or the handle is signaled.
407 * Context [I] Context to pass to the callback function when it is executed.
408 * Milliseconds [I] Number of milliseconds to wait before timing out.
409 * Flags [I] Flags. See notes.
411 * RETURNS
412 * Success: STATUS_SUCCESS.
413 * Failure: Any NTSTATUS code.
415 * NOTES
416 * Flags can be one or more of the following:
417 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
418 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
419 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
420 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
421 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
423 NTSTATUS WINAPI RtlRegisterWait(PHANDLE NewWaitObject, HANDLE Object,
424 RTL_WAITORTIMERCALLBACKFUNC Callback,
425 PVOID Context, ULONG Milliseconds, ULONG Flags)
427 struct wait_work_item *wait_work_item;
428 NTSTATUS status;
430 TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags );
432 wait_work_item = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*wait_work_item) );
433 if (!wait_work_item)
434 return STATUS_NO_MEMORY;
436 wait_work_item->Object = Object;
437 wait_work_item->Callback = Callback;
438 wait_work_item->Context = Context;
439 wait_work_item->Milliseconds = Milliseconds;
440 wait_work_item->Flags = Flags;
441 wait_work_item->CallbackInProgress = FALSE;
442 wait_work_item->DeleteCount = 0;
443 wait_work_item->CompletionEvent = NULL;
445 status = NtCreateEvent( &wait_work_item->CancelEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE );
446 if (status != STATUS_SUCCESS)
448 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
449 return status;
452 Flags = Flags & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD |
453 WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION);
454 status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags );
455 if (status != STATUS_SUCCESS)
457 delete_wait_work_item( wait_work_item );
458 return status;
461 *NewWaitObject = wait_work_item;
462 return status;
465 /***********************************************************************
466 * RtlDeregisterWaitEx (NTDLL.@)
468 * Cancels a wait operation and frees the resources associated with calling
469 * RtlRegisterWait().
471 * PARAMS
472 * WaitObject [I] Handle to the wait object to free.
474 * RETURNS
475 * Success: STATUS_SUCCESS.
476 * Failure: Any NTSTATUS code.
478 NTSTATUS WINAPI RtlDeregisterWaitEx(HANDLE WaitHandle, HANDLE CompletionEvent)
480 struct wait_work_item *wait_work_item = WaitHandle;
481 NTSTATUS status = STATUS_SUCCESS;
483 TRACE( "(%p)\n", WaitHandle );
485 NtSetEvent( wait_work_item->CancelEvent, NULL );
486 if (wait_work_item->CallbackInProgress)
488 if (CompletionEvent != NULL)
490 if (CompletionEvent == INVALID_HANDLE_VALUE)
492 status = NtCreateEvent( &CompletionEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE );
493 if (status != STATUS_SUCCESS)
494 return status;
495 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
496 if (wait_work_item->CallbackInProgress)
497 NtWaitForSingleObject( CompletionEvent, FALSE, NULL );
498 NtClose( CompletionEvent );
500 else
502 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
503 if (wait_work_item->CallbackInProgress)
504 status = STATUS_PENDING;
507 else
508 status = STATUS_PENDING;
511 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
513 status = STATUS_SUCCESS;
514 delete_wait_work_item( wait_work_item );
517 return status;
520 /***********************************************************************
521 * RtlDeregisterWait (NTDLL.@)
523 * Cancels a wait operation and frees the resources associated with calling
524 * RtlRegisterWait().
526 * PARAMS
527 * WaitObject [I] Handle to the wait object to free.
529 * RETURNS
530 * Success: STATUS_SUCCESS.
531 * Failure: Any NTSTATUS code.
533 NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle)
535 return RtlDeregisterWaitEx(WaitHandle, NULL);
539 /************************** Timer Queue Impl **************************/
541 struct timer_queue;
542 struct queue_timer
544 struct timer_queue *q;
545 struct list entry;
546 ULONG runcount; /* number of callbacks pending execution */
547 RTL_WAITORTIMERCALLBACKFUNC callback;
548 PVOID param;
549 DWORD period;
550 ULONG flags;
551 ULONGLONG expire;
552 BOOL destroy; /* timer should be deleted; once set, never unset */
553 HANDLE event; /* removal event */
556 struct timer_queue
558 DWORD magic;
559 RTL_CRITICAL_SECTION cs;
560 struct list timers; /* sorted by expiration time */
561 BOOL quit; /* queue should be deleted; once set, never unset */
562 HANDLE event;
563 HANDLE thread;
566 #define EXPIRE_NEVER (~(ULONGLONG) 0)
567 #define TIMER_QUEUE_MAGIC 0x516d6954 /* TimQ */
569 static void queue_remove_timer(struct queue_timer *t)
571 /* We MUST hold the queue cs while calling this function. This ensures
572 that we cannot queue another callback for this timer. The runcount
573 being zero makes sure we don't have any already queued. */
574 struct timer_queue *q = t->q;
576 assert(t->runcount == 0);
577 assert(t->destroy);
579 list_remove(&t->entry);
580 if (t->event)
581 NtSetEvent(t->event, NULL);
582 RtlFreeHeap(GetProcessHeap(), 0, t);
584 if (q->quit && list_empty(&q->timers))
585 NtSetEvent(q->event, NULL);
588 static void timer_cleanup_callback(struct queue_timer *t)
590 struct timer_queue *q = t->q;
591 RtlEnterCriticalSection(&q->cs);
593 assert(0 < t->runcount);
594 --t->runcount;
596 if (t->destroy && t->runcount == 0)
597 queue_remove_timer(t);
599 RtlLeaveCriticalSection(&q->cs);
602 static DWORD WINAPI timer_callback_wrapper(LPVOID p)
604 struct queue_timer *t = p;
605 t->callback(t->param, TRUE);
606 timer_cleanup_callback(t);
607 return 0;
610 static inline ULONGLONG queue_current_time(void)
612 LARGE_INTEGER now, freq;
613 NtQueryPerformanceCounter(&now, &freq);
614 return now.QuadPart * 1000 / freq.QuadPart;
617 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
618 BOOL set_event)
620 /* We MUST hold the queue cs while calling this function. */
621 struct timer_queue *q = t->q;
622 struct list *ptr = &q->timers;
624 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
626 if (time != EXPIRE_NEVER)
627 LIST_FOR_EACH(ptr, &q->timers)
629 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
630 if (time < cur->expire)
631 break;
633 list_add_before(ptr, &t->entry);
635 t->expire = time;
637 /* If we insert at the head of the list, we need to expire sooner
638 than expected. */
639 if (set_event && &t->entry == list_head(&q->timers))
640 NtSetEvent(q->event, NULL);
643 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
644 BOOL set_event)
646 /* We MUST hold the queue cs while calling this function. */
647 list_remove(&t->entry);
648 queue_add_timer(t, time, set_event);
651 static void queue_timer_expire(struct timer_queue *q)
653 struct queue_timer *t = NULL;
655 RtlEnterCriticalSection(&q->cs);
656 if (list_head(&q->timers))
658 ULONGLONG now, next;
659 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
660 if (!t->destroy && t->expire <= ((now = queue_current_time())))
662 ++t->runcount;
663 if (t->period)
665 next = t->expire + t->period;
666 /* avoid trigger cascade if overloaded / hibernated */
667 if (next < now)
668 next = now + t->period;
670 else
671 next = EXPIRE_NEVER;
672 queue_move_timer(t, next, FALSE);
674 else
675 t = NULL;
677 RtlLeaveCriticalSection(&q->cs);
679 if (t)
681 if (t->flags & WT_EXECUTEINTIMERTHREAD)
682 timer_callback_wrapper(t);
683 else
685 ULONG flags
686 = (t->flags
687 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
688 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
689 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
690 if (status != STATUS_SUCCESS)
691 timer_cleanup_callback(t);
696 static ULONG queue_get_timeout(struct timer_queue *q)
698 struct queue_timer *t;
699 ULONG timeout = INFINITE;
701 RtlEnterCriticalSection(&q->cs);
702 if (list_head(&q->timers))
704 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
705 assert(!t->destroy || t->expire == EXPIRE_NEVER);
707 if (t->expire != EXPIRE_NEVER)
709 ULONGLONG time = queue_current_time();
710 timeout = t->expire < time ? 0 : t->expire - time;
713 RtlLeaveCriticalSection(&q->cs);
715 return timeout;
718 static void WINAPI timer_queue_thread_proc(LPVOID p)
720 struct timer_queue *q = p;
721 ULONG timeout_ms;
723 timeout_ms = INFINITE;
724 for (;;)
726 LARGE_INTEGER timeout;
727 NTSTATUS status;
728 BOOL done = FALSE;
730 status = NtWaitForSingleObject(
731 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
733 if (status == STATUS_WAIT_0)
735 /* There are two possible ways to trigger the event. Either
736 we are quitting and the last timer got removed, or a new
737 timer got put at the head of the list so we need to adjust
738 our timeout. */
739 RtlEnterCriticalSection(&q->cs);
740 if (q->quit && list_empty(&q->timers))
741 done = TRUE;
742 RtlLeaveCriticalSection(&q->cs);
744 else if (status == STATUS_TIMEOUT)
745 queue_timer_expire(q);
747 if (done)
748 break;
750 timeout_ms = queue_get_timeout(q);
753 NtClose(q->event);
754 RtlDeleteCriticalSection(&q->cs);
755 q->magic = 0;
756 RtlFreeHeap(GetProcessHeap(), 0, q);
759 static void queue_destroy_timer(struct queue_timer *t)
761 /* We MUST hold the queue cs while calling this function. */
762 t->destroy = TRUE;
763 if (t->runcount == 0)
764 /* Ensure a timer is promptly removed. If callbacks are pending,
765 it will be removed after the last one finishes by the callback
766 cleanup wrapper. */
767 queue_remove_timer(t);
768 else
769 /* Make sure no destroyed timer masks an active timer at the head
770 of the sorted list. */
771 queue_move_timer(t, EXPIRE_NEVER, FALSE);
774 /***********************************************************************
775 * RtlCreateTimerQueue (NTDLL.@)
777 * Creates a timer queue object and returns a handle to it.
779 * PARAMS
780 * NewTimerQueue [O] The newly created queue.
782 * RETURNS
783 * Success: STATUS_SUCCESS.
784 * Failure: Any NTSTATUS code.
786 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
788 NTSTATUS status;
789 struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
790 if (!q)
791 return STATUS_NO_MEMORY;
793 RtlInitializeCriticalSection(&q->cs);
794 list_init(&q->timers);
795 q->quit = FALSE;
796 q->magic = TIMER_QUEUE_MAGIC;
797 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
798 if (status != STATUS_SUCCESS)
800 RtlFreeHeap(GetProcessHeap(), 0, q);
801 return status;
803 status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
804 timer_queue_thread_proc, q, &q->thread, NULL);
805 if (status != STATUS_SUCCESS)
807 NtClose(q->event);
808 RtlFreeHeap(GetProcessHeap(), 0, q);
809 return status;
812 *NewTimerQueue = q;
813 return STATUS_SUCCESS;
816 /***********************************************************************
817 * RtlDeleteTimerQueueEx (NTDLL.@)
819 * Deletes a timer queue object.
821 * PARAMS
822 * TimerQueue [I] The timer queue to destroy.
823 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
824 * wait until all timers are finished firing before
825 * returning. Otherwise, return immediately and set the
826 * event when all timers are done.
828 * RETURNS
829 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
830 * Failure: Any NTSTATUS code.
832 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
834 struct timer_queue *q = TimerQueue;
835 struct queue_timer *t, *temp;
836 HANDLE thread;
837 NTSTATUS status;
839 if (!q || q->magic != TIMER_QUEUE_MAGIC)
840 return STATUS_INVALID_HANDLE;
842 thread = q->thread;
844 RtlEnterCriticalSection(&q->cs);
845 q->quit = TRUE;
846 if (list_head(&q->timers))
847 /* When the last timer is removed, it will signal the timer thread to
848 exit... */
849 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
850 queue_destroy_timer(t);
851 else
852 /* However if we have none, we must do it ourselves. */
853 NtSetEvent(q->event, NULL);
854 RtlLeaveCriticalSection(&q->cs);
856 if (CompletionEvent == INVALID_HANDLE_VALUE)
858 NtWaitForSingleObject(thread, FALSE, NULL);
859 status = STATUS_SUCCESS;
861 else
863 if (CompletionEvent)
865 FIXME("asynchronous return on completion event unimplemented\n");
866 NtWaitForSingleObject(thread, FALSE, NULL);
867 NtSetEvent(CompletionEvent, NULL);
869 status = STATUS_PENDING;
872 NtClose(thread);
873 return status;
876 static struct timer_queue *default_timer_queue;
878 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
880 if (TimerQueue)
881 return TimerQueue;
882 else
884 if (!default_timer_queue)
886 HANDLE q;
887 NTSTATUS status = RtlCreateTimerQueue(&q);
888 if (status == STATUS_SUCCESS)
890 PVOID p = interlocked_cmpxchg_ptr(
891 (void **) &default_timer_queue, q, NULL);
892 if (p)
893 /* Got beat to the punch. */
894 RtlDeleteTimerQueueEx(q, NULL);
897 return default_timer_queue;
901 /***********************************************************************
902 * RtlCreateTimer (NTDLL.@)
904 * Creates a new timer associated with the given queue.
906 * PARAMS
907 * NewTimer [O] The newly created timer.
908 * TimerQueue [I] The queue to hold the timer.
909 * Callback [I] The callback to fire.
910 * Parameter [I] The argument for the callback.
911 * DueTime [I] The delay, in milliseconds, before first firing the
912 * timer.
913 * Period [I] The period, in milliseconds, at which to fire the timer
914 * after the first callback. If zero, the timer will only
915 * fire once. It still needs to be deleted with
916 * RtlDeleteTimer.
917 * Flags [I] Flags controlling the execution of the callback. In
918 * addition to the WT_* thread pool flags (see
919 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
920 * WT_EXECUTEONLYONCE are supported.
922 * RETURNS
923 * Success: STATUS_SUCCESS.
924 * Failure: Any NTSTATUS code.
926 NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
927 RTL_WAITORTIMERCALLBACKFUNC Callback,
928 PVOID Parameter, DWORD DueTime, DWORD Period,
929 ULONG Flags)
931 NTSTATUS status;
932 struct queue_timer *t;
933 struct timer_queue *q = get_timer_queue(TimerQueue);
935 if (!q) return STATUS_NO_MEMORY;
936 if (q->magic != TIMER_QUEUE_MAGIC) return STATUS_INVALID_HANDLE;
938 t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t);
939 if (!t)
940 return STATUS_NO_MEMORY;
942 t->q = q;
943 t->runcount = 0;
944 t->callback = Callback;
945 t->param = Parameter;
946 t->period = Period;
947 t->flags = Flags;
948 t->destroy = FALSE;
949 t->event = NULL;
951 status = STATUS_SUCCESS;
952 RtlEnterCriticalSection(&q->cs);
953 if (q->quit)
954 status = STATUS_INVALID_HANDLE;
955 else
956 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
957 RtlLeaveCriticalSection(&q->cs);
959 if (status == STATUS_SUCCESS)
960 *NewTimer = t;
961 else
962 RtlFreeHeap(GetProcessHeap(), 0, t);
964 return status;
967 /***********************************************************************
968 * RtlUpdateTimer (NTDLL.@)
970 * Changes the time at which a timer expires.
972 * PARAMS
973 * TimerQueue [I] The queue that holds the timer.
974 * Timer [I] The timer to update.
975 * DueTime [I] The delay, in milliseconds, before next firing the timer.
976 * Period [I] The period, in milliseconds, at which to fire the timer
977 * after the first callback. If zero, the timer will not
978 * refire once. It still needs to be deleted with
979 * RtlDeleteTimer.
981 * RETURNS
982 * Success: STATUS_SUCCESS.
983 * Failure: Any NTSTATUS code.
985 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
986 DWORD DueTime, DWORD Period)
988 struct queue_timer *t = Timer;
989 struct timer_queue *q = t->q;
991 RtlEnterCriticalSection(&q->cs);
992 /* Can't change a timer if it was once-only or destroyed. */
993 if (t->expire != EXPIRE_NEVER)
995 t->period = Period;
996 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
998 RtlLeaveCriticalSection(&q->cs);
1000 return STATUS_SUCCESS;
1003 /***********************************************************************
1004 * RtlDeleteTimer (NTDLL.@)
1006 * Cancels a timer-queue timer.
1008 * PARAMS
1009 * TimerQueue [I] The queue that holds the timer.
1010 * Timer [I] The timer to update.
1011 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
1012 * wait until the timer is finished firing all pending
1013 * callbacks before returning. Otherwise, return
1014 * immediately and set the timer is done.
1016 * RETURNS
1017 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
1018 or if the completion event is NULL.
1019 * Failure: Any NTSTATUS code.
1021 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
1022 HANDLE CompletionEvent)
1024 struct queue_timer *t = Timer;
1025 struct timer_queue *q;
1026 NTSTATUS status = STATUS_PENDING;
1027 HANDLE event = NULL;
1029 if (!Timer)
1030 return STATUS_INVALID_PARAMETER_1;
1031 q = t->q;
1032 if (CompletionEvent == INVALID_HANDLE_VALUE)
1034 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
1035 if (status == STATUS_SUCCESS)
1036 status = STATUS_PENDING;
1038 else if (CompletionEvent)
1039 event = CompletionEvent;
1041 RtlEnterCriticalSection(&q->cs);
1042 t->event = event;
1043 if (t->runcount == 0 && event)
1044 status = STATUS_SUCCESS;
1045 queue_destroy_timer(t);
1046 RtlLeaveCriticalSection(&q->cs);
1048 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
1050 if (status == STATUS_PENDING)
1052 NtWaitForSingleObject(event, FALSE, NULL);
1053 status = STATUS_SUCCESS;
1055 NtClose(event);
1058 return status;