1 /*
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
6 * PROGRAMMER:
7 */
8
9 /* INCLUDES *****************************************************************/
10
11 #include <rtl.h>
12
13 #define NDEBUG
14 #include <debug.h>
15
16 #undef LIST_FOR_EACH
17 #undef LIST_FOR_EACH_SAFE
18 #include <wine/list.h>
19
20 /* FUNCTIONS ***************************************************************/
21
22 extern PRTL_START_POOL_THREAD RtlpStartThreadFunc;
23 extern PRTL_EXIT_POOL_THREAD RtlpExitThreadFunc;
24 HANDLE TimerThreadHandle = NULL;
25
26 NTSTATUS
RtlpInitializeTimerThread(VOID)27 RtlpInitializeTimerThread(VOID)
28 {
29 return STATUS_NOT_IMPLEMENTED;
30 }
31
get_nt_timeout(PLARGE_INTEGER pTime,ULONG timeout)32 static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
33 {
34 if (timeout == INFINITE) return NULL;
35 pTime->QuadPart = (ULONGLONG)timeout * -10000;
36 return pTime;
37 }
38
39 struct timer_queue;
40 struct queue_timer
41 {
42 struct timer_queue *q;
43 struct list entry;
44 ULONG runcount; /* number of callbacks pending execution */
45 WAITORTIMERCALLBACKFUNC callback;
46 PVOID param;
47 DWORD period;
48 ULONG flags;
49 ULONGLONG expire;
50 BOOL destroy; /* timer should be deleted; once set, never unset */
51 HANDLE event; /* removal event */
52 };
53
54 struct timer_queue
55 {
56 DWORD magic;
57 RTL_CRITICAL_SECTION cs;
58 struct list timers; /* sorted by expiration time */
59 BOOL quit; /* queue should be deleted; once set, never unset */
60 HANDLE event;
61 HANDLE thread;
62 };
63
64 #define EXPIRE_NEVER (~(ULONGLONG) 0)
65 #define TIMER_QUEUE_MAGIC 0x516d6954 /* TimQ */
66
queue_remove_timer(struct queue_timer * t)67 static void queue_remove_timer(struct queue_timer *t)
68 {
69 /* We MUST hold the queue cs while calling this function. This ensures
70 that we cannot queue another callback for this timer. The runcount
71 being zero makes sure we don't have any already queued. */
72 struct timer_queue *q = t->q;
73
74 assert(t->runcount == 0);
75 assert(t->destroy);
76
77 list_remove(&t->entry);
78 if (t->event)
79 NtSetEvent(t->event, NULL);
80 RtlFreeHeap(RtlGetProcessHeap(), 0, t);
81
82 if (q->quit && list_empty(&q->timers))
83 NtSetEvent(q->event, NULL);
84 }
85
timer_cleanup_callback(struct queue_timer * t)86 static void timer_cleanup_callback(struct queue_timer *t)
87 {
88 struct timer_queue *q = t->q;
89 RtlEnterCriticalSection(&q->cs);
90
91 assert(0 < t->runcount);
92 --t->runcount;
93
94 if (t->destroy && t->runcount == 0)
95 queue_remove_timer(t);
96
97 RtlLeaveCriticalSection(&q->cs);
98 }
99
timer_callback_wrapper(LPVOID p)100 static VOID WINAPI timer_callback_wrapper(LPVOID p)
101 {
102 struct queue_timer *t = p;
103 t->callback(t->param, TRUE);
104 timer_cleanup_callback(t);
105 }
106
queue_current_time(void)107 static inline ULONGLONG queue_current_time(void)
108 {
109 LARGE_INTEGER now, freq;
110 NtQueryPerformanceCounter(&now, &freq);
111 return now.QuadPart * 1000 / freq.QuadPart;
112 }
113
queue_add_timer(struct queue_timer * t,ULONGLONG time,BOOL set_event)114 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
115 BOOL set_event)
116 {
117 /* We MUST hold the queue cs while calling this function. */
118 struct timer_queue *q = t->q;
119 struct list *ptr = &q->timers;
120
121 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
122
123 if (time != EXPIRE_NEVER)
124 LIST_FOR_EACH(ptr, &q->timers)
125 {
126 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
127 if (time < cur->expire)
128 break;
129 }
130 list_add_before(ptr, &t->entry);
131
132 t->expire = time;
133
134 /* If we insert at the head of the list, we need to expire sooner
135 than expected. */
136 if (set_event && &t->entry == list_head(&q->timers))
137 NtSetEvent(q->event, NULL);
138 }
139
queue_move_timer(struct queue_timer * t,ULONGLONG time,BOOL set_event)140 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
141 BOOL set_event)
142 {
143 /* We MUST hold the queue cs while calling this function. */
144 list_remove(&t->entry);
145 queue_add_timer(t, time, set_event);
146 }
147
queue_timer_expire(struct timer_queue * q)148 static void queue_timer_expire(struct timer_queue *q)
149 {
150 struct queue_timer *t = NULL;
151
152 RtlEnterCriticalSection(&q->cs);
153 if (list_head(&q->timers))
154 {
155 ULONGLONG now, next;
156 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
157 if (!t->destroy && t->expire <= ((now = queue_current_time())))
158 {
159 ++t->runcount;
160 if (t->period)
161 {
162 next = t->expire + t->period;
163 /* avoid trigger cascade if overloaded / hibernated */
164 if (next < now)
165 next = now + t->period;
166 }
167 else
168 next = EXPIRE_NEVER;
169 queue_move_timer(t, next, FALSE);
170 }
171 else
172 t = NULL;
173 }
174 RtlLeaveCriticalSection(&q->cs);
175
176 if (t)
177 {
178 if (t->flags & WT_EXECUTEINTIMERTHREAD)
179 timer_callback_wrapper(t);
180 else
181 {
182 ULONG flags
183 = (t->flags
184 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
185 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
186 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
187 if (status != STATUS_SUCCESS)
188 timer_cleanup_callback(t);
189 }
190 }
191 }
192
queue_get_timeout(struct timer_queue * q)193 static ULONG queue_get_timeout(struct timer_queue *q)
194 {
195 struct queue_timer *t;
196 ULONG timeout = INFINITE;
197
198 RtlEnterCriticalSection(&q->cs);
199 if (list_head(&q->timers))
200 {
201 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
202 assert(!t->destroy || t->expire == EXPIRE_NEVER);
203
204 if (t->expire != EXPIRE_NEVER)
205 {
206 ULONGLONG time = queue_current_time();
207 timeout = t->expire < time ? 0 : (ULONG)(t->expire - time);
208 }
209 }
210 RtlLeaveCriticalSection(&q->cs);
211
212 return timeout;
213 }
214
timer_queue_thread_proc(LPVOID p)215 static DWORD WINAPI timer_queue_thread_proc(LPVOID p)
216 {
217 struct timer_queue *q = p;
218 ULONG timeout_ms;
219
220 timeout_ms = INFINITE;
221 for (;;)
222 {
223 LARGE_INTEGER timeout;
224 NTSTATUS status;
225 BOOL done = FALSE;
226
227 status = NtWaitForSingleObject(
228 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
229
230 if (status == STATUS_WAIT_0)
231 {
232 /* There are two possible ways to trigger the event. Either
233 we are quitting and the last timer got removed, or a new
234 timer got put at the head of the list so we need to adjust
235 our timeout. */
236 RtlEnterCriticalSection(&q->cs);
237 if (q->quit && list_empty(&q->timers))
238 done = TRUE;
239 RtlLeaveCriticalSection(&q->cs);
240 }
241 else if (status == STATUS_TIMEOUT)
242 queue_timer_expire(q);
243
244 if (done)
245 break;
246
247 timeout_ms = queue_get_timeout(q);
248 }
249
250 NtClose(q->event);
251 RtlDeleteCriticalSection(&q->cs);
252 q->magic = 0;
253 RtlFreeHeap(RtlGetProcessHeap(), 0, q);
254 RtlpExitThreadFunc(STATUS_SUCCESS);
255 return 0;
256 }
257
queue_destroy_timer(struct queue_timer * t)258 static void queue_destroy_timer(struct queue_timer *t)
259 {
260 /* We MUST hold the queue cs while calling this function. */
261 t->destroy = TRUE;
262 if (t->runcount == 0)
263 /* Ensure a timer is promptly removed. If callbacks are pending,
264 it will be removed after the last one finishes by the callback
265 cleanup wrapper. */
266 queue_remove_timer(t);
267 else
268 /* Make sure no destroyed timer masks an active timer at the head
269 of the sorted list. */
270 queue_move_timer(t, EXPIRE_NEVER, FALSE);
271 }
272
273 /***********************************************************************
274 * RtlCreateTimerQueue (NTDLL.@)
275 *
276 * Creates a timer queue object and returns a handle to it.
277 *
278 * PARAMS
279 * NewTimerQueue [O] The newly created queue.
280 *
281 * RETURNS
282 * Success: STATUS_SUCCESS.
283 * Failure: Any NTSTATUS code.
284 */
RtlCreateTimerQueue(PHANDLE NewTimerQueue)285 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
286 {
287 NTSTATUS status;
288 struct timer_queue *q = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *q);
289 if (!q)
290 return STATUS_NO_MEMORY;
291
292 RtlInitializeCriticalSection(&q->cs);
293 list_init(&q->timers);
294 q->quit = FALSE;
295 q->magic = TIMER_QUEUE_MAGIC;
296 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
297 if (status != STATUS_SUCCESS)
298 {
299 RtlFreeHeap(RtlGetProcessHeap(), 0, q);
300 return status;
301 }
302 status = RtlpStartThreadFunc(timer_queue_thread_proc, q, &q->thread);
303 if (status != STATUS_SUCCESS)
304 {
305 NtClose(q->event);
306 RtlFreeHeap(RtlGetProcessHeap(), 0, q);
307 return status;
308 }
309
310 NtResumeThread(q->thread, NULL);
311 *NewTimerQueue = q;
312 return STATUS_SUCCESS;
313 }
314
315 /***********************************************************************
316 * RtlDeleteTimerQueueEx (NTDLL.@)
317 *
318 * Deletes a timer queue object.
319 *
320 * PARAMS
321 * TimerQueue [I] The timer queue to destroy.
322 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
323 * wait until all timers are finished firing before
324 * returning. Otherwise, return immediately and set the
325 * event when all timers are done.
326 *
327 * RETURNS
328 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
329 * Failure: Any NTSTATUS code.
330 */
RtlDeleteTimerQueueEx(HANDLE TimerQueue,HANDLE CompletionEvent)331 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
332 {
333 struct timer_queue *q = TimerQueue;
334 struct queue_timer *t, *temp;
335 HANDLE thread;
336 NTSTATUS status;
337
338 if (!q || q->magic != TIMER_QUEUE_MAGIC)
339 return STATUS_INVALID_HANDLE;
340
341 thread = q->thread;
342
343 RtlEnterCriticalSection(&q->cs);
344 q->quit = TRUE;
345 if (list_head(&q->timers))
346 /* When the last timer is removed, it will signal the timer thread to
347 exit... */
348 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
349 queue_destroy_timer(t);
350 else
351 /* However if we have none, we must do it ourselves. */
352 NtSetEvent(q->event, NULL);
353 RtlLeaveCriticalSection(&q->cs);
354
355 if (CompletionEvent == INVALID_HANDLE_VALUE)
356 {
357 NtWaitForSingleObject(thread, FALSE, NULL);
358 status = STATUS_SUCCESS;
359 }
360 else
361 {
362 if (CompletionEvent)
363 {
364 DPRINT1("asynchronous return on completion event unimplemented\n");
365 NtWaitForSingleObject(thread, FALSE, NULL);
366 NtSetEvent(CompletionEvent, NULL);
367 }
368 status = STATUS_PENDING;
369 }
370
371 NtClose(thread);
372 return status;
373 }
374
get_timer_queue(HANDLE TimerQueue)375 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
376 {
377 static struct timer_queue *default_timer_queue;
378
379 if (TimerQueue)
380 return TimerQueue;
381 else
382 {
383 if (!default_timer_queue)
384 {
385 HANDLE q;
386 NTSTATUS status = RtlCreateTimerQueue(&q);
387 if (status == STATUS_SUCCESS)
388 {
389 PVOID p = InterlockedCompareExchangePointer(
390 (void **) &default_timer_queue, q, NULL);
391 if (p)
392 /* Got beat to the punch. */
393 RtlDeleteTimerQueueEx(q, NULL);
394 }
395 }
396 return default_timer_queue;
397 }
398 }
399
400 /***********************************************************************
401 * RtlCreateTimer (NTDLL.@)
402 *
403 * Creates a new timer associated with the given queue.
404 *
405 * PARAMS
406 * NewTimer [O] The newly created timer.
407 * TimerQueue [I] The queue to hold the timer.
408 * Callback [I] The callback to fire.
409 * Parameter [I] The argument for the callback.
410 * DueTime [I] The delay, in milliseconds, before first firing the
411 * timer.
412 * Period [I] The period, in milliseconds, at which to fire the timer
413 * after the first callback. If zero, the timer will only
414 * fire once. It still needs to be deleted with
415 * RtlDeleteTimer.
416 * Flags [I] Flags controlling the execution of the callback. In
417 * addition to the WT_* thread pool flags (see
418 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
419 * WT_EXECUTEONLYONCE are supported.
420 *
421 * RETURNS
422 * Success: STATUS_SUCCESS.
423 * Failure: Any NTSTATUS code.
424 */
RtlCreateTimer(HANDLE TimerQueue,PHANDLE NewTimer,WAITORTIMERCALLBACKFUNC Callback,PVOID Parameter,DWORD DueTime,DWORD Period,ULONG Flags)425 NTSTATUS WINAPI RtlCreateTimer(HANDLE TimerQueue, PHANDLE NewTimer,
426 WAITORTIMERCALLBACKFUNC Callback,
427 PVOID Parameter, DWORD DueTime, DWORD Period,
428 ULONG Flags)
429 {
430 NTSTATUS status;
431 struct queue_timer *t;
432 struct timer_queue *q = get_timer_queue(TimerQueue);
433
434 if (!q) return STATUS_NO_MEMORY;
435 if (q->magic != TIMER_QUEUE_MAGIC) return STATUS_INVALID_HANDLE;
436
437 t = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *t);
438 if (!t)
439 return STATUS_NO_MEMORY;
440
441 t->q = q;
442 t->runcount = 0;
443 t->callback = Callback;
444 t->param = Parameter;
445 t->period = Period;
446 t->flags = Flags;
447 t->destroy = FALSE;
448 t->event = NULL;
449
450 status = STATUS_SUCCESS;
451 RtlEnterCriticalSection(&q->cs);
452 if (q->quit)
453 status = STATUS_INVALID_HANDLE;
454 else
455 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
456 RtlLeaveCriticalSection(&q->cs);
457
458 if (status == STATUS_SUCCESS)
459 *NewTimer = t;
460 else
461 RtlFreeHeap(RtlGetProcessHeap(), 0, t);
462
463 return status;
464 }
465
466 NTSTATUS
467 WINAPI
RtlSetTimer(HANDLE TimerQueue,PHANDLE NewTimer,WAITORTIMERCALLBACKFUNC Callback,PVOID Parameter,DWORD DueTime,DWORD Period,ULONG Flags)468 RtlSetTimer(
469 HANDLE TimerQueue,
470 PHANDLE NewTimer,
471 WAITORTIMERCALLBACKFUNC Callback,
472 PVOID Parameter,
473 DWORD DueTime,
474 DWORD Period,
475 ULONG Flags)
476 {
477 return RtlCreateTimer(TimerQueue,
478 NewTimer,
479 Callback,
480 Parameter,
481 DueTime,
482 Period,
483 Flags);
484 }
485
486 /***********************************************************************
487 * RtlUpdateTimer (NTDLL.@)
488 *
489 * Changes the time at which a timer expires.
490 *
491 * PARAMS
492 * TimerQueue [I] The queue that holds the timer.
493 * Timer [I] The timer to update.
494 * DueTime [I] The delay, in milliseconds, before next firing the timer.
495 * Period [I] The period, in milliseconds, at which to fire the timer
496 * after the first callback. If zero, the timer will not
497 * refire once. It still needs to be deleted with
498 * RtlDeleteTimer.
499 *
500 * RETURNS
501 * Success: STATUS_SUCCESS.
502 * Failure: Any NTSTATUS code.
503 */
RtlUpdateTimer(HANDLE TimerQueue,HANDLE Timer,DWORD DueTime,DWORD Period)504 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
505 DWORD DueTime, DWORD Period)
506 {
507 struct queue_timer *t = Timer;
508 struct timer_queue *q = t->q;
509
510 RtlEnterCriticalSection(&q->cs);
511 /* Can't change a timer if it was once-only or destroyed. */
512 if (t->expire != EXPIRE_NEVER)
513 {
514 t->period = Period;
515 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
516 }
517 RtlLeaveCriticalSection(&q->cs);
518
519 return STATUS_SUCCESS;
520 }
521
522 /***********************************************************************
523 * RtlDeleteTimer (NTDLL.@)
524 *
525 * Cancels a timer-queue timer.
526 *
527 * PARAMS
528 * TimerQueue [I] The queue that holds the timer.
529 * Timer [I] The timer to update.
530 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
531 * wait until the timer is finished firing all pending
532 * callbacks before returning. Otherwise, return
533 * immediately and set the timer is done.
534 *
535 * RETURNS
536 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
537 or if the completion event is NULL.
538 * Failure: Any NTSTATUS code.
539 */
RtlDeleteTimer(HANDLE TimerQueue,HANDLE Timer,HANDLE CompletionEvent)540 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
541 HANDLE CompletionEvent)
542 {
543 struct queue_timer *t = Timer;
544 struct timer_queue *q;
545 NTSTATUS status = STATUS_PENDING;
546 HANDLE event = NULL;
547
548 if (!Timer)
549 return STATUS_INVALID_PARAMETER_1;
550 q = t->q;
551 if (CompletionEvent == INVALID_HANDLE_VALUE)
552 {
553 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
554 if (status == STATUS_SUCCESS)
555 status = STATUS_PENDING;
556 }
557 else if (CompletionEvent)
558 event = CompletionEvent;
559
560 RtlEnterCriticalSection(&q->cs);
561 t->event = event;
562 if (t->runcount == 0 && event)
563 status = STATUS_SUCCESS;
564 queue_destroy_timer(t);
565 RtlLeaveCriticalSection(&q->cs);
566
567 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
568 {
569 if (status == STATUS_PENDING)
570 {
571 NtWaitForSingleObject(event, FALSE, NULL);
572 status = STATUS_SUCCESS;
573 }
574 NtClose(event);
575 }
576
577 return status;
578 }
579
580 /*
581 * @implemented
582 */
583 NTSTATUS
584 NTAPI
RtlCancelTimer(HANDLE TimerQueue,HANDLE Timer)585 RtlCancelTimer(HANDLE TimerQueue, HANDLE Timer)
586 {
587 return RtlDeleteTimer(TimerQueue, Timer, NULL);
588 }
589
590 /*
591 * @implemented
592 */
593 NTSTATUS
594 NTAPI
RtlDeleteTimerQueue(HANDLE TimerQueue)595 RtlDeleteTimerQueue(HANDLE TimerQueue)
596 {
597 return RtlDeleteTimerQueueEx(TimerQueue, INVALID_HANDLE_VALUE);
598 }
599
600 /* EOF */
601