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 HANDLE TimerThreadHandle
= NULL
;
25 RtlpInitializeTimerThread(VOID
)
27 return STATUS_NOT_IMPLEMENTED
;
30 static inline PLARGE_INTEGER
get_nt_timeout( PLARGE_INTEGER pTime
, ULONG timeout
)
32 if (timeout
== INFINITE
) return NULL
;
33 pTime
->QuadPart
= (ULONGLONG
)timeout
* -10000;
40 struct timer_queue
*q
;
42 ULONG runcount
; /* number of callbacks pending execution */
43 WAITORTIMERCALLBACKFUNC callback
;
48 BOOL destroy
; /* timer should be deleted; once set, never unset */
49 HANDLE event
; /* removal event */
54 RTL_CRITICAL_SECTION cs
;
55 struct list timers
; /* sorted by expiration time */
56 BOOL quit
; /* queue should be deleted; once set, never unset */
61 #define EXPIRE_NEVER (~(ULONGLONG) 0)
63 static void queue_remove_timer(struct queue_timer
*t
)
65 /* We MUST hold the queue cs while calling this function. This ensures
66 that we cannot queue another callback for this timer. The runcount
67 being zero makes sure we don't have any already queued. */
68 struct timer_queue
*q
= t
->q
;
70 assert(t
->runcount
== 0);
73 list_remove(&t
->entry
);
75 NtSetEvent(t
->event
, NULL
);
76 RtlFreeHeap(RtlGetProcessHeap(), 0, t
);
78 if (q
->quit
&& list_count(&q
->timers
) == 0)
79 NtSetEvent(q
->event
, NULL
);
82 static void timer_cleanup_callback(struct queue_timer
*t
)
84 struct timer_queue
*q
= t
->q
;
85 RtlEnterCriticalSection(&q
->cs
);
87 assert(0 < t
->runcount
);
90 if (t
->destroy
&& t
->runcount
== 0)
91 queue_remove_timer(t
);
93 RtlLeaveCriticalSection(&q
->cs
);
96 static DWORD WINAPI
timer_callback_wrapper(LPVOID p
)
98 struct queue_timer
*t
= p
;
99 t
->callback(t
->param
, TRUE
);
100 timer_cleanup_callback(t
);
104 static inline ULONGLONG
queue_current_time(void)
107 NtQuerySystemTime(&now
);
108 return now
.QuadPart
/ 10000;
111 static void queue_add_timer(struct queue_timer
*t
, ULONGLONG time
,
114 /* We MUST hold the queue cs while calling this function. */
115 struct timer_queue
*q
= t
->q
;
116 struct list
*ptr
= &q
->timers
;
118 assert(!q
->quit
|| (t
->destroy
&& time
== EXPIRE_NEVER
));
120 if (time
!= EXPIRE_NEVER
)
121 LIST_FOR_EACH(ptr
, &q
->timers
)
123 struct queue_timer
*cur
= LIST_ENTRY(ptr
, struct queue_timer
, entry
);
124 if (time
< cur
->expire
)
127 list_add_before(ptr
, &t
->entry
);
131 /* If we insert at the head of the list, we need to expire sooner
133 if (set_event
&& &t
->entry
== list_head(&q
->timers
))
134 NtSetEvent(q
->event
, NULL
);
137 static inline void queue_move_timer(struct queue_timer
*t
, ULONGLONG time
,
140 /* We MUST hold the queue cs while calling this function. */
141 list_remove(&t
->entry
);
142 queue_add_timer(t
, time
, set_event
);
145 static void queue_timer_expire(struct timer_queue
*q
)
147 struct queue_timer
*t
= NULL
;
149 RtlEnterCriticalSection(&q
->cs
);
150 if (list_head(&q
->timers
))
152 t
= LIST_ENTRY(list_head(&q
->timers
), struct queue_timer
, entry
);
153 if (!t
->destroy
&& t
->expire
<= queue_current_time())
157 t
, t
->period
? queue_current_time() + t
->period
: EXPIRE_NEVER
,
163 RtlLeaveCriticalSection(&q
->cs
);
167 if (t
->flags
& WT_EXECUTEINTIMERTHREAD
)
168 timer_callback_wrapper(t
);
173 & (WT_EXECUTEINIOTHREAD
| WT_EXECUTEINPERSISTENTTHREAD
174 | WT_EXECUTELONGFUNCTION
| WT_TRANSFER_IMPERSONATION
));
175 NTSTATUS status
= RtlQueueWorkItem((WORKERCALLBACKFUNC
)timer_callback_wrapper
, t
, flags
);
176 if (status
!= STATUS_SUCCESS
)
177 timer_cleanup_callback(t
);
182 static ULONG
queue_get_timeout(struct timer_queue
*q
)
184 struct queue_timer
*t
;
185 ULONG timeout
= INFINITE
;
187 RtlEnterCriticalSection(&q
->cs
);
188 if (list_head(&q
->timers
))
190 t
= LIST_ENTRY(list_head(&q
->timers
), struct queue_timer
, entry
);
191 assert(!t
->destroy
|| t
->expire
== EXPIRE_NEVER
);
193 if (t
->expire
!= EXPIRE_NEVER
)
195 ULONGLONG time
= queue_current_time();
196 timeout
= t
->expire
< time
? 0 : t
->expire
- time
;
199 RtlLeaveCriticalSection(&q
->cs
);
204 static void WINAPI
timer_queue_thread_proc(LPVOID p
)
206 struct timer_queue
*q
= p
;
209 timeout_ms
= INFINITE
;
212 LARGE_INTEGER timeout
;
216 status
= NtWaitForSingleObject(
217 q
->event
, FALSE
, get_nt_timeout(&timeout
, timeout_ms
));
219 if (status
== STATUS_WAIT_0
)
221 /* There are two possible ways to trigger the event. Either
222 we are quitting and the last timer got removed, or a new
223 timer got put at the head of the list so we need to adjust
225 RtlEnterCriticalSection(&q
->cs
);
226 if (q
->quit
&& list_count(&q
->timers
) == 0)
228 RtlLeaveCriticalSection(&q
->cs
);
230 else if (status
== STATUS_TIMEOUT
)
231 queue_timer_expire(q
);
236 timeout_ms
= queue_get_timeout(q
);
240 RtlDeleteCriticalSection(&q
->cs
);
241 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
244 static void queue_destroy_timer(struct queue_timer
*t
)
246 /* We MUST hold the queue cs while calling this function. */
248 if (t
->runcount
== 0)
249 /* Ensure a timer is promptly removed. If callbacks are pending,
250 it will be removed after the last one finishes by the callback
252 queue_remove_timer(t
);
254 /* Make sure no destroyed timer masks an active timer at the head
255 of the sorted list. */
256 queue_move_timer(t
, EXPIRE_NEVER
, FALSE
);
259 /***********************************************************************
260 * RtlCreateTimerQueue (NTDLL.@)
262 * Creates a timer queue object and returns a handle to it.
265 * NewTimerQueue [O] The newly created queue.
268 * Success: STATUS_SUCCESS.
269 * Failure: Any NTSTATUS code.
271 NTSTATUS WINAPI
RtlCreateTimerQueue(PHANDLE NewTimerQueue
)
274 struct timer_queue
*q
= RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *q
);
276 return STATUS_NO_MEMORY
;
278 RtlInitializeCriticalSection(&q
->cs
);
279 list_init(&q
->timers
);
281 status
= NtCreateEvent(&q
->event
, EVENT_ALL_ACCESS
, NULL
, FALSE
, FALSE
);
282 if (status
!= STATUS_SUCCESS
)
284 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
287 status
= RtlCreateUserThread(NtCurrentProcess(), NULL
, FALSE
, 0, 0, 0,
288 (PTHREAD_START_ROUTINE
)timer_queue_thread_proc
, q
, &q
->thread
, NULL
);
289 if (status
!= STATUS_SUCCESS
)
292 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
297 return STATUS_SUCCESS
;
300 /***********************************************************************
301 * RtlDeleteTimerQueueEx (NTDLL.@)
303 * Deletes a timer queue object.
306 * TimerQueue [I] The timer queue to destroy.
307 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
308 * wait until all timers are finished firing before
309 * returning. Otherwise, return immediately and set the
310 * event when all timers are done.
313 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
314 * Failure: Any NTSTATUS code.
316 NTSTATUS WINAPI
RtlDeleteTimerQueueEx(HANDLE TimerQueue
, HANDLE CompletionEvent
)
318 struct timer_queue
*q
= TimerQueue
;
319 struct queue_timer
*t
, *temp
;
324 return STATUS_INVALID_HANDLE
;
328 RtlEnterCriticalSection(&q
->cs
);
330 if (list_head(&q
->timers
))
331 /* When the last timer is removed, it will signal the timer thread to
333 LIST_FOR_EACH_ENTRY_SAFE(t
, temp
, &q
->timers
, struct queue_timer
, entry
)
334 queue_destroy_timer(t
);
336 /* However if we have none, we must do it ourselves. */
337 NtSetEvent(q
->event
, NULL
);
338 RtlLeaveCriticalSection(&q
->cs
);
340 if (CompletionEvent
== INVALID_HANDLE_VALUE
)
342 NtWaitForSingleObject(thread
, FALSE
, NULL
);
343 status
= STATUS_SUCCESS
;
349 DPRINT1("asynchronous return on completion event unimplemented\n");
350 NtWaitForSingleObject(thread
, FALSE
, NULL
);
351 NtSetEvent(CompletionEvent
, NULL
);
353 status
= STATUS_PENDING
;
360 static struct timer_queue
*default_timer_queue
;
362 static struct timer_queue
*get_timer_queue(HANDLE TimerQueue
)
368 if (!default_timer_queue
)
371 NTSTATUS status
= RtlCreateTimerQueue(&q
);
372 if (status
== STATUS_SUCCESS
)
374 PVOID p
= _InterlockedCompareExchangePointer(
375 (void **) &default_timer_queue
, q
, NULL
);
377 /* Got beat to the punch. */
378 RtlDeleteTimerQueueEx(p
, NULL
);
381 return default_timer_queue
;
385 /***********************************************************************
386 * RtlCreateTimer (NTDLL.@)
388 * Creates a new timer associated with the given queue.
391 * NewTimer [O] The newly created timer.
392 * TimerQueue [I] The queue to hold the timer.
393 * Callback [I] The callback to fire.
394 * Parameter [I] The argument for the callback.
395 * DueTime [I] The delay, in milliseconds, before first firing the
397 * Period [I] The period, in milliseconds, at which to fire the timer
398 * after the first callback. If zero, the timer will only
399 * fire once. It still needs to be deleted with
401 * Flags [I] Flags controling the execution of the callback. In
402 * addition to the WT_* thread pool flags (see
403 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
404 * WT_EXECUTEONLYONCE are supported.
407 * Success: STATUS_SUCCESS.
408 * Failure: Any NTSTATUS code.
410 NTSTATUS WINAPI
RtlCreateTimer(HANDLE TimerQueue
, PHANDLE NewTimer
,
411 WAITORTIMERCALLBACKFUNC Callback
,
412 PVOID Parameter
, DWORD DueTime
, DWORD Period
,
416 struct queue_timer
*t
;
417 struct timer_queue
*q
= get_timer_queue(TimerQueue
);
419 return STATUS_NO_MEMORY
;
421 t
= RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *t
);
423 return STATUS_NO_MEMORY
;
427 t
->callback
= Callback
;
428 t
->param
= Parameter
;
434 status
= STATUS_SUCCESS
;
435 RtlEnterCriticalSection(&q
->cs
);
437 status
= STATUS_INVALID_HANDLE
;
439 queue_add_timer(t
, queue_current_time() + DueTime
, TRUE
);
440 RtlLeaveCriticalSection(&q
->cs
);
442 if (status
== STATUS_SUCCESS
)
445 RtlFreeHeap(RtlGetProcessHeap(), 0, t
);
450 /***********************************************************************
451 * RtlUpdateTimer (NTDLL.@)
453 * Changes the time at which a timer expires.
456 * TimerQueue [I] The queue that holds the timer.
457 * Timer [I] The timer to update.
458 * DueTime [I] The delay, in milliseconds, before next firing the timer.
459 * Period [I] The period, in milliseconds, at which to fire the timer
460 * after the first callback. If zero, the timer will not
461 * refire once. It still needs to be deleted with
465 * Success: STATUS_SUCCESS.
466 * Failure: Any NTSTATUS code.
468 NTSTATUS WINAPI
RtlUpdateTimer(HANDLE TimerQueue
, HANDLE Timer
,
469 DWORD DueTime
, DWORD Period
)
471 struct queue_timer
*t
= Timer
;
472 struct timer_queue
*q
= t
->q
;
474 RtlEnterCriticalSection(&q
->cs
);
475 /* Can't change a timer if it was once-only or destroyed. */
476 if (t
->expire
!= EXPIRE_NEVER
)
479 queue_move_timer(t
, queue_current_time() + DueTime
, TRUE
);
481 RtlLeaveCriticalSection(&q
->cs
);
483 return STATUS_SUCCESS
;
486 /***********************************************************************
487 * RtlDeleteTimer (NTDLL.@)
489 * Cancels a timer-queue timer.
492 * TimerQueue [I] The queue that holds the timer.
493 * Timer [I] The timer to update.
494 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
495 * wait until the timer is finished firing all pending
496 * callbacks before returning. Otherwise, return
497 * immediately and set the timer is done.
500 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
501 or if the completion event is NULL.
502 * Failure: Any NTSTATUS code.
504 NTSTATUS WINAPI
RtlDeleteTimer(HANDLE TimerQueue
, HANDLE Timer
,
505 HANDLE CompletionEvent
)
507 struct queue_timer
*t
= Timer
;
508 struct timer_queue
*q
;
509 NTSTATUS status
= STATUS_PENDING
;
513 return STATUS_INVALID_PARAMETER_1
;
515 if (CompletionEvent
== INVALID_HANDLE_VALUE
)
516 status
= NtCreateEvent(&event
, EVENT_ALL_ACCESS
, NULL
, FALSE
, FALSE
);
517 else if (CompletionEvent
)
518 event
= CompletionEvent
;
520 RtlEnterCriticalSection(&q
->cs
);
522 if (t
->runcount
== 0 && event
)
523 status
= STATUS_SUCCESS
;
524 queue_destroy_timer(t
);
525 RtlLeaveCriticalSection(&q
->cs
);
527 if (CompletionEvent
== INVALID_HANDLE_VALUE
&& event
)
529 if (status
== STATUS_PENDING
)
530 NtWaitForSingleObject(event
, FALSE
, NULL
);
542 RtlDeleteTimerQueue(HANDLE TimerQueue
)
544 return RtlDeleteTimerQueueEx(TimerQueue
, INVALID_HANDLE_VALUE
);