2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * PURPOSE: Timer Queue implementation
5 * FILE: lib/rtl/timerqueue.c
9 /* INCLUDES *****************************************************************/
17 #undef LIST_FOR_EACH_SAFE
18 #include <wine/list.h>
20 /* FUNCTIONS ***************************************************************/
22 typedef VOID (CALLBACK
*WAITORTIMERCALLBACKFUNC
) (PVOID
, BOOLEAN
);
24 HANDLE TimerThreadHandle
= NULL
;
27 RtlpInitializeTimerThread(VOID
)
29 return STATUS_NOT_IMPLEMENTED
;
32 static inline PLARGE_INTEGER
get_nt_timeout( PLARGE_INTEGER pTime
, ULONG timeout
)
34 if (timeout
== INFINITE
) return NULL
;
35 pTime
->QuadPart
= (ULONGLONG
)timeout
* -10000;
42 struct timer_queue
*q
;
44 ULONG runcount
; /* number of callbacks pending execution */
45 WAITORTIMERCALLBACKFUNC callback
;
50 BOOL destroy
; /* timer should be deleted; once set, never unset */
51 HANDLE event
; /* removal event */
56 RTL_CRITICAL_SECTION cs
;
57 struct list timers
; /* sorted by expiration time */
58 BOOL quit
; /* queue should be deleted; once set, never unset */
63 #define EXPIRE_NEVER (~(ULONGLONG) 0)
65 static void queue_remove_timer(struct queue_timer
*t
)
67 /* We MUST hold the queue cs while calling this function. This ensures
68 that we cannot queue another callback for this timer. The runcount
69 being zero makes sure we don't have any already queued. */
70 struct timer_queue
*q
= t
->q
;
72 assert(t
->runcount
== 0);
75 list_remove(&t
->entry
);
77 NtSetEvent(t
->event
, NULL
);
78 RtlFreeHeap(RtlGetProcessHeap(), 0, t
);
80 if (q
->quit
&& list_count(&q
->timers
) == 0)
81 NtSetEvent(q
->event
, NULL
);
84 static void timer_cleanup_callback(struct queue_timer
*t
)
86 struct timer_queue
*q
= t
->q
;
87 RtlEnterCriticalSection(&q
->cs
);
89 assert(0 < t
->runcount
);
92 if (t
->destroy
&& t
->runcount
== 0)
93 queue_remove_timer(t
);
95 RtlLeaveCriticalSection(&q
->cs
);
98 static DWORD WINAPI
timer_callback_wrapper(LPVOID p
)
100 struct queue_timer
*t
= p
;
101 t
->callback(t
->param
, TRUE
);
102 timer_cleanup_callback(t
);
106 static inline ULONGLONG
queue_current_time(void)
109 NtQuerySystemTime(&now
);
110 return now
.QuadPart
/ 10000;
113 static void queue_add_timer(struct queue_timer
*t
, ULONGLONG time
,
116 /* We MUST hold the queue cs while calling this function. */
117 struct timer_queue
*q
= t
->q
;
118 struct list
*ptr
= &q
->timers
;
120 assert(!q
->quit
|| (t
->destroy
&& time
== EXPIRE_NEVER
));
122 if (time
!= EXPIRE_NEVER
)
123 LIST_FOR_EACH(ptr
, &q
->timers
)
125 struct queue_timer
*cur
= LIST_ENTRY(ptr
, struct queue_timer
, entry
);
126 if (time
< cur
->expire
)
129 list_add_before(ptr
, &t
->entry
);
133 /* If we insert at the head of the list, we need to expire sooner
135 if (set_event
&& &t
->entry
== list_head(&q
->timers
))
136 NtSetEvent(q
->event
, NULL
);
139 static inline void queue_move_timer(struct queue_timer
*t
, ULONGLONG time
,
142 /* We MUST hold the queue cs while calling this function. */
143 list_remove(&t
->entry
);
144 queue_add_timer(t
, time
, set_event
);
147 static void queue_timer_expire(struct timer_queue
*q
)
149 struct queue_timer
*t
= NULL
;
151 RtlEnterCriticalSection(&q
->cs
);
152 if (list_head(&q
->timers
))
154 t
= LIST_ENTRY(list_head(&q
->timers
), struct queue_timer
, entry
);
155 if (!t
->destroy
&& t
->expire
<= queue_current_time())
159 t
, t
->period
? queue_current_time() + t
->period
: EXPIRE_NEVER
,
165 RtlLeaveCriticalSection(&q
->cs
);
169 if (t
->flags
& WT_EXECUTEINTIMERTHREAD
)
170 timer_callback_wrapper(t
);
175 & (WT_EXECUTEINIOTHREAD
| WT_EXECUTEINPERSISTENTTHREAD
176 | WT_EXECUTELONGFUNCTION
| WT_TRANSFER_IMPERSONATION
));
177 NTSTATUS status
= RtlQueueWorkItem((WORKERCALLBACKFUNC
)timer_callback_wrapper
, t
, flags
);
178 if (status
!= STATUS_SUCCESS
)
179 timer_cleanup_callback(t
);
184 static ULONG
queue_get_timeout(struct timer_queue
*q
)
186 struct queue_timer
*t
;
187 ULONG timeout
= INFINITE
;
189 RtlEnterCriticalSection(&q
->cs
);
190 if (list_head(&q
->timers
))
192 t
= LIST_ENTRY(list_head(&q
->timers
), struct queue_timer
, entry
);
193 assert(!t
->destroy
|| t
->expire
== EXPIRE_NEVER
);
195 if (t
->expire
!= EXPIRE_NEVER
)
197 ULONGLONG time
= queue_current_time();
198 timeout
= t
->expire
< time
? 0 : t
->expire
- time
;
201 RtlLeaveCriticalSection(&q
->cs
);
206 static void WINAPI
timer_queue_thread_proc(LPVOID p
)
208 struct timer_queue
*q
= p
;
211 timeout_ms
= INFINITE
;
214 LARGE_INTEGER timeout
;
218 status
= NtWaitForSingleObject(
219 q
->event
, FALSE
, get_nt_timeout(&timeout
, timeout_ms
));
221 if (status
== STATUS_WAIT_0
)
223 /* There are two possible ways to trigger the event. Either
224 we are quitting and the last timer got removed, or a new
225 timer got put at the head of the list so we need to adjust
227 RtlEnterCriticalSection(&q
->cs
);
228 if (q
->quit
&& list_count(&q
->timers
) == 0)
230 RtlLeaveCriticalSection(&q
->cs
);
232 else if (status
== STATUS_TIMEOUT
)
233 queue_timer_expire(q
);
238 timeout_ms
= queue_get_timeout(q
);
242 RtlDeleteCriticalSection(&q
->cs
);
243 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
246 static void queue_destroy_timer(struct queue_timer
*t
)
248 /* We MUST hold the queue cs while calling this function. */
250 if (t
->runcount
== 0)
251 /* Ensure a timer is promptly removed. If callbacks are pending,
252 it will be removed after the last one finishes by the callback
254 queue_remove_timer(t
);
256 /* Make sure no destroyed timer masks an active timer at the head
257 of the sorted list. */
258 queue_move_timer(t
, EXPIRE_NEVER
, FALSE
);
261 /***********************************************************************
262 * RtlCreateTimerQueue (NTDLL.@)
264 * Creates a timer queue object and returns a handle to it.
267 * NewTimerQueue [O] The newly created queue.
270 * Success: STATUS_SUCCESS.
271 * Failure: Any NTSTATUS code.
273 NTSTATUS WINAPI
RtlCreateTimerQueue(PHANDLE NewTimerQueue
)
276 struct timer_queue
*q
= RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *q
);
278 return STATUS_NO_MEMORY
;
280 RtlInitializeCriticalSection(&q
->cs
);
281 list_init(&q
->timers
);
283 status
= NtCreateEvent(&q
->event
, EVENT_ALL_ACCESS
, NULL
, FALSE
, FALSE
);
284 if (status
!= STATUS_SUCCESS
)
286 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
289 status
= RtlCreateUserThread(NtCurrentProcess(), NULL
, FALSE
, 0, 0, 0,
290 (PTHREAD_START_ROUTINE
)timer_queue_thread_proc
, q
, &q
->thread
, NULL
);
291 if (status
!= STATUS_SUCCESS
)
294 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
299 return STATUS_SUCCESS
;
302 /***********************************************************************
303 * RtlDeleteTimerQueueEx (NTDLL.@)
305 * Deletes a timer queue object.
308 * TimerQueue [I] The timer queue to destroy.
309 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
310 * wait until all timers are finished firing before
311 * returning. Otherwise, return immediately and set the
312 * event when all timers are done.
315 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
316 * Failure: Any NTSTATUS code.
318 NTSTATUS WINAPI
RtlDeleteTimerQueueEx(HANDLE TimerQueue
, HANDLE CompletionEvent
)
320 struct timer_queue
*q
= TimerQueue
;
321 struct queue_timer
*t
, *temp
;
326 return STATUS_INVALID_HANDLE
;
330 RtlEnterCriticalSection(&q
->cs
);
332 if (list_head(&q
->timers
))
333 /* When the last timer is removed, it will signal the timer thread to
335 LIST_FOR_EACH_ENTRY_SAFE(t
, temp
, &q
->timers
, struct queue_timer
, entry
)
336 queue_destroy_timer(t
);
338 /* However if we have none, we must do it ourselves. */
339 NtSetEvent(q
->event
, NULL
);
340 RtlLeaveCriticalSection(&q
->cs
);
342 if (CompletionEvent
== INVALID_HANDLE_VALUE
)
344 NtWaitForSingleObject(thread
, FALSE
, NULL
);
345 status
= STATUS_SUCCESS
;
351 DPRINT1("asynchronous return on completion event unimplemented\n");
352 NtWaitForSingleObject(thread
, FALSE
, NULL
);
353 NtSetEvent(CompletionEvent
, NULL
);
355 status
= STATUS_PENDING
;
362 static struct timer_queue
*default_timer_queue
;
364 static struct timer_queue
*get_timer_queue(HANDLE TimerQueue
)
370 if (!default_timer_queue
)
373 NTSTATUS status
= RtlCreateTimerQueue(&q
);
374 if (status
== STATUS_SUCCESS
)
376 PVOID p
= _InterlockedCompareExchangePointer(
377 (void **) &default_timer_queue
, q
, NULL
);
379 /* Got beat to the punch. */
380 RtlDeleteTimerQueueEx(p
, NULL
);
383 return default_timer_queue
;
387 /***********************************************************************
388 * RtlCreateTimer (NTDLL.@)
390 * Creates a new timer associated with the given queue.
393 * NewTimer [O] The newly created timer.
394 * TimerQueue [I] The queue to hold the timer.
395 * Callback [I] The callback to fire.
396 * Parameter [I] The argument for the callback.
397 * DueTime [I] The delay, in milliseconds, before first firing the
399 * Period [I] The period, in milliseconds, at which to fire the timer
400 * after the first callback. If zero, the timer will only
401 * fire once. It still needs to be deleted with
403 * Flags [I] Flags controling the execution of the callback. In
404 * addition to the WT_* thread pool flags (see
405 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
406 * WT_EXECUTEONLYONCE are supported.
409 * Success: STATUS_SUCCESS.
410 * Failure: Any NTSTATUS code.
412 NTSTATUS WINAPI
RtlCreateTimer(HANDLE TimerQueue
, PHANDLE NewTimer
,
413 WAITORTIMERCALLBACKFUNC Callback
,
414 PVOID Parameter
, DWORD DueTime
, DWORD Period
,
418 struct queue_timer
*t
;
419 struct timer_queue
*q
= get_timer_queue(TimerQueue
);
421 return STATUS_NO_MEMORY
;
423 t
= RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *t
);
425 return STATUS_NO_MEMORY
;
429 t
->callback
= Callback
;
430 t
->param
= Parameter
;
436 status
= STATUS_SUCCESS
;
437 RtlEnterCriticalSection(&q
->cs
);
439 status
= STATUS_INVALID_HANDLE
;
441 queue_add_timer(t
, queue_current_time() + DueTime
, TRUE
);
442 RtlLeaveCriticalSection(&q
->cs
);
444 if (status
== STATUS_SUCCESS
)
447 RtlFreeHeap(RtlGetProcessHeap(), 0, t
);
452 /***********************************************************************
453 * RtlUpdateTimer (NTDLL.@)
455 * Changes the time at which a timer expires.
458 * TimerQueue [I] The queue that holds the timer.
459 * Timer [I] The timer to update.
460 * DueTime [I] The delay, in milliseconds, before next firing the timer.
461 * Period [I] The period, in milliseconds, at which to fire the timer
462 * after the first callback. If zero, the timer will not
463 * refire once. It still needs to be deleted with
467 * Success: STATUS_SUCCESS.
468 * Failure: Any NTSTATUS code.
470 NTSTATUS WINAPI
RtlUpdateTimer(HANDLE TimerQueue
, HANDLE Timer
,
471 DWORD DueTime
, DWORD Period
)
473 struct queue_timer
*t
= Timer
;
474 struct timer_queue
*q
= t
->q
;
476 RtlEnterCriticalSection(&q
->cs
);
477 /* Can't change a timer if it was once-only or destroyed. */
478 if (t
->expire
!= EXPIRE_NEVER
)
481 queue_move_timer(t
, queue_current_time() + DueTime
, TRUE
);
483 RtlLeaveCriticalSection(&q
->cs
);
485 return STATUS_SUCCESS
;
488 /***********************************************************************
489 * RtlDeleteTimer (NTDLL.@)
491 * Cancels a timer-queue timer.
494 * TimerQueue [I] The queue that holds the timer.
495 * Timer [I] The timer to update.
496 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
497 * wait until the timer is finished firing all pending
498 * callbacks before returning. Otherwise, return
499 * immediately and set the timer is done.
502 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
503 or if the completion event is NULL.
504 * Failure: Any NTSTATUS code.
506 NTSTATUS WINAPI
RtlDeleteTimer(HANDLE TimerQueue
, HANDLE Timer
,
507 HANDLE CompletionEvent
)
509 struct queue_timer
*t
= Timer
;
510 struct timer_queue
*q
= t
->q
;
511 NTSTATUS status
= STATUS_PENDING
;
514 if (CompletionEvent
== INVALID_HANDLE_VALUE
)
515 status
= NtCreateEvent(&event
, EVENT_ALL_ACCESS
, NULL
, FALSE
, FALSE
);
516 else if (CompletionEvent
)
517 event
= CompletionEvent
;
519 RtlEnterCriticalSection(&q
->cs
);
521 if (t
->runcount
== 0 && event
)
522 status
= STATUS_SUCCESS
;
523 queue_destroy_timer(t
);
524 RtlLeaveCriticalSection(&q
->cs
);
526 if (CompletionEvent
== INVALID_HANDLE_VALUE
&& event
)
528 if (status
== STATUS_PENDING
)
529 NtWaitForSingleObject(event
, FALSE
, NULL
);
541 RtlDeleteTimerQueue(HANDLE TimerQueue
)
543 return RtlDeleteTimerQueueEx(TimerQueue
, INVALID_HANDLE_VALUE
);