[3/4] ntdll: Implement the timer queue thread.

Dan Hipschman dsh at linux.ucla.edu
Tue Jul 22 20:11:35 CDT 2008


This is pretty close to the same as before, but with bugs Rob pointed out
fixed in this version, and the code a little bit simplified.  I'm using
64-bit time values here so wrap shouldn't become a problem.

I also noticed a bug in my original code.  When a once-only timer has fired,
we should not delete it.  That's still up to the user, so instead I've set
the next expiration time to an impossible value.

This isn't the most efficient implementation, but it works, and it should
not be difficult to tweak.  Hence, I'd rather get this version in and add
the optimizations one at a time, in little patches.  "Get it working first..."

---
 dlls/kernel32/tests/sync.c |   34 ++++++-
 dlls/ntdll/threadpool.c    |  217 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 235 insertions(+), 16 deletions(-)

diff --git a/dlls/kernel32/tests/sync.c b/dlls/kernel32/tests/sync.c
index 05f67dc..d41bd79 100644
--- a/dlls/kernel32/tests/sync.c
+++ b/dlls/kernel32/tests/sync.c
@@ -588,11 +588,29 @@ static void CALLBACK timer_queue_cb3(PVOID p, BOOLEAN timedOut)
     }
 }
 
+static void CALLBACK timer_queue_cb4(PVOID p, BOOLEAN timedOut)
+{
+    struct timer_queue_data1 *d = p;
+    ok(timedOut, "Timer callbacks should always time out\n");
+    if (d->t)
+    {
+        /* This tests whether a timer gets flagged for deletion before
+           or after the callback runs.  If we start this timer with a
+           period of zero (run once), then ChangeTimerQueueTimer will
+           fail if the timer is already flagged.  Hence we really run
+           only once.  Otherwise we will run multiple times.  */
+        BOOL ret = pChangeTimerQueueTimer(d->q, d->t, 50, 50);
+        todo_wine
+        ok(ret, "ChangeTimerQueueTimer\n");
+        ++d->num_calls;
+    }
+}
+
 static void test_timer_queue(void)
 {
     HANDLE q, t1, t2, t3, t4, t5;
     int n1, n2, n3, n4, n5;
-    struct timer_queue_data1 d2, d3;
+    struct timer_queue_data1 d2, d3, d4;
     HANDLE e;
     BOOL ret;
 
@@ -661,13 +679,9 @@ static void test_timer_queue(void)
 
     ret = pDeleteTimerQueueEx(q, INVALID_HANDLE_VALUE);
     ok(ret, "DeleteTimerQueueEx\n");
-    todo_wine
-    {
     ok(n1 == 1, "Timer callback 1\n");
     ok(n2 < n3, "Timer callback 2 should be much slower than 3\n");
-    }
     ok(n4 == 0, "Timer callback 4\n");
-    todo_wine
     ok(n5 == 1, "Timer callback 5\n");
 
     /* Test synchronous deletion of the queue with event trigger. */
@@ -713,6 +727,15 @@ static void test_timer_queue(void)
     ok(ret, "CreateTimerQueueTimer\n");
     ok(t3 != NULL, "CreateTimerQueueTimer\n");
 
+    d4.t = t4 = NULL;
+    d4.num_calls = 0;
+    d4.q = q;
+    ret = pCreateTimerQueueTimer(&t4, q, timer_queue_cb4, &d4, 10,
+                                 0, 0);
+    d4.t = t4;
+    ok(ret, "CreateTimerQueueTimer\n");
+    ok(t4 != NULL, "CreateTimerQueueTimer\n");
+
     Sleep(200);
 
     ret = pDeleteTimerQueueEx(q, INVALID_HANDLE_VALUE);
@@ -722,6 +745,7 @@ static void test_timer_queue(void)
     ok(d2.num_calls == d2.max_calls, "DeleteTimerQueueTimer\n");
     ok(d3.num_calls == d3.max_calls, "ChangeTimerQueueTimer\n");
     }
+    ok(d4.num_calls == 1, "Timer flagged for deletion incorrectly\n");
 }
 
 START_TEST(sync)
diff --git a/dlls/ntdll/threadpool.c b/dlls/ntdll/threadpool.c
index c698cb7..a5cf102 100644
--- a/dlls/ntdll/threadpool.c
+++ b/dlls/ntdll/threadpool.c
@@ -21,6 +21,7 @@
 #include "config.h"
 #include "wine/port.h"
 
+#include <assert.h>
 #include <stdarg.h>
 #include <limits.h>
 
@@ -532,23 +533,170 @@ NTSTATUS WINAPI RtlDeregisterWait(HANDLE WaitHandle)
 
 /************************** Timer Queue Impl **************************/
 
+struct timer_queue;
 struct queue_timer
 {
+    struct timer_queue *q;
     struct list entry;
+    ULONG runcount;             /* number of callbacks pending execution */
+    RTL_WAITORTIMERCALLBACKFUNC callback;
+    PVOID param;
+    DWORD period;
+    ULONG flags;
+    ULONGLONG expire;
+    BOOL destroy;      /* timer should be deleted; once set, never unset */
 };
 
 struct timer_queue
 {
     RTL_CRITICAL_SECTION cs;
     struct list timers;
+    struct queue_timer *next;                /* the next timer to expire */
+    BOOL quit;         /* queue should be deleted; once set, never unset */
+    HANDLE event;
+    HANDLE thread;
 };
 
-static void queue_remove_timer(struct queue_timer *t)
+static void queue_remove_timer(struct timer_queue *q, struct queue_timer *t)
 {
+    /* We MUST hold the queue cs while calling this function.  This ensures
+       that we cannot queue another callback for this timer.  The runcount
+       being zero makes sure we don't have any already queued.  */
+    assert(t->runcount == 0);
+    assert(t->destroy);
     list_remove(&t->entry);
+    if (q->next == t)
+        q->next = NULL;
     RtlFreeHeap(GetProcessHeap(), 0, t);
 }
 
+static void timer_cleanup_callback(struct queue_timer *t)
+{
+    struct timer_queue *q = t->q;
+    RtlEnterCriticalSection(&q->cs);
+
+    assert(0 < t->runcount);
+    --t->runcount;
+
+    if (t->destroy && t->runcount == 0)
+    {
+        queue_remove_timer(q, t);
+        /* Wake up the cleanup loop so it can see if all timers are gone.  */
+        if (q->quit)
+            NtSetEvent(q->event, NULL);
+    }
+
+    RtlLeaveCriticalSection(&q->cs);
+}
+
+static DWORD WINAPI timer_callback_wrapper(LPVOID p)
+{
+    struct queue_timer *t = p;
+    t->callback(t->param, TRUE);
+    timer_cleanup_callback(t);
+    return 0;
+}
+
+static ULONGLONG queue_current_time(void)
+{
+    LARGE_INTEGER now;
+    NtQuerySystemTime(&now);
+    return now.QuadPart / 10000;
+}
+
+static void queue_timer_expire_next(struct timer_queue *q)
+{
+    struct queue_timer *t;
+
+    RtlEnterCriticalSection(&q->cs);
+    t = q->next;
+    if (t && !t->destroy && t->expire <= queue_current_time())
+    {
+        ++t->runcount;
+        t->expire = t->period ? queue_current_time() + t->period : ~(ULONGLONG) 0;
+    }
+    else
+        t = NULL;
+    RtlLeaveCriticalSection(&q->cs);
+
+    if (t)
+    {
+        if (t->flags & WT_EXECUTEINTIMERTHREAD)
+            timer_callback_wrapper(t);
+        else
+        {
+            ULONG flags
+                = (t->flags
+                   & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
+                      | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION));
+            NTSTATUS status = RtlQueueWorkItem(timer_callback_wrapper, t, flags);
+            if (status != STATUS_SUCCESS)
+                timer_cleanup_callback(t);
+        }
+    }
+}
+
+static ULONG timer_queue_update(struct timer_queue *q)
+{
+    /* We MUST hold the queue cs while calling this function.  */
+    struct queue_timer *t, *temp, *next = NULL;
+    ULONGLONG time, next_expire = ~(ULONGLONG) 0;
+
+    LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
+        if (t->destroy)
+        {
+            if (t->runcount == 0)
+                queue_remove_timer(q, t);
+        }
+        else if (t->expire < next_expire)
+        {
+            next_expire = t->expire;
+            next = t;
+        }
+
+    time = queue_current_time();
+    q->next = next;
+    return next ? (next_expire < time ? 0 : next_expire - time) : INFINITE;
+}
+
+static void WINAPI timer_queue_thread_proc(LPVOID p)
+{
+    struct timer_queue *q = p;
+    ULONG timeout_ms;
+    BOOL done;
+
+    timeout_ms = INFINITE;
+    while (!q->quit)
+    {
+        LARGE_INTEGER timeout;
+        DWORD ret = NtWaitForSingleObject(q->event, FALSE,
+                                          get_nt_timeout(&timeout, timeout_ms));
+
+        if (ret == STATUS_TIMEOUT)
+            queue_timer_expire_next(q);
+
+        RtlEnterCriticalSection(&q->cs);
+        timeout_ms = timer_queue_update(q);
+        RtlLeaveCriticalSection(&q->cs);
+    }
+
+    done = FALSE;
+    while (!done)
+    {
+        RtlEnterCriticalSection(&q->cs);
+        timer_queue_update(q);
+        if (list_count(&q->timers) == 0)
+            done = TRUE;
+        RtlLeaveCriticalSection(&q->cs);
+        if (!done)
+            NtWaitForSingleObject(q->event, FALSE, NULL);
+    }
+
+    NtClose(q->event);
+    RtlDeleteCriticalSection(&q->cs);
+    RtlFreeHeap(GetProcessHeap(), 0, q);
+}
+
 /***********************************************************************
  *              RtlCreateTimerQueue   (NTDLL.@)
  *
@@ -563,12 +711,29 @@ static void queue_remove_timer(struct queue_timer *t)
  */
 NTSTATUS WINAPI RtlCreateTimerQueue(PHANDLE NewTimerQueue)
 {
+    NTSTATUS status;
     struct timer_queue *q = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *q);
     if (!q)
         return STATUS_NO_MEMORY;
 
     RtlInitializeCriticalSection(&q->cs);
     list_init(&q->timers);
+    q->next = NULL;
+    q->quit = FALSE;
+    status = NtCreateEvent(&q->event, EVENT_ALL_ACCESS, NULL, FALSE, FALSE);
+    if (status != STATUS_SUCCESS)
+    {
+        RtlFreeHeap(GetProcessHeap(), 0, q);
+        return status;
+    }
+    status = RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, NULL, 0, 0,
+                                 timer_queue_thread_proc, q, &q->thread, NULL);
+    if (status != STATUS_SUCCESS)
+    {
+        NtClose(q->event);
+        RtlFreeHeap(GetProcessHeap(), 0, q);
+        return status;
+    }
 
     *NewTimerQueue = q;
     return STATUS_SUCCESS;
@@ -594,23 +759,34 @@ NTSTATUS WINAPI RtlDeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
 {
     struct timer_queue *q = TimerQueue;
     struct queue_timer *t, *temp;
+    HANDLE thread = q->thread;
+    NTSTATUS status;
 
     RtlEnterCriticalSection(&q->cs);
     LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
-        queue_remove_timer(t);
+        t->destroy = TRUE;
+    q->quit = TRUE;
+    NtSetEvent(q->event, NULL);
     RtlLeaveCriticalSection(&q->cs);
 
-    RtlDeleteCriticalSection(&q->cs);
-    RtlFreeHeap(GetProcessHeap(), 0, q);
-
     if (CompletionEvent == INVALID_HANDLE_VALUE)
-        return STATUS_SUCCESS;
+    {
+        NtWaitForSingleObject(thread, FALSE, NULL);
+        status = STATUS_SUCCESS;
+    }
     else
     {
         if (CompletionEvent)
+        {
+            FIXME("asynchronous return on completion event unimplemented\n");
+            NtWaitForSingleObject(thread, FALSE, NULL);
             NtSetEvent(CompletionEvent, NULL);
-        return STATUS_PENDING;
+        }
+        status = STATUS_PENDING;
     }
+
+    NtClose(thread);
+    return status;
 }
 
 /***********************************************************************
@@ -643,17 +819,36 @@ NTSTATUS WINAPI RtlCreateTimer(PHANDLE NewTimer, HANDLE TimerQueue,
                                PVOID Parameter, DWORD DueTime, DWORD Period,
                                ULONG Flags)
 {
+    NTSTATUS status;
     struct timer_queue *q = TimerQueue;
     struct queue_timer *t = RtlAllocateHeap(GetProcessHeap(), 0, sizeof *t);
     if (!t)
         return STATUS_NO_MEMORY;
 
-    FIXME("timer expiration unimplemented\n");
+    t->q = q;
+    t->runcount = 0;
+    t->callback = Callback;
+    t->param = Parameter;
+    t->period = Period;
+    t->flags = Flags;
+    t->expire = queue_current_time() + DueTime;
+    t->destroy = FALSE;
 
+    status = STATUS_SUCCESS;
     RtlEnterCriticalSection(&q->cs);
-    list_add_tail(&q->timers, &t->entry);
+    if (q->quit)
+        status = STATUS_INVALID_HANDLE;
+    else
+    {
+        list_add_tail(&q->timers, &t->entry);
+        NtSetEvent(q->event, NULL);
+    }
     RtlLeaveCriticalSection(&q->cs);
 
-    *NewTimer = t;
-    return STATUS_SUCCESS;
+    if (status == STATUS_SUCCESS)
+        *NewTimer = t;
+    else
+        RtlFreeHeap(GetProcessHeap(), 0, t);
+
+    return status;
 }



More information about the wine-patches mailing list