[libvirt] [PATCH] events: Propose a separate lock for event queue
Daniel P. Berrange
berrange at redhat.com
Wed Oct 12 10:49:18 UTC 2011
On Mon, Oct 10, 2011 at 01:45:50PM +0200, Michal Privoznik wrote:
> Currently, push & pop from event queue (both server & client side)
> rely on lock from higher levels, e.g. on driver lock (qemu),
> private_data (remote), ...; This alone is not sufficient as not
> every function that interacts with this queue can/does lock,
> esp. in client where we have a different approach, "passing
> the buck".
>
> Therefore we need a separate lock just to protect event queue.
> +static void
> +virDomainEventStateLock(virDomainEventStatePtr state)
> +{
> + if (!state)
> + return;
> +
> + virMutexLock(&state->lock);
> +}
> +
> +static void
> +virDomainEventStateUnlock(virDomainEventStatePtr state)
> +{
> + if (!state)
> + return;
> +
> + virMutexUnlock(&state->lock);
> +}
Normal coding policy is to *not* check for NULL in the mutex
functions. It is safer to crash on NULL, then to return without
locking.
> virDomainEventStateQueue(virDomainEventStatePtr state,
> virDomainEventPtr event)
> {
> + int count;
> +
> if (state->timer < 0) {
> virDomainEventFree(event);
> return;
> }
>
> + virDomainEventStateLock(state);
> +
> if (virDomainEventQueuePush(state->queue, event) < 0) {
> VIR_DEBUG("Error adding event to queue");
> virDomainEventFree(event);
> }
>
> - if (state->queue->count == 1)
> + count = state->queue->count;
> + virDomainEventStateUnlock(state);
> +
> + if (count == 1)
> virEventUpdateTimeout(state->timer, 0);
> +
> }
Updating the timer outside the lock is unsafe.
>
> void
> @@ -1182,6 +1217,7 @@ virDomainEventStateFlush(virDomainEventStatePtr state,
> {
> virDomainEventQueue tempQueue;
>
> + virDomainEventStateLock(state);
> state->isDispatching = true;
>
> /* Copy the queue, so we're reentrant safe when dispatchFunc drops the
> @@ -1190,6 +1226,7 @@ virDomainEventStateFlush(virDomainEventStatePtr state,
> tempQueue.events = state->queue->events;
> state->queue->count = 0;
> state->queue->events = NULL;
> + virDomainEventStateUnlock(state);
>
> virEventUpdateTimeout(state->timer, -1);
> virDomainEventQueueDispatch(&tempQueue,
This is unsafe
> @@ -1198,9 +1235,11 @@ virDomainEventStateFlush(virDomainEventStatePtr state,
> opaque);
>
> /* Purge any deleted callbacks */
> + virDomainEventStateLock(state);
> virDomainEventCallbackListPurgeMarked(state->callbacks);
>
> state->isDispatching = false;
> + virDomainEventStateUnlock(state);
> }
With these two timer updates you have a race condition with 2 threads:
T1: virDomainEventStateFlush T2: virDomainEventQueue
1. Lock
2. Set count = 0;
3. Unlock
4. lock
5. Save current count value
6. Queue new event
7. unlock
8. virEventUpdateTimer(1);
9. virEventUpdateTimer(-1)
Now the timer is disabled, but we have an event pending
You need to make sure all timer updates are *inside* the critical
section.
Regards,
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