xref: /reactos/sdk/lib/rtl/timerqueue.c (revision 5fa4fd7b)
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