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