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
);
242 RtlExitUserThread(STATUS_SUCCESS
);
245 static void queue_destroy_timer(struct queue_timer
*t
)
247 /* We MUST hold the queue cs while calling this function. */
249 if (t
->runcount
== 0)
250 /* Ensure a timer is promptly removed. If callbacks are pending,
251 it will be removed after the last one finishes by the callback
253 queue_remove_timer(t
);
255 /* Make sure no destroyed timer masks an active timer at the head
256 of the sorted list. */
257 queue_move_timer(t
, EXPIRE_NEVER
, FALSE
);
260 /***********************************************************************
261 * RtlCreateTimerQueue (NTDLL.@)
263 * Creates a timer queue object and returns a handle to it.
266 * NewTimerQueue [O] The newly created queue.
269 * Success: STATUS_SUCCESS.
270 * Failure: Any NTSTATUS code.
272 NTSTATUS WINAPI
RtlCreateTimerQueue(PHANDLE NewTimerQueue
)
275 struct timer_queue
*q
= RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *q
);
277 return STATUS_NO_MEMORY
;
279 RtlInitializeCriticalSection(&q
->cs
);
280 list_init(&q
->timers
);
282 status
= NtCreateEvent(&q
->event
, EVENT_ALL_ACCESS
, NULL
, FALSE
, FALSE
);
283 if (status
!= STATUS_SUCCESS
)
285 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
288 status
= RtlCreateUserThread(NtCurrentProcess(), NULL
, FALSE
, 0, 0, 0,
289 (PTHREAD_START_ROUTINE
)timer_queue_thread_proc
, q
, &q
->thread
, NULL
);
290 if (status
!= STATUS_SUCCESS
)
293 RtlFreeHeap(RtlGetProcessHeap(), 0, q
);
298 return STATUS_SUCCESS
;
301 /***********************************************************************
302 * RtlDeleteTimerQueueEx (NTDLL.@)
304 * Deletes a timer queue object.
307 * TimerQueue [I] The timer queue to destroy.
308 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
309 * wait until all timers are finished firing before
310 * returning. Otherwise, return immediately and set the
311 * event when all timers are done.
314 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
315 * Failure: Any NTSTATUS code.
317 NTSTATUS WINAPI
RtlDeleteTimerQueueEx(HANDLE TimerQueue
, HANDLE CompletionEvent
)
319 struct timer_queue
*q
= TimerQueue
;
320 struct queue_timer
*t
, *temp
;
325 return STATUS_INVALID_HANDLE
;
329 RtlEnterCriticalSection(&q
->cs
);
331 if (list_head(&q
->timers
))
332 /* When the last timer is removed, it will signal the timer thread to
334 LIST_FOR_EACH_ENTRY_SAFE(t
, temp
, &q
->timers
, struct queue_timer
, entry
)
335 queue_destroy_timer(t
);
337 /* However if we have none, we must do it ourselves. */
338 NtSetEvent(q
->event
, NULL
);
339 RtlLeaveCriticalSection(&q
->cs
);
341 if (CompletionEvent
== INVALID_HANDLE_VALUE
)
343 NtWaitForSingleObject(thread
, FALSE
, NULL
);
344 status
= STATUS_SUCCESS
;
350 DPRINT1("asynchronous return on completion event unimplemented\n");
351 NtWaitForSingleObject(thread
, FALSE
, NULL
);
352 NtSetEvent(CompletionEvent
, NULL
);
354 status
= STATUS_PENDING
;
361 static struct timer_queue
*default_timer_queue
;
363 static struct timer_queue
*get_timer_queue(HANDLE TimerQueue
)
369 if (!default_timer_queue
)
372 NTSTATUS status
= RtlCreateTimerQueue(&q
);
373 if (status
== STATUS_SUCCESS
)
375 PVOID p
= InterlockedCompareExchangePointer(
376 (void **) &default_timer_queue
, q
, NULL
);
378 /* Got beat to the punch. */
379 RtlDeleteTimerQueueEx(p
, NULL
);
382 return default_timer_queue
;
386 /***********************************************************************
387 * RtlCreateTimer (NTDLL.@)
389 * Creates a new timer associated with the given queue.
392 * NewTimer [O] The newly created timer.
393 * TimerQueue [I] The queue to hold the timer.
394 * Callback [I] The callback to fire.
395 * Parameter [I] The argument for the callback.
396 * DueTime [I] The delay, in milliseconds, before first firing the
398 * Period [I] The period, in milliseconds, at which to fire the timer
399 * after the first callback. If zero, the timer will only
400 * fire once. It still needs to be deleted with
402 * Flags [I] Flags controling the execution of the callback. In
403 * addition to the WT_* thread pool flags (see
404 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
405 * WT_EXECUTEONLYONCE are supported.
408 * Success: STATUS_SUCCESS.
409 * Failure: Any NTSTATUS code.
411 NTSTATUS WINAPI
RtlCreateTimer(HANDLE TimerQueue
, PHANDLE NewTimer
,
412 WAITORTIMERCALLBACKFUNC Callback
,
413 PVOID Parameter
, DWORD DueTime
, DWORD Period
,
417 struct queue_timer
*t
;
418 struct timer_queue
*q
= get_timer_queue(TimerQueue
);
420 return STATUS_NO_MEMORY
;
422 t
= RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *t
);
424 return STATUS_NO_MEMORY
;
428 t
->callback
= Callback
;
429 t
->param
= Parameter
;
435 status
= STATUS_SUCCESS
;
436 RtlEnterCriticalSection(&q
->cs
);
438 status
= STATUS_INVALID_HANDLE
;
440 queue_add_timer(t
, queue_current_time() + DueTime
, TRUE
);
441 RtlLeaveCriticalSection(&q
->cs
);
443 if (status
== STATUS_SUCCESS
)
446 RtlFreeHeap(RtlGetProcessHeap(), 0, t
);
451 /***********************************************************************
452 * RtlUpdateTimer (NTDLL.@)
454 * Changes the time at which a timer expires.
457 * TimerQueue [I] The queue that holds the timer.
458 * Timer [I] The timer to update.
459 * DueTime [I] The delay, in milliseconds, before next firing the timer.
460 * Period [I] The period, in milliseconds, at which to fire the timer
461 * after the first callback. If zero, the timer will not
462 * refire once. It still needs to be deleted with
466 * Success: STATUS_SUCCESS.
467 * Failure: Any NTSTATUS code.
469 NTSTATUS WINAPI
RtlUpdateTimer(HANDLE TimerQueue
, HANDLE Timer
,
470 DWORD DueTime
, DWORD Period
)
472 struct queue_timer
*t
= Timer
;
473 struct timer_queue
*q
= t
->q
;
475 RtlEnterCriticalSection(&q
->cs
);
476 /* Can't change a timer if it was once-only or destroyed. */
477 if (t
->expire
!= EXPIRE_NEVER
)
480 queue_move_timer(t
, queue_current_time() + DueTime
, TRUE
);
482 RtlLeaveCriticalSection(&q
->cs
);
484 return STATUS_SUCCESS
;
487 /***********************************************************************
488 * RtlDeleteTimer (NTDLL.@)
490 * Cancels a timer-queue timer.
493 * TimerQueue [I] The queue that holds the timer.
494 * Timer [I] The timer to update.
495 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
496 * wait until the timer is finished firing all pending
497 * callbacks before returning. Otherwise, return
498 * immediately and set the timer is done.
501 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
502 or if the completion event is NULL.
503 * Failure: Any NTSTATUS code.
505 NTSTATUS WINAPI
RtlDeleteTimer(HANDLE TimerQueue
, HANDLE Timer
,
506 HANDLE CompletionEvent
)
508 struct queue_timer
*t
= Timer
;
509 struct timer_queue
*q
;
510 NTSTATUS status
= STATUS_PENDING
;
514 return STATUS_INVALID_PARAMETER_1
;
516 if (CompletionEvent
== INVALID_HANDLE_VALUE
)
517 status
= NtCreateEvent(&event
, EVENT_ALL_ACCESS
, NULL
, FALSE
, FALSE
);
518 else if (CompletionEvent
)
519 event
= CompletionEvent
;
521 RtlEnterCriticalSection(&q
->cs
);
523 if (t
->runcount
== 0 && event
)
524 status
= STATUS_SUCCESS
;
525 queue_destroy_timer(t
);
526 RtlLeaveCriticalSection(&q
->cs
);
528 if (CompletionEvent
== INVALID_HANDLE_VALUE
&& event
)
530 if (status
== STATUS_PENDING
)
531 NtWaitForSingleObject(event
, FALSE
, NULL
);
543 RtlDeleteTimerQueue(HANDLE TimerQueue
)
545 return RtlDeleteTimerQueueEx(TimerQueue
, INVALID_HANDLE_VALUE
);