Abandoning silverblade-audio branch.
[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 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 }
243
244 static void queue_destroy_timer(struct queue_timer *t)
245 {
246 /* We MUST hold the queue cs while calling this function. */
247 t->destroy = TRUE;
248 if (t->runcount == 0)
249 /* Ensure a timer is promptly removed. If callbacks are pending,
250 it will be removed after the last one finishes by the callback
251 cleanup wrapper. */
252 queue_remove_timer(t);
253 else
254 /* Make sure no destroyed timer masks an active timer at the head
255 of the sorted list. */
256 queue_move_timer(t, EXPIRE_NEVER, FALSE);
257 }
258
259 /***********************************************************************
260 * RtlCreateTimerQueue (NTDLL.@)
261 *
262 * Creates a timer queue object and returns a handle to it.
263 *
264 * PARAMS
265 * NewTimerQueue [O] The newly created queue.
266 *
267 * RETURNS
268 * Success: STATUS_SUCCESS.
269 * Failure: Any NTSTATUS code.
270 */
271 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
272 {
273 NTSTATUS status;
274 struct timer_queue *q = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *q);
275 if (!q)
276 return STATUS_NO_MEMORY;
277
278 RtlInitializeCriticalSection(&q->cs);
279 list_init(&q->timers);
280 q->quit = FALSE;
281 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, FALSE, FALSE);
282 if (status != STATUS_SUCCESS)
283 {
284 RtlFreeHeap(RtlGetProcessHeap(), 0, q);
285 return status;
286 }
287 status = RtlCreateUserThread(NtCurrentProcess(), NULL, FALSE, 0, 0, 0,
288 (PTHREAD_START_ROUTINE)timer_queue_thread_proc, q, &q->thread, NULL);
289 if (status != STATUS_SUCCESS)
290 {
291 NtClose(q->event);
292 RtlFreeHeap(RtlGetProcessHeap(), 0, q);
293 return status;
294 }
295
296 *NewTimerQueue = q;
297 return STATUS_SUCCESS;
298 }
299
300 /***********************************************************************
301 * RtlDeleteTimerQueueEx (NTDLL.@)
302 *
303 * Deletes a timer queue object.
304 *
305 * PARAMS
306 * TimerQueue [I] The timer queue to destroy.
307 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
308 * wait until all timers are finished firing before
309 * returning. Otherwise, return immediately and set the
310 * event when all timers are done.
311 *
312 * RETURNS
313 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
314 * Failure: Any NTSTATUS code.
315 */
316 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
317 {
318 struct timer_queue *q = TimerQueue;
319 struct queue_timer *t, *temp;
320 HANDLE thread;
321 NTSTATUS status;
322
323 if (!q)
324 return STATUS_INVALID_HANDLE;
325
326 thread = q->thread;
327
328 RtlEnterCriticalSection(&q->cs);
329 q->quit = TRUE;
330 if (list_head(&q->timers))
331 /* When the last timer is removed, it will signal the timer thread to
332 exit... */
333 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
334 queue_destroy_timer(t);
335 else
336 /* However if we have none, we must do it ourselves. */
337 NtSetEvent(q->event, NULL);
338 RtlLeaveCriticalSection(&q->cs);
339
340 if (CompletionEvent == INVALID_HANDLE_VALUE)
341 {
342 NtWaitForSingleObject(thread, FALSE, NULL);
343 status = STATUS_SUCCESS;
344 }
345 else
346 {
347 if (CompletionEvent)
348 {
349 DPRINT1("asynchronous return on completion event unimplemented\n");
350 NtWaitForSingleObject(thread, FALSE, NULL);
351 NtSetEvent(CompletionEvent, NULL);
352 }
353 status = STATUS_PENDING;
354 }
355
356 NtClose(thread);
357 return status;
358 }
359
360 static struct timer_queue *default_timer_queue;
361
362 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
363 {
364 if (TimerQueue)
365 return TimerQueue;
366 else
367 {
368 if (!default_timer_queue)
369 {
370 HANDLE q;
371 NTSTATUS status = RtlCreateTimerQueue(&q);
372 if (status == STATUS_SUCCESS)
373 {
374 PVOID p = _InterlockedCompareExchangePointer(
375 (void **) &default_timer_queue, q, NULL);
376 if (p)
377 /* Got beat to the punch. */
378 RtlDeleteTimerQueueEx(p, NULL);
379 }
380 }
381 return default_timer_queue;
382 }
383 }
384
385 /***********************************************************************
386 * RtlCreateTimer (NTDLL.@)
387 *
388 * Creates a new timer associated with the given queue.
389 *
390 * PARAMS
391 * NewTimer [O] The newly created timer.
392 * TimerQueue [I] The queue to hold the timer.
393 * Callback [I] The callback to fire.
394 * Parameter [I] The argument for the callback.
395 * DueTime [I] The delay, in milliseconds, before first firing the
396 * timer.
397 * Period [I] The period, in milliseconds, at which to fire the timer
398 * after the first callback. If zero, the timer will only
399 * fire once. It still needs to be deleted with
400 * RtlDeleteTimer.
401 * Flags [I] Flags controling the execution of the callback. In
402 * addition to the WT_* thread pool flags (see
403 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
404 * WT_EXECUTEONLYONCE are supported.
405 *
406 * RETURNS
407 * Success: STATUS_SUCCESS.
408 * Failure: Any NTSTATUS code.
409 */
410 NTSTATUS WINAPI RtlCreateTimer(HANDLE TimerQueue, PHANDLE NewTimer,
411 WAITORTIMERCALLBACKFUNC Callback,
412 PVOID Parameter, DWORD DueTime, DWORD Period,
413 ULONG Flags)
414 {
415 NTSTATUS status;
416 struct queue_timer *t;
417 struct timer_queue *q = get_timer_queue(TimerQueue);
418 if (!q)
419 return STATUS_NO_MEMORY;
420
421 t = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof *t);
422 if (!t)
423 return STATUS_NO_MEMORY;
424
425 t->q = q;
426 t->runcount = 0;
427 t->callback = Callback;
428 t->param = Parameter;
429 t->period = Period;
430 t->flags = Flags;
431 t->destroy = FALSE;
432 t->event = NULL;
433
434 status = STATUS_SUCCESS;
435 RtlEnterCriticalSection(&q->cs);
436 if (q->quit)
437 status = STATUS_INVALID_HANDLE;
438 else
439 queue_add_timer(t, queue_current_time() + DueTime, TRUE);
440 RtlLeaveCriticalSection(&q->cs);
441
442 if (status == STATUS_SUCCESS)
443 *NewTimer = t;
444 else
445 RtlFreeHeap(RtlGetProcessHeap(), 0, t);
446
447 return status;
448 }
449
450 /***********************************************************************
451 * RtlUpdateTimer (NTDLL.@)
452 *
453 * Changes the time at which a timer expires.
454 *
455 * PARAMS
456 * TimerQueue [I] The queue that holds the timer.
457 * Timer [I] The timer to update.
458 * DueTime [I] The delay, in milliseconds, before next firing the timer.
459 * Period [I] The period, in milliseconds, at which to fire the timer
460 * after the first callback. If zero, the timer will not
461 * refire once. It still needs to be deleted with
462 * RtlDeleteTimer.
463 *
464 * RETURNS
465 * Success: STATUS_SUCCESS.
466 * Failure: Any NTSTATUS code.
467 */
468 NTSTATUS WINAPI RtlUpdateTimer(HANDLE TimerQueue, HANDLE Timer,
469 DWORD DueTime, DWORD Period)
470 {
471 struct queue_timer *t = Timer;
472 struct timer_queue *q = t->q;
473
474 RtlEnterCriticalSection(&q->cs);
475 /* Can't change a timer if it was once-only or destroyed. */
476 if (t->expire != EXPIRE_NEVER)
477 {
478 t->period = Period;
479 queue_move_timer(t, queue_current_time() + DueTime, TRUE);
480 }
481 RtlLeaveCriticalSection(&q->cs);
482
483 return STATUS_SUCCESS;
484 }
485
486 /***********************************************************************
487 * RtlDeleteTimer (NTDLL.@)
488 *
489 * Cancels a timer-queue timer.
490 *
491 * PARAMS
492 * TimerQueue [I] The queue that holds the timer.
493 * Timer [I] The timer to update.
494 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
495 * wait until the timer is finished firing all pending
496 * callbacks before returning. Otherwise, return
497 * immediately and set the timer is done.
498 *
499 * RETURNS
500 * Success: STATUS_SUCCESS if the timer is done, STATUS_PENDING if not,
501 or if the completion event is NULL.
502 * Failure: Any NTSTATUS code.
503 */
504 NTSTATUS WINAPI RtlDeleteTimer(HANDLE TimerQueue, HANDLE Timer,
505 HANDLE CompletionEvent)
506 {
507 struct queue_timer *t = Timer;
508 struct timer_queue *q;
509 NTSTATUS status = STATUS_PENDING;
510 HANDLE event = NULL;
511
512 if (!Timer)
513 return STATUS_INVALID_PARAMETER_1;
514 q = t->q;
515 if (CompletionEvent == INVALID_HANDLE_VALUE)
516 status = NtCreateEvent(&event, EVENT_ALL_ACCESS, NULL, FALSE, FALSE);
517 else if (CompletionEvent)
518 event = CompletionEvent;
519
520 RtlEnterCriticalSection(&q->cs);
521 t->event = event;
522 if (t->runcount == 0 && event)
523 status = STATUS_SUCCESS;
524 queue_destroy_timer(t);
525 RtlLeaveCriticalSection(&q->cs);
526
527 if (CompletionEvent == INVALID_HANDLE_VALUE && event)
528 {
529 if (status == STATUS_PENDING)
530 NtWaitForSingleObject(event, FALSE, NULL);
531 NtClose(event);
532 }
533
534 return status;
535 }
536
537 /*
538 * @implemented
539 */
540 NTSTATUS
541 NTAPI
542 RtlDeleteTimerQueue(HANDLE TimerQueue)
543 {
544 return RtlDeleteTimerQueueEx(TimerQueue, INVALID_HANDLE_VALUE);
545 }
546
547 /* EOF */