1 /*
2 * Thread pooling
3 *
4 * Copyright (c) 2006 Robert Shearman
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <stdarg.h>
26 #include <limits.h>
27
28 #define NONAMELESSUNION
29 #include "ntstatus.h"
30 #define WIN32_NO_STATUS
31 #include "winternl.h"
32
33 #include "wine/debug.h"
34 #include "wine/list.h"
35
36 #include "ntdll_misc.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(threadpool);
39
40 #define WORKER_TIMEOUT 30000 /* 30 seconds */
41
42 static LONG num_workers;
43 static LONG num_work_items;
44 static LONG num_busy_workers;
45
46 static struct list work_item_list = LIST_INIT(work_item_list);
47 static HANDLE work_item_event;
48
49 static RTL_CRITICAL_SECTION threadpool_cs;
50 static RTL_CRITICAL_SECTION_DEBUG critsect_debug =
51 {
52 0, 0, &threadpool_cs,
53 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
54 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_cs") }
55 };
56 static RTL_CRITICAL_SECTION threadpool_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
57
58 static HANDLE compl_port = NULL;
59 static RTL_CRITICAL_SECTION threadpool_compl_cs;
60 static RTL_CRITICAL_SECTION_DEBUG critsect_compl_debug =
61 {
62 0, 0, &threadpool_compl_cs,
63 { &critsect_compl_debug.ProcessLocksList, &critsect_compl_debug.ProcessLocksList },
64 0, 0, { (DWORD_PTR)(__FILE__ ": threadpool_compl_cs") }
65 };
66 static RTL_CRITICAL_SECTION threadpool_compl_cs = { &critsect_compl_debug, -1, 0, 0, 0, 0 };
67
68 struct work_item
69 {
70 struct list entry;
71 PRTL_WORK_ITEM_ROUTINE function;
72 PVOID context;
73 };
74
75 static inline LONG interlocked_inc( PLONG dest )
76 {
77 return interlocked_xchg_add( dest, 1 ) + 1;
78 }
79
80 static inline LONG interlocked_dec( PLONG dest )
81 {
82 return interlocked_xchg_add( dest, -1 ) - 1;
83 }
84
85 static void WINAPI worker_thread_proc(void * param)
86 {
87 interlocked_inc(&num_workers);
88
89 /* free the work item memory sooner to reduce memory usage */
90 while (TRUE)
91 {
92 if (num_work_items > 0)
93 {
94 struct list *item;
95 RtlEnterCriticalSection(&threadpool_cs);
96 item = list_head(&work_item_list);
97 if (item)
98 {
99 struct work_item *work_item_ptr = LIST_ENTRY(item, struct work_item, entry);
100 struct work_item work_item;
101 list_remove(&work_item_ptr->entry);
102 interlocked_dec(&num_work_items);
103
104 RtlLeaveCriticalSection(&threadpool_cs);
105
106 work_item = *work_item_ptr;
107 RtlFreeHeap(GetProcessHeap(), 0, work_item_ptr);
108
109 TRACE("executing %p(%p)\n", work_item.function, work_item.context);
110
111 interlocked_inc(&num_busy_workers);
112
113 /* do the work */
114 work_item.function(work_item.context);
115
116 interlocked_dec(&num_busy_workers);
117 }
118 else
119 RtlLeaveCriticalSection(&threadpool_cs);
120 }
121 else
122 {
123 NTSTATUS status;
124 LARGE_INTEGER timeout;
125 timeout.QuadPart = -(WORKER_TIMEOUT * (ULONGLONG)10000);
126 status = NtWaitForSingleObject(work_item_event, FALSE, &timeout);
127 if (status != STATUS_WAIT_0)
128 break;
129 }
130 }
131
132 interlocked_dec(&num_workers);
133
134 RtlExitUserThread(0);
135
136 /* never reached */
137 }
138
139 static NTSTATUS add_work_item_to_queue(struct work_item *work_item)
140 {
141 NTSTATUS status;
142
143 RtlEnterCriticalSection(&threadpool_cs);
144 list_add_tail(&work_item_list, &work_item->entry);
145 num_work_items++;
146 RtlLeaveCriticalSection(&threadpool_cs);
147
148 if (!work_item_event)
149 {
150 HANDLE sem;
151 status = NtCreateSemaphore(&sem, SEMAPHORE_ALL_ACCESS, NULL, 1, LONG_MAX);
152 if (interlocked_cmpxchg_ptr( &work_item_event, sem, 0 ))
153 NtClose(sem); /* somebody beat us to it */
154 }
155 else
156 status = NtReleaseSemaphore(work_item_event, 1, NULL);
157
158 return status;
159 }
160
161 /***********************************************************************
162 * RtlQueueWorkItem (NTDLL.@)
163 *
164 * Queues a work item into a thread in the thread pool.
165 *
166 * PARAMS
167 * Function [I] Work function to execute.
168 * Context [I] Context to pass to the work function when it is executed.
169 * Flags [I] Flags. See notes.
170 *
171 * RETURNS
172 * Success: STATUS_SUCCESS.
173 * Failure: Any NTSTATUS code.
174 *
175 * NOTES
176 * Flags can be one or more of the following:
177 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
178 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
179 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
180 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
181 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
182 */
183 NTSTATUS WINAPI RtlQueueWorkItem(PRTL_WORK_ITEM_ROUTINE Function, PVOID Context, ULONG Flags)
184 {
185 HANDLE thread;
186 NTSTATUS status;
187 struct work_item *work_item = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(struct work_item));
188
189 if (!work_item)
190 return STATUS_NO_MEMORY;
191
192 work_item->function = Function;
193 work_item->context = Context;
194
195 if (Flags & ~WT_EXECUTELONGFUNCTION)
196 FIXME("Flags 0x%x not supported\n", Flags);
197
198 status = add_work_item_to_queue(work_item);
199
200 /* FIXME: tune this algorithm to not be as aggressive with creating threads
201 * if WT_EXECUTELONGFUNCTION isn't specified */
202 if ((status == STATUS_SUCCESS) &&
203 ((num_workers == 0) || (num_workers == num_busy_workers)))
204 {
205 status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE,
206 NULL, 0, 0,
207 worker_thread_proc, NULL, &thread, NULL );
208 if (status == STATUS_SUCCESS)
209 NtClose( thread );
210
211 /* NOTE: we don't care if we couldn't create the thread if there is at
212 * least one other available to process the request */
213 if ((num_workers > 0) && (status != STATUS_SUCCESS))
214 status = STATUS_SUCCESS;
215 }
216
217 if (status != STATUS_SUCCESS)
218 {
219 RtlEnterCriticalSection(&threadpool_cs);
220
221 interlocked_dec(&num_work_items);
222 list_remove(&work_item->entry);
223 RtlFreeHeap(GetProcessHeap(), 0, work_item);
224
225 RtlLeaveCriticalSection(&threadpool_cs);
226
227 return status;
228 }
229
230 return STATUS_SUCCESS;
231 }
232
233 /***********************************************************************
234 * iocp_poller - get completion events and run callbacks
235 */
236 static DWORD CALLBACK iocp_poller(LPVOID Arg)
237 {
238 while( TRUE )
239 {
240 PRTL_OVERLAPPED_COMPLETION_ROUTINE callback;
241 LPVOID overlapped;
242 IO_STATUS_BLOCK iosb;
243 NTSTATUS res = NtRemoveIoCompletion( compl_port, (PULONG_PTR)&callback, (PULONG_PTR)&overlapped, &iosb, NULL );
244 if (res)
245 {
246 ERR("NtRemoveIoCompletion failed: 0x%x\n", res);
247 }
248 else
249 {
250 DWORD transferred = 0;
251 DWORD err = 0;
252
253 if (iosb.u.Status == STATUS_SUCCESS)
254 transferred = iosb.Information;
255 else
256 err = RtlNtStatusToDosError(iosb.u.Status);
257
258 callback( err, transferred, overlapped );
259 }
260 }
261 return 0;
262 }
263
264 /***********************************************************************
265 * RtlSetIoCompletionCallback (NTDLL.@)
266 *
267 * Binds a handle to a thread pool's completion port, and possibly
268 * starts a non-I/O thread to monitor this port and call functions back.
269 *
270 * PARAMS
271 * FileHandle [I] Handle to bind to a completion port.
272 * Function [I] Callback function to call on I/O completions.
273 * Flags [I] Not used.
274 *
275 * RETURNS
276 * Success: STATUS_SUCCESS.
277 * Failure: Any NTSTATUS code.
278 *
279 */
280 NTSTATUS WINAPI RtlSetIoCompletionCallback(HANDLE FileHandle, PRTL_OVERLAPPED_COMPLETION_ROUTINE Function, ULONG Flags)
281 {
282 IO_STATUS_BLOCK iosb;
283 FILE_COMPLETION_INFORMATION info;
284
285 if (Flags) FIXME("Unknown value Flags=0x%x\n", Flags);
286
287 if (!compl_port)
288 {
289 NTSTATUS res = STATUS_SUCCESS;
290
291 RtlEnterCriticalSection(&threadpool_compl_cs);
292 if (!compl_port)
293 {
294 HANDLE cport;
295
296 res = NtCreateIoCompletion( &cport, IO_COMPLETION_ALL_ACCESS, NULL, 0 );
297 if (!res)
298 {
299 /* FIXME native can start additional threads in case of e.g. hung callback function. */
300 res = RtlQueueWorkItem( iocp_poller, NULL, WT_EXECUTEDEFAULT );
301 if (!res)
302 compl_port = cport;
303 else
304 NtClose( cport );
305 }
306 }
307 RtlLeaveCriticalSection(&threadpool_compl_cs);
308 if (res) return res;
309 }
310
311 info.CompletionPort = compl_port;
312 info.CompletionKey = (ULONG_PTR)Function;
313
314 return NtSetInformationFile( FileHandle, &iosb, &info, sizeof(info), FileCompletionInformation );
315 }
316
317 static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
318 {
319 if (timeout == INFINITE) return NULL;
320 pTime->QuadPart = (ULONGLONG)timeout * -10000;
321 return pTime;
322 }
323
324 struct wait_work_item
325 {
326 HANDLE Object;
327 HANDLE CancelEvent;
328 WAITORTIMERCALLBACK Callback;
329 PVOID Context;
330 ULONG Milliseconds;
331 ULONG Flags;
332 HANDLE CompletionEvent;
333 LONG DeleteCount;
334 BOOLEAN CallbackInProgress;
335 };
336
337 static void delete_wait_work_item(struct wait_work_item *wait_work_item)
338 {
339 NtClose( wait_work_item->CancelEvent );
340 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
341 }
342
343 static DWORD CALLBACK wait_thread_proc(LPVOID Arg)
344 {
345 struct wait_work_item *wait_work_item = Arg;
346 NTSTATUS status;
347 BOOLEAN alertable = (wait_work_item->Flags & WT_EXECUTEINIOTHREAD) ? TRUE : FALSE;
348 HANDLE handles[2] = { wait_work_item->Object, wait_work_item->CancelEvent };
349 LARGE_INTEGER timeout;
350 HANDLE completion_event;
351
352 TRACE("\n");
353
354 while (TRUE)
355 {
356 status = NtWaitForMultipleObjects( 2, handles, FALSE, alertable,
357 get_nt_timeout( &timeout, wait_work_item->Milliseconds ) );
358 if (status == STATUS_WAIT_0 || status == STATUS_TIMEOUT)
359 {
360 BOOLEAN TimerOrWaitFired;
361
362 if (status == STATUS_WAIT_0)
363 {
364 TRACE( "object %p signaled, calling callback %p with context %p\n",
365 wait_work_item->Object, wait_work_item->Callback,
366 wait_work_item->Context );
367 TimerOrWaitFired = FALSE;
368 }
369 else
370 {
371 TRACE( "wait for object %p timed out, calling callback %p with context %p\n",
372 wait_work_item->Object, wait_work_item->Callback,
373 wait_work_item->Context );
374 TimerOrWaitFired = TRUE;
375 }
376 wait_work_item->CallbackInProgress = TRUE;
377 wait_work_item->Callback( wait_work_item->Context, TimerOrWaitFired );
378 wait_work_item->CallbackInProgress = FALSE;
379
380 if (wait_work_item->Flags & WT_EXECUTEONLYONCE)
381 break;
382 }
383 else
384 break;
385 }
386
387 completion_event = wait_work_item->CompletionEvent;
388 if (completion_event) NtSetEvent( completion_event, NULL );
389
390 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
391 delete_wait_work_item( wait_work_item );
392
393 return 0;
394 }
395
396 /***********************************************************************
397 * RtlRegisterWait (NTDLL.@)
398 *
399 * Registers a wait for a handle to become signaled.
400 *
401 * PARAMS
402 * NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it.
403 * Object [I] Object to wait to become signaled.
404 * Callback [I] Callback function to execute when the wait times out or the handle is signaled.
405 * Context [I] Context to pass to the callback function when it is executed.
406 * Milliseconds [I] Number of milliseconds to wait before timing out.
407 * Flags [I] Flags. See notes.
408 *
409 * RETURNS
410 * Success: STATUS_SUCCESS.
411 * Failure: Any NTSTATUS code.
412 *
413 * NOTES
414 * Flags can be one or more of the following:
415 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
416 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
417 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
418 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
419 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
420 */
421 NTSTATUS WINAPI RtlRegisterWait(PHANDLE NewWaitObject, HANDLE Object,
422 RTL_WAITORTIMERCALLBACKFUNC Callback,
423 PVOID Context, ULONG Milliseconds, ULONG Flags)
424 {
425 struct wait_work_item *wait_work_item;
426 NTSTATUS status;
427
428 TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags );
429
430 wait_work_item = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*wait_work_item) );
431 if (!wait_work_item)
432 return STATUS_NO_MEMORY;
433
434 wait_work_item->Object = Object;
435 wait_work_item->Callback = Callback;
436 wait_work_item->Context = Context;
437 wait_work_item->Milliseconds = Milliseconds;
438 wait_work_item->Flags = Flags;
439 wait_work_item->CallbackInProgress = FALSE;
440 wait_work_item->DeleteCount = 0;
441 wait_work_item->CompletionEvent = NULL;
442
443 status = NtCreateEvent( &wait_work_item->CancelEvent, EVENT_ALL_ACCESS, NULL, TRUE, FALSE );
444 if (status != STATUS_SUCCESS)
445 {
446 RtlFreeHeap( GetProcessHeap(), 0, wait_work_item );
447 return status;
448 }
449
450 status = RtlQueueWorkItem( wait_thread_proc, wait_work_item, Flags & ~WT_EXECUTEONLYONCE );
451 if (status != STATUS_SUCCESS)
452 {
453 delete_wait_work_item( wait_work_item );
454 return status;
455 }
456
457 *NewWaitObject = wait_work_item;
458 return status;
459 }
460
461 /***********************************************************************
462 * RtlDeregisterWaitEx (NTDLL.@)
463 *
464 * Cancels a wait operation and frees the resources associated with calling
465 * RtlRegisterWait().
466 *
467 * PARAMS
468 * WaitObject [I] Handle to the wait object to free.
469 *
470 * RETURNS
471 * Success: STATUS_SUCCESS.
472 * Failure: Any NTSTATUS code.
473 */
474 NTSTATUS WINAPI RtlDeregisterWaitEx(HANDLE WaitHandle, HANDLE CompletionEvent)
475 {
476 struct wait_work_item *wait_work_item = WaitHandle;
477 NTSTATUS status = STATUS_SUCCESS;
478
479 TRACE( "(%p)\n", WaitHandle );
480
481 NtSetEvent( wait_work_item->CancelEvent, NULL );
482 if (wait_work_item->CallbackInProgress)
483 {
484 if (CompletionEvent != NULL)
485 {
486 if (CompletionEvent == INVALID_HANDLE_VALUE)
487 {
488 status = NtCreateEvent( &CompletionEvent, EVENT_ALL_ACCESS, NULL, TRUE, FALSE );
489 if (status != STATUS_SUCCESS)
490 return status;
491 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
492 if (wait_work_item->CallbackInProgress)
493 NtWaitForSingleObject( CompletionEvent, FALSE, NULL );
494 NtClose( CompletionEvent );
495 }
496 else
497 {
498 interlocked_xchg_ptr( &wait_work_item->CompletionEvent, CompletionEvent );
499 if (wait_work_item->CallbackInProgress)
500 status = STATUS_PENDING;
501 }
502 }
503 else
504 status = STATUS_PENDING;
505 }
506
507 if (interlocked_inc( &wait_work_item->DeleteCount ) == 2 )
508 {
509 status = STATUS_SUCCESS;
510 delete_wait_work_item( wait_work_item );
511 }
512
513 return status;
514 }
515
516 /***********************************************************************
517 * RtlDeregisterWait (NTDLL.@)
518 *
519 * Cancels a wait operation and frees the resources associated with calling
520 * RtlRegisterWait().
521 *
522 * PARAMS
523 * WaitObject [I] Handle to the wait object to free.
524 *
525 * RETURNS
526 * Success: STATUS_SUCCESS.
527 * Failure: Any NTSTATUS code.
528 */
529 NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle)
530 {
531 return RtlDeregisterWaitEx(WaitHandle, NULL);
532 }
533
534
535 /************************** Timer Queue Impl **************************/
536
537 struct timer_queue;
538 struct queue_timer
539 {
540 struct timer_queue *q;
541 struct list entry;
542 ULONG runcount; /* number of callbacks pending execution */
543 RTL_WAITORTIMERCALLBACKFUNC callback;
544 PVOID param;
545 DWORD period;
546 ULONG flags;
547 ULONGLONG expire;
548 BOOL destroy; /* timer should be deleted; once set, never unset */
549 HANDLE event; /* removal event */
550 };
551
552 struct timer_queue
553 {
554 RTL_CRITICAL_SECTION cs;
555 struct list timers; /* sorted by expiration time */
556 BOOL quit; /* queue should be deleted; once set, never unset */
557 HANDLE event;
558 HANDLE thread;
559 };
560
561 #define EXPIRE_NEVER (~(ULONGLONG) 0)
562
563 static void queue_remove_timer(struct queue_timer *t)
564 {
565 /* We MUST hold the queue cs while calling this function. This ensures
566 that we cannot queue another callback for this timer. The runcount
567 being zero makes sure we don't have any already queued. */
568 struct timer_queue *q = t->q;
569
570 assert(t->runcount == 0);
571 assert(t->destroy);
572
573 list_remove(&t->entry);
574 if (t->event)
575 NtSetEvent(t->event, NULL);
576 RtlFreeHeap(GetProcessHeap(), 0, t);
577
578 if (q->quit && list_count(&q->timers) == 0)
579 NtSetEvent(q->event, NULL);
580 }
581
582 static void timer_cleanup_callback(struct queue_timer *t)
583 {
584 struct timer_queue *q = t->q;
585 RtlEnterCriticalSection(&q->cs);
586
587 assert(0 < t->runcount);
588 --t->runcount;
589
590 if (t->destroy && t->runcount == 0)
591 queue_remove_timer(t);
592
593 RtlLeaveCriticalSection(&q->cs);
594 }
595
596 static DWORD WINAPI timer_callback_wrapper(LPVOID p)
597 {
598 struct queue_timer *t = p;
599 t->callback(t->param, TRUE);
600 timer_cleanup_callback(t);
601 return 0;
602 }
603
604 static inline ULONGLONG queue_current_time(void)
605 {
606 LARGE_INTEGER now;
607 NtQuerySystemTime(&now);
608 return now.QuadPart / 10000;
609 }
610
611 static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
612 BOOL set_event)
613 {
614 /* We MUST hold the queue cs while calling this function. */
615 struct timer_queue *q = t->q;
616 struct list *ptr = &q->timers;
617
618 assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
619
620 if (time != EXPIRE_NEVER)
621 LIST_FOR_EACH(ptr, &q->timers)
622 {
623 struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
624 if (time < cur->expire)
625 break;
626 }
627 list_add_before(ptr, &t->entry);
628
629 t->expire = time;
630
631 /* If we insert at the head of the list, we need to expire sooner
632 than expected. */
633 if (set_event && &t->entry == list_head(&q->timers))
634 NtSetEvent(q->event, NULL);
635 }
636
637 static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
638 BOOL set_event)
639 {
640 /* We MUST hold the queue cs while calling this function. */
641 list_remove(&t->entry);
642 queue_add_timer(t, time, set_event);
643 }
644
645 static void queue_timer_expire(struct timer_queue *q)
646 {
647 struct queue_timer *t = NULL;
648
649 RtlEnterCriticalSection(&q->cs);
650 if (list_head(&q->timers))
651 {
652 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
653 if (!t->destroy && t->expire <= queue_current_time())
654 {
655 ++t->runcount;
656 queue_move_timer(
657 t, t->period ? queue_current_time() + t->period : EXPIRE_NEVER,
658 FALSE);
659 }
660 else
661 t = NULL;
662 }
663 RtlLeaveCriticalSection(&q->cs);
664
665 if (t)
666 {
667 if (t->flags & WT_EXECUTEINTIMERTHREAD)
668 timer_callback_wrapper(t);
669 else
670 {
671 ULONG flags
672 = (t->flags
673 & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
674 | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
675 NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
676 if (status != STATUS_SUCCESS)
677 timer_cleanup_callback(t);
678 }
679 }
680 }
681
682 static ULONG queue_get_timeout(struct timer_queue *q)
683 {
684 struct queue_timer *t;
685 ULONG timeout = INFINITE;
686
687 RtlEnterCriticalSection(&q->cs);
688 if (list_head(&q->timers))
689 {
690 t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
691 assert(!t->destroy || t->expire == EXPIRE_NEVER);
692
693 if (t->expire != EXPIRE_NEVER)
694 {
695 ULONGLONG time = queue_current_time();
696 timeout = t->expire < time ? 0 : t->expire - time;
697 }
698 }
699 RtlLeaveCriticalSection(&q->cs);
700
701 return timeout;
702 }
703
704 static void WINAPI timer_queue_thread_proc(LPVOID p)
705 {
706 struct timer_queue *q = p;
707 ULONG timeout_ms;
708
709 timeout_ms = INFINITE;
710 for (;;)
711 {
712 LARGE_INTEGER timeout;
713 NTSTATUS status;
714 BOOL done = FALSE;
715
716 status = NtWaitForSingleObject(
717 q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
718
719 if (status == STATUS_WAIT_0)
720 {
721 /* There are two possible ways to trigger the event. Either
722 we are quitting and the last timer got removed, or a new
723 timer got put at the head of the list so we need to adjust
724 our timeout. */
725 RtlEnterCriticalSection(&q->cs);
726 if (q->quit && list_count(&q->timers) == 0)
727 done = TRUE;
728 RtlLeaveCriticalSection(&q->cs);
729 }
730 else if (status == STATUS_TIMEOUT)
731 queue_timer_expire(q);
732
733 if (done)
734 break;
735
736 timeout_ms = queue_get_timeout(q);
737 }
738
739 NtClose(q->event);
740 RtlDeleteCriticalSection(&q->cs);
741 RtlFreeHeap(GetProcessHeap(), 0, q);
742 }
743
744 static void queue_destroy_timer(struct queue_timer *t)
745 {
746 /* We MUST hold the queue cs while calling this function. */
747 t->destroy = TRUE;
748 if (t->runcount == 0)
749 /* Ensure a timer is promptly removed. If callbacks are pending,
750 it will be removed after the last one finishes by the callback
751 cleanup wrapper. */
752 queue_remove_timer(t);
753 else
754 /* Make sure no destroyed timer masks an active timer at the head
755 of the sorted list. */
756 queue_move_timer(t, EXPIRE_NEVER, FALSE);
757 }
758
759 /***********************************************************************
760 * RtlCreateTimerQueue (NTDLL.@)
761 *
762 * Creates a timer queue object and returns a handle to it.
763 *
764 * PARAMS
765 * NewTimerQueue [O] The newly created queue.
766 *
767 * RETURNS
768 * Success: STATUS_SUCCESS.
769 * Failure: Any NTSTATUS code.
770 */
771 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
772 {
773 NTSTATUS status;
774 struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
775 if (!q)
776 return STATUS_NO_MEMORY;
777
778 RtlInitializeCriticalSection(&q->cs);
779 list_init(&q->timers);
780 q->quit = FALSE;
781 status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, FALSE, FALSE);
782 if (status != STATUS_SUCCESS)
783 {
784 RtlFreeHeap(GetProcessHeap(), 0, q);
785 return status;
786 }
787 status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
788 timer_queue_thread_proc, q, &q->thread, NULL);
789 if (status != STATUS_SUCCESS)
790 {
791 NtClose(q->event);
792 RtlFreeHeap(GetProcessHeap(), 0, q);
793 return status;
794 }
795
796 *NewTimerQueue = q;
797 return STATUS_SUCCESS;
798 }
799
800 /***********************************************************************
801 * RtlDeleteTimerQueueEx (NTDLL.@)
802 *
803 * Deletes a timer queue object.
804 *
805 * PARAMS
806 * TimerQueue [I] The timer queue to destroy.
807 * CompletionEvent [I] If NULL, return immediately. If INVALID_HANDLE_VALUE,
808 * wait until all timers are finished firing before
809 * returning. Otherwise, return immediately and set the
810 * event when all timers are done.
811 *
812 * RETURNS
813 * Success: STATUS_SUCCESS if synchronous, STATUS_PENDING if not.
814 * Failure: Any NTSTATUS code.
815 */
816 NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
817 {
818 struct timer_queue *q = TimerQueue;
819 struct queue_timer *t, *temp;
820 HANDLE thread;
821 NTSTATUS status;
822
823 if (!q)
824 return STATUS_INVALID_HANDLE;
825
826 thread = q->thread;
827
828 RtlEnterCriticalSection(&q->cs);
829 q->quit = TRUE;
830 if (list_head(&q->timers))
831 /* When the last timer is removed, it will signal the timer thread to
832 exit... */
833 LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
834 queue_destroy_timer(t);
835 else
836 /* However if we have none, we must do it ourselves. */
837 NtSetEvent(q->event, NULL);
838 RtlLeaveCriticalSection(&q->cs);
839
840 if (CompletionEvent == INVALID_HANDLE_VALUE)
841 {
842 NtWaitForSingleObject(thread, FALSE, NULL);
843 status = STATUS_SUCCESS;
844 }
845 else
846 {
847 if (CompletionEvent)
848 {
849 FIXME("asynchronous return on completion event unimplemented\n");
850 NtWaitForSingleObject(thread, FALSE, NULL);
851 NtSetEvent(CompletionEvent, NULL);
852 }
853 status = STATUS_PENDING;
854 }
855
856 NtClose(thread);
857 return status;
858 }
859
860 static struct timer_queue *default_timer_queue;
861
862 static struct timer_queue *get_timer_queue(HANDLE TimerQueue)
863 {
864 if (TimerQueue)
865 return TimerQueue;
866 else
867 {
868 if (!default_timer_queue)
869 {
870 HANDLE q;
871 NTSTATUS status = RtlCreateTimerQueue(&q);
872 if (status == STATUS_SUCCESS)
873 {
874 PVOID p = interlocked_cmpxchg_ptr(
875 (void **) &default_timer_queue, q, NULL);
876 if (p)
877 /* Got beat to the punch. */
878 RtlDeleteTimerQueueEx(p, NULL);
879 }
880 }
881 return default_timer_queue;
882 }
883 }
884
885 /***********************************************************************
886 * RtlCreateTimer (NTDLL.@)
887 *
888 * Creates a new timer associated with the given queue.
889 *
890 * PARAMS
891 * NewTimer [O] The newly created timer.
892 * TimerQueue [I] The queue to hold the timer.
893 * Callback [I] The callback to fire.
894 * Parameter [I] The argument for the callback.
895 * DueTime [I] The delay, in milliseconds, before first firing the
896 * timer.
897 * Period [I] The period, in milliseconds, at which to fire the timer
898 * after the first callback. If zero, the timer will only
899 * fire once. It still needs to be deleted with
900 * RtlDeleteTimer.
901 * Flags [I] Flags controling the execution of the callback. In
902 * addition to the WT_* thread pool flags (see
903 * RtlQueueWorkItem), WT_EXECUTEINTIMERTHREAD and
904 * WT_EXECUTEONLYONCE are supported.
905 *
906 * RETURNS
907 * Success: STATUS_SUCCESS.
908 * Failure: Any NTSTATUS code.
909 */
910 NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
911 RTL_WAITORTIMERCALLBACKFUNC Callback,
912 PVOID Parameter, DWORD DueTime, DWORD Period,
913 ULONG Flags)
914 {
915 NTSTATUS status;
916 struct queue_timer *t;
917 struct timer_queue *q = get_timer_queue(TimerQueue);