[8/11] kernel32: Implement the timer queue thread.
Dan Hipschman
dsh at linux.ucla.edu
Sat Jul 19 14:18:58 CDT 2008
Thanks for these especially good comments, Rob!
On Sat, Jul 19, 2008 at 07:08:17PM +0100, Rob Shearman wrote:
> 2008/7/19 Dan Hipschman <dsh at linux.ucla.edu>:
> >
> > This patch implements the timer thread, running callbacks, cleaning up
> > timers, and all that good stuff. It should be very efficient, using mostly
> > atomic operations and one critical section for thread safety.
>
> It would be more efficient for queues with large numbers of timers if
> you used the approach used by server/fd.c of keeping a list of timers
> sorted by when they next expire to avoid having to search through the
> entire list every time a timer expires.
The answer to most of your questions is, because I was trying to start
simple. I'll look into this, though.
> > +static void queue_timer_expire(struct queue_timer *t)
> > +{
> > + /* We MUST hold the queue cs while calling this function. */
> > + if (!t->die)
> > + {
> > + ULONG flags =
> > + t->flags & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD
> > + | WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION);
> > + InterlockedIncrement(&t->runcount);
> > + QueueUserWorkItem(timer_callback_wrapper, t, flags);
>
> You should check the return value and undo the incrementing of
> t->runcount if it failed. However, you're also calling
> QueueUserWorkItem inside a critical section so you're imposing a lock
> ordering on the loader lock. This may cause a deadlock that may or may
> not exist on Windows if a timer queue function is called from an
> application's DllMain.
Thanks for telling me about this. It should be safe to move
QueueUserWorkItem outside the CS. I'll think about it later.
> It would be better to use a thread pool thread for processing timer
> queue since it can be reused when the timer queue is destroyed. It
> might then also be better to only start the timer queue thread when
> there are timers to process and also release the timer queue thread
> back to the thread pool when the last timer is removed from the queue.
>
> Also, have you thought about using one thread for all timer queues?
> This might not be appropriate for WT_EXECUTEINTIMERTHREAD timers, but
> should improve the performance of the process as a whole without
> affecting other timers.
Yes, I thought about these, but again, trying to keep it simple...
> > @@ -1085,22 +1226,32 @@ BOOL WINAPI DeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
> > struct queue_timer *t, *temp;
> > BOOL ret;
> >
> > + RtlEnterCriticalSection(&q->cs);
> > + LIST_FOR_EACH_ENTRY_SAFE(t, temp, &q->timers, struct queue_timer, entry)
> > + t->die = TRUE;
> > + q->quit = TRUE;
> > + RtlLeaveCriticalSection(&q->cs);
> > + SetEvent(q->event);
> > +
> > if (CompletionEvent == INVALID_HANDLE_VALUE)
> > {
> > ret = TRUE;
> > + WaitForSingleObject(q->thread, INFINITE);
>
> What if timer_queue_thread_proc exits before you reach here? q->thread
> will now be invalid (and could be re-used by another thread in the
> mean time). Another way of looking at it is that you don't own q after
> the critical section where q->quit is set.
This is a great catch. I utterly missed this. Maybe the easiest fix is
to dup the thread handle into a local. I'll look for a cleaner approach
as well.
> If you use an expire list in a local variable then you won't need
> locking when processing the expired timers and so this will be trivial
> to implement.
I'll take another look.
> Quite a few comments, but the patches are of a good general quality,
> so well done.
Thanks for taking the time to go over this so thoroughly. Thanks, Hans,
for the comments as well; I'll check out GetTickCount64.
Dan
More information about the wine-devel
mailing list