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