ntdll: Implement the timer queue thread.
This commit is contained in:
parent
6de5bdb0f7
commit
5ef54c4cec
|
@ -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)
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "config.h"
|
||||
#include "wine/port.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
@ -532,21 +533,223 @@ 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 list timers; /* sorted by expiration time */
|
||||
BOOL quit; /* queue should be deleted; once set, never unset */
|
||||
HANDLE event;
|
||||
HANDLE thread;
|
||||
};
|
||||
|
||||
#define EXPIRE_NEVER (~(ULONGLONG) 0)
|
||||
|
||||
static void queue_remove_timer(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. */
|
||||
struct timer_queue *q = t->q;
|
||||
|
||||
assert(t->runcount == 0);
|
||||
assert(t->destroy);
|
||||
|
||||
list_remove(&t->entry);
|
||||
RtlFreeHeap(GetProcessHeap(), 0, t);
|
||||
|
||||
if (q->quit && list_count(&q->timers) == 0)
|
||||
NtSetEvent(q->event, NULL);
|
||||
}
|
||||
|
||||
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(t);
|
||||
|
||||
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 inline ULONGLONG queue_current_time(void)
|
||||
{
|
||||
LARGE_INTEGER now;
|
||||
NtQuerySystemTime(&now);
|
||||
return now.QuadPart / 10000;
|
||||
}
|
||||
|
||||
static void queue_add_timer(struct queue_timer *t, ULONGLONG time,
|
||||
BOOL set_event)
|
||||
{
|
||||
/* We MUST hold the queue cs while calling this function. */
|
||||
struct timer_queue *q = t->q;
|
||||
struct list *ptr = &q->timers;
|
||||
|
||||
assert(!q->quit || (t->destroy && time == EXPIRE_NEVER));
|
||||
|
||||
if (time != EXPIRE_NEVER)
|
||||
LIST_FOR_EACH(ptr, &q->timers)
|
||||
{
|
||||
struct queue_timer *cur = LIST_ENTRY(ptr, struct queue_timer, entry);
|
||||
if (time < cur->expire)
|
||||
break;
|
||||
}
|
||||
list_add_before(ptr, &t->entry);
|
||||
|
||||
t->expire = time;
|
||||
|
||||
/* If we insert at the head of the list, we need to expire sooner
|
||||
than expected. */
|
||||
if (set_event && &t->entry == list_head(&q->timers))
|
||||
NtSetEvent(q->event, NULL);
|
||||
}
|
||||
|
||||
static inline void queue_move_timer(struct queue_timer *t, ULONGLONG time,
|
||||
BOOL set_event)
|
||||
{
|
||||
/* We MUST hold the queue cs while calling this function. */
|
||||
list_remove(&t->entry);
|
||||
queue_add_timer(t, time, set_event);
|
||||
}
|
||||
|
||||
static void queue_timer_expire(struct timer_queue *q)
|
||||
{
|
||||
struct queue_timer *t;
|
||||
|
||||
RtlEnterCriticalSection(&q->cs);
|
||||
if (list_head(&q->timers))
|
||||
{
|
||||
t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
|
||||
if (!t->destroy && t->expire <= queue_current_time())
|
||||
{
|
||||
++t->runcount;
|
||||
queue_move_timer(
|
||||
t, t->period ? queue_current_time() + t->period : EXPIRE_NEVER,
|
||||
FALSE);
|
||||
}
|
||||
}
|
||||
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 queue_get_timeout(struct timer_queue *q)
|
||||
{
|
||||
struct queue_timer *t;
|
||||
ULONG timeout = INFINITE;
|
||||
|
||||
RtlEnterCriticalSection(&q->cs);
|
||||
if (list_head(&q->timers))
|
||||
{
|
||||
t = LIST_ENTRY(list_head(&q->timers), struct queue_timer, entry);
|
||||
assert(!t->destroy || t->expire == EXPIRE_NEVER);
|
||||
|
||||
if (t->expire != EXPIRE_NEVER)
|
||||
{
|
||||
ULONGLONG time = queue_current_time();
|
||||
timeout = t->expire < time ? 0 : t->expire - time;
|
||||
}
|
||||
}
|
||||
RtlLeaveCriticalSection(&q->cs);
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
static void WINAPI timer_queue_thread_proc(LPVOID p)
|
||||
{
|
||||
struct timer_queue *q = p;
|
||||
ULONG timeout_ms;
|
||||
|
||||
timeout_ms = INFINITE;
|
||||
for (;;)
|
||||
{
|
||||
LARGE_INTEGER timeout;
|
||||
NTSTATUS status;
|
||||
BOOL done = FALSE;
|
||||
|
||||
status = NtWaitForSingleObject(
|
||||
q->event, FALSE, get_nt_timeout(&timeout, timeout_ms));
|
||||
|
||||
if (status == STATUS_WAIT_0)
|
||||
{
|
||||
/* There are two possible ways to trigger the event. Either
|
||||
we are quitting and the last timer got removed, or a new
|
||||
timer got put at the head of the list so we need to adjust
|
||||
our timeout. */
|
||||
RtlEnterCriticalSection(&q->cs);
|
||||
if (q->quit && list_count(&q->timers) == 0)
|
||||
done = TRUE;
|
||||
RtlLeaveCriticalSection(&q->cs);
|
||||
}
|
||||
else if (status == STATUS_TIMEOUT)
|
||||
queue_timer_expire(q);
|
||||
|
||||
if (done)
|
||||
break;
|
||||
|
||||
timeout_ms = queue_get_timeout(q);
|
||||
}
|
||||
|
||||
NtClose(q->event);
|
||||
RtlDeleteCriticalSection(&q->cs);
|
||||
RtlFreeHeap(GetProcessHeap(), 0, q);
|
||||
}
|
||||
|
||||
static void queue_destroy_timer(struct queue_timer *t)
|
||||
{
|
||||
/* We MUST hold the queue cs while calling this function. */
|
||||
t->destroy = TRUE;
|
||||
if (t->runcount == 0)
|
||||
/* Ensure a timer is promptly removed. If callbacks are pending,
|
||||
it will be removed after the last one finishes by the callback
|
||||
cleanup wrapper. */
|
||||
queue_remove_timer(t);
|
||||
else
|
||||
/* Make sure no destroyed timer masks an active timer at the head
|
||||
of the sorted list. */
|
||||
queue_move_timer(t, EXPIRE_NEVER, FALSE);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
|
@ -563,12 +766,28 @@ 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->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 +813,39 @@ 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);
|
||||
q->quit = TRUE;
|
||||
if (list_head(&q->timers))
|
||||
/* When the last timer is removed, it will signal the timer thread to
|
||||
exit... */
|
||||
LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
|
||||
queue_remove_timer(t);
|
||||
queue_destroy_timer(t);
|
||||
else
|
||||
/* However if we have none, we must do it ourselves. */
|
||||
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 +878,32 @@ 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->destroy = FALSE;
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
RtlEnterCriticalSection(&q->cs);
|
||||
list_add_tail(&q->timers, &t->entry);
|
||||
if (q->quit)
|
||||
status = STATUS_INVALID_HANDLE;
|
||||
else
|
||||
queue_add_timer(t, queue_current_time() + DueTime, TRUE);
|
||||
RtlLeaveCriticalSection(&q->cs);
|
||||
|
||||
if (status == STATUS_SUCCESS)
|
||||
*NewTimer = t;
|
||||
return STATUS_SUCCESS;
|
||||
else
|
||||
RtlFreeHeap(GetProcessHeap(), 0, t);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue