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