[libvirt] [PATCH 1/3] daemon: Create priority workers pool

Daniel P. Berrange berrange at redhat.com
Thu Aug 25 14:24:11 UTC 2011


On Wed, Aug 24, 2011 at 10:12:36PM +0100, Daniel P. Berrange wrote:
> On Tue, Aug 23, 2011 at 05:53:35PM +0200, Michal Privoznik wrote:
> > On 23.08.2011 14:23, Daniel P. Berrange wrote:
> > > On Tue, Aug 16, 2011 at 06:39:10PM +0200, Michal Privoznik wrote:
> > >> diff --git a/src/util/threadpool.c b/src/util/threadpool.c
> > >> index 8217591..ad2d249 100644
> > >> --- a/src/util/threadpool.c
> > >> +++ b/src/util/threadpool.c
> > >> @@ -185,27 +185,41 @@ void virThreadPoolFree(virThreadPoolPtr pool)
> > >>      VIR_FREE(pool);
> > >>  }
> > >>  
> > >> +/*
> > >> + * @only_if_free - place job in pool iff there is
> > >> + * a free worker(s).
> > >> + *
> > >> + * Return: 0 on success,
> > >> + * -1 if no free worker available but requested
> > >> + * -2 otherwise
> > >> + */
> > >>  int virThreadPoolSendJob(virThreadPoolPtr pool,
> > >> +                         bool only_if_free,
> > >>                           void *jobData)
> > >>  {
> > >>      virThreadPoolJobPtr job;
> > >> +    int ret = -2;
> > >>  
> > >>      virMutexLock(&pool->mutex);
> > >>      if (pool->quit)
> > >>          goto error;
> > >>  
> > >> -    if (pool->freeWorkers == 0 &&
> > >> -        pool->nWorkers < pool->maxWorkers) {
> > >> -        if (VIR_EXPAND_N(pool->workers, pool->nWorkers, 1) < 0) {
> > >> -            virReportOOMError();
> > >> -            goto error;
> > >> -        }
> > >> +    if (pool->freeWorkers == 0) {

If we added another counter 'pool->jobQueueDepth', changed whenever a
job is added or remove from the queue, then I think we could get the
correct semantics by doing

       if ((pool->freeWorkers - pool->jobQueueDepth) <= 0) {
         ...
       }

> > >> +        if (pool->nWorkers < pool->maxWorkers) {
> > >> +            if (VIR_EXPAND_N(pool->workers, pool->nWorkers, 1) < 0) {
> > >> +                virReportOOMError();
> > >> +                goto error;
> > >> +            }
> > >>  
> > >> -        if (virThreadCreate(&pool->workers[pool->nWorkers - 1],
> > >> -                            true,
> > >> -                            virThreadPoolWorker,
> > >> -                            pool) < 0) {
> > >> -            pool->nWorkers--;
> > >> +            if (virThreadCreate(&pool->workers[pool->nWorkers - 1],
> > >> +                                true,
> > >> +                                virThreadPoolWorker,
> > >> +                                pool) < 0) {
> > >> +                pool->nWorkers--;
> > >> +                goto error;
> > >> +            }
> > >> +        } else if (only_if_free) {
> > >> +            ret = -1;
> > >>              goto error;
> > >>          }
> > > 
> > > I don't think this check is correct, because it is only checking
> > > the free workers, against the current/max workers. It is not
> > > taking into account the possibility that there are queued jobs
> > > which have not yet been served. So it might look like there is
> > > a free worker, but there is already a pending job which could
> > > consume it.
> > 
> > I don't agree. Currently we allow expanding of pool only when placing a
> > job into pool. During placing, pool is locked, so freeWorkers variable
> > cannot change. Assume freeWorkers == 0; As soon as we can't expand the
> > pool, job will wait on queue. So we may decide if we want to place job
> > or not. If freeWorkers is not zero, there can't be any job on the queue,
> > because it would be immediately taken by a free worker.
> 
> The problem I'm seeing involves a sequence of two calls to
> virThreadPoolSendJob().  Most of the time we will expect
> a liner sequence of virThreadPoolSendJob calls, which will
> result in something like this set of steps:
> 
>  * Initial condition: freeWorkers==1, nWorkers==1, maxWorkers==5, queuedJobs==0
> 
>  * Thread 1 calls virThreadPoolSendJob()
>       1. Acquires lock
>       2. Queues job
>       3. Notifies condition
>       4. Releases lock
> 
>  * Current condition: freeWorkers==1, nWorkers==1, maxWorkers==5, queuedJobs==1
> 
>  * Thread 2 is a worker thread
>       1. Woken up from condition wait
>       2. Acquires lock
>       3. Decrements freeWorkers
>       5. Releases lock
>       6. Starts processing job
> 
>  * Current condition: freeWorkers==0, nWorkers==1, maxWorkers==5, queuedJobs==0
> 
>  * Thread 3 calls virThreadPoolSendJob()
>       1. Acquires lock
>       2. Spawns new worker thread
>       3. Queues job
>       4. Notifies condition
>       5. Releases lock
> 
>  * Current condition: freeWorkers==0, nWorkers==2, maxWorkers==5, queuedJobs==1
> 
>  * Thread 4 is a new worker thread
>       1. Woken up from condition wait
>       2. Acquires lock
>       3. Decrements freeWorkers
>       5. Releases lock
>       6. Starts processing job
> 
>  * Final condition: freeWorkers==0, nWorkers==2, maxWorkers==5, queuedJobs==0
> 
> 
> But now consider what happens if two calls to virThreadPoolSendJob
> arrive concurrently. It is possible thread 3 will acquire the lock
> before thread 2 does. This results in the following set of steps:
> 
>  * Initial condition: freeWorkers==1, nWorkers==1, maxWorkers==5, queuedJobs==0
> 
>  * Thread 1 calls virThreadPoolSendJob()
>       1. Acquires lock
>       2. Queues job
>       3. Notifies condition
>       4. Releases lock
> 
>  * Current condition: freeWorkers==1, nWorkers==1, maxWorkers==5, queuedJobs==1
> 
>  * Thread 3 calls virThreadPoolSendJob()
>       1. Acquires lock
>       2. Queues job
>       3. Notifies condition
>       4. Releases lock
> 
>  * Current condition: freeWorkers==1, nWorkers==1, maxWorkers==5, queuedJobs==2
> 
>  * Thread 2 is a worker thread
>       1. Woken up from condition wait
>       2. Acquires lock
>       3. Decrements freeWorkers
>       5. Releases lock
>       6. Starts processing job
> 
>  * Final condition: freeWorkers==0, nWorkers==1, maxWorkers==5, queuedJobs==1
> 
> So we end up with 2 jobs and only one worker thread to process
> them. If that second job was a high priority job, it would be
> queued in the normal queue because 'freeWorkers' was still 1,
> even though there was a job in the queue ahead of it, so the
> effective 'freeWorkers' was 0.
> 
> This is actually an existing bug in the virThreadPool code for queuing
> jobs, but it was harmless. When we start to differentiate between low
> and high priority jobs, then this bug becomes active and can result in
> a high priority job being blocked by low priority ones.


Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|




More information about the libvir-list mailing list