[libvirt] [Patch v4 0/3] Add new feature into live migration

If the memory of guest OS is changed constantly, the live migration can not be ended ever for ever. We can use the command 'virsh migrate-setmaxdowntime' to control the live migration. But the value of maxdowntime is diffcult to calculate because it depends on the transfer speed of network and constantly changing memroy size. We need a easy way to control the live migration. This patch set add the support of auto cold migration fallback on timeout. With this patch set, when we migrate the guest OS, we can specify a timeout. If the live migration timeouts, the migration will fallback to cold migration. Test of this patchset on Linux: Env: a. The size of guest OS's memory: 1GB b. The transfer speed of network: about 100Mb/s c. The size of constantly changing memory: more than 900MB 1. migrate without timeout # virsh migrate --live RHEL6RC qemu+ssh://<dest IP>/system tcp://<dest IP>:49152 The migration does not end after 12 hours. 2. migrate with timeout(30 minutes): # virsh -t migrate --live --timeout 1800 RHEL6RC qemu+ssh://<dest IP>/system tcp:<dest IP>:49152 (Time: 1828615.523 ms) v4: - move tools/timer.* into src/util/ directory - use v3: - use the existing virEventXXXTimeout() APT to implement timer - merge Add, Update, Delete into one single interface virSetTimeout() - start timer thread when initializing timer v2: - implement timer for Windows - implement dynamic timers Wen Congyang (3): move daemon/event.* into src/util/ directory timer impl force guest to suspend at timeout cfg.mk | 1 + daemon/Makefile.am | 1 - daemon/event.c | 700 ---------------------------------------------- daemon/event.h | 134 --------- daemon/libvirtd.c | 2 +- src/Makefile.am | 4 +- src/libvirt.c | 2 + src/libvirt_private.syms | 20 ++ src/util/event_impl.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/event_impl.h | 134 +++++++++ src/util/timer.c | 159 +++++++++++ src/util/timer.h | 34 +++ tests/Makefile.am | 2 +- tests/eventtest.c | 2 +- tools/Makefile.am | 1 - tools/console.c | 2 +- tools/virsh.c | 69 +++++- tools/virsh.pod | 4 + 18 files changed, 1129 insertions(+), 842 deletions(-) delete mode 100644 daemon/event.c delete mode 100644 daemon/event.h create mode 100644 src/util/event_impl.c create mode 100644 src/util/event_impl.h create mode 100644 src/util/timer.c create mode 100644 src/util/timer.h

move daemon/event.* into src/util/ directory because the timer needs the API virEventRunOnce(). Signed-off-by: Wen Congyang <wency@cn.fujitsu.com> --- daemon/Makefile.am | 1 - daemon/event.c | 700 ---------------------------------------------- daemon/event.h | 134 --------- daemon/libvirtd.c | 2 +- src/Makefile.am | 3 +- src/libvirt_private.syms | 14 + src/util/event_impl.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/event_impl.h | 134 +++++++++ tests/Makefile.am | 2 +- tests/eventtest.c | 2 +- tools/Makefile.am | 1 - tools/console.c | 2 +- tools/virsh.c | 2 +- 13 files changed, 855 insertions(+), 842 deletions(-) delete mode 100644 daemon/event.c delete mode 100644 daemon/event.h create mode 100644 src/util/event_impl.c create mode 100644 src/util/event_impl.h diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 3ffb7be..adab3cc 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -3,7 +3,6 @@ CLEANFILES = DAEMON_SOURCES = \ - event.c event.h \ libvirtd.c libvirtd.h \ remote.c remote.h \ dispatch.c dispatch.h \ diff --git a/daemon/event.c b/daemon/event.c deleted file mode 100644 index 89ca9f0..0000000 --- a/daemon/event.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - * event.c: event loop for monitoring file handles - * - * Copyright (C) 2007, 2010 Red Hat, Inc. - * Copyright (C) 2007 Daniel P. Berrange - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Author: Daniel P. Berrange <berrange@redhat.com> - */ - -#include <config.h> - -#include <stdlib.h> -#include <string.h> -#include <poll.h> -#include <sys/time.h> -#include <errno.h> -#include <unistd.h> - -#include "threads.h" -#include "logging.h" -#include "event.h" -#include "memory.h" -#include "util.h" -#include "ignore-value.h" - -#define EVENT_DEBUG(fmt, ...) DEBUG(fmt, __VA_ARGS__) - -static int virEventInterruptLocked(void); - -/* State for a single file handle being monitored */ -struct virEventHandle { - int watch; - int fd; - int events; - virEventHandleCallback cb; - virFreeCallback ff; - void *opaque; - int deleted; -}; - -/* State for a single timer being generated */ -struct virEventTimeout { - int timer; - int frequency; - unsigned long long expiresAt; - virEventTimeoutCallback cb; - virFreeCallback ff; - void *opaque; - int deleted; -}; - -/* Allocate extra slots for virEventHandle/virEventTimeout - records in this multiple */ -#define EVENT_ALLOC_EXTENT 10 - -/* State for the main event loop */ -struct virEventLoop { - virMutex lock; - int running; - virThread leader; - int wakeupfd[2]; - size_t handlesCount; - size_t handlesAlloc; - struct virEventHandle *handles; - size_t timeoutsCount; - size_t timeoutsAlloc; - struct virEventTimeout *timeouts; -}; - -/* Only have one event loop */ -static struct virEventLoop eventLoop; - -/* Unique ID for the next FD watch to be registered */ -static int nextWatch = 1; - -/* Unique ID for the next timer to be registered */ -static int nextTimer = 1; - -/* - * Register a callback for monitoring file handle events. - * NB, it *must* be safe to call this from within a callback - * For this reason we only ever append to existing list. - */ -int virEventAddHandleImpl(int fd, int events, - virEventHandleCallback cb, - void *opaque, - virFreeCallback ff) { - int watch; - EVENT_DEBUG("Add handle fd=%d events=%d cb=%p opaque=%p", fd, events, cb, opaque); - virMutexLock(&eventLoop.lock); - if (eventLoop.handlesCount == eventLoop.handlesAlloc) { - EVENT_DEBUG("Used %zu handle slots, adding at least %d more", - eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); - if (VIR_RESIZE_N(eventLoop.handles, eventLoop.handlesAlloc, - eventLoop.handlesCount, EVENT_ALLOC_EXTENT) < 0) { - virMutexUnlock(&eventLoop.lock); - return -1; - } - } - - watch = nextWatch++; - - eventLoop.handles[eventLoop.handlesCount].watch = watch; - eventLoop.handles[eventLoop.handlesCount].fd = fd; - eventLoop.handles[eventLoop.handlesCount].events = - virEventHandleTypeToPollEvent(events); - eventLoop.handles[eventLoop.handlesCount].cb = cb; - eventLoop.handles[eventLoop.handlesCount].ff = ff; - eventLoop.handles[eventLoop.handlesCount].opaque = opaque; - eventLoop.handles[eventLoop.handlesCount].deleted = 0; - - eventLoop.handlesCount++; - - virEventInterruptLocked(); - virMutexUnlock(&eventLoop.lock); - - return watch; -} - -void virEventUpdateHandleImpl(int watch, int events) { - int i; - EVENT_DEBUG("Update handle w=%d e=%d", watch, events); - - if (watch <= 0) { - VIR_WARN("Ignoring invalid update watch %d", watch); - return; - } - - virMutexLock(&eventLoop.lock); - for (i = 0 ; i < eventLoop.handlesCount ; i++) { - if (eventLoop.handles[i].watch == watch) { - eventLoop.handles[i].events = - virEventHandleTypeToPollEvent(events); - virEventInterruptLocked(); - break; - } - } - virMutexUnlock(&eventLoop.lock); -} - -/* - * Unregister a callback from a file handle - * NB, it *must* be safe to call this from within a callback - * For this reason we only ever set a flag in the existing list. - * Actual deletion will be done out-of-band - */ -int virEventRemoveHandleImpl(int watch) { - int i; - EVENT_DEBUG("Remove handle w=%d", watch); - - if (watch <= 0) { - VIR_WARN("Ignoring invalid remove watch %d", watch); - return -1; - } - - virMutexLock(&eventLoop.lock); - for (i = 0 ; i < eventLoop.handlesCount ; i++) { - if (eventLoop.handles[i].deleted) - continue; - - if (eventLoop.handles[i].watch == watch) { - EVENT_DEBUG("mark delete %d %d", i, eventLoop.handles[i].fd); - eventLoop.handles[i].deleted = 1; - virEventInterruptLocked(); - virMutexUnlock(&eventLoop.lock); - return 0; - } - } - virMutexUnlock(&eventLoop.lock); - return -1; -} - - -/* - * Register a callback for a timer event - * NB, it *must* be safe to call this from within a callback - * For this reason we only ever append to existing list. - */ -int virEventAddTimeoutImpl(int frequency, - virEventTimeoutCallback cb, - void *opaque, - virFreeCallback ff) { - struct timeval now; - int ret; - EVENT_DEBUG("Adding timer %d with %d ms freq", nextTimer, frequency); - if (gettimeofday(&now, NULL) < 0) { - return -1; - } - - virMutexLock(&eventLoop.lock); - if (eventLoop.timeoutsCount == eventLoop.timeoutsAlloc) { - EVENT_DEBUG("Used %zu timeout slots, adding at least %d more", - eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); - if (VIR_RESIZE_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, - eventLoop.timeoutsCount, EVENT_ALLOC_EXTENT) < 0) { - virMutexUnlock(&eventLoop.lock); - return -1; - } - } - - eventLoop.timeouts[eventLoop.timeoutsCount].timer = nextTimer++; - eventLoop.timeouts[eventLoop.timeoutsCount].frequency = frequency; - eventLoop.timeouts[eventLoop.timeoutsCount].cb = cb; - eventLoop.timeouts[eventLoop.timeoutsCount].ff = ff; - eventLoop.timeouts[eventLoop.timeoutsCount].opaque = opaque; - eventLoop.timeouts[eventLoop.timeoutsCount].deleted = 0; - eventLoop.timeouts[eventLoop.timeoutsCount].expiresAt = - frequency >= 0 ? frequency + - (((unsigned long long)now.tv_sec)*1000) + - (((unsigned long long)now.tv_usec)/1000) : 0; - - eventLoop.timeoutsCount++; - ret = nextTimer-1; - virEventInterruptLocked(); - virMutexUnlock(&eventLoop.lock); - return ret; -} - -void virEventUpdateTimeoutImpl(int timer, int frequency) { - struct timeval tv; - int i; - EVENT_DEBUG("Updating timer %d timeout with %d ms freq", timer, frequency); - - if (timer <= 0) { - VIR_WARN("Ignoring invalid update timer %d", timer); - return; - } - - if (gettimeofday(&tv, NULL) < 0) { - return; - } - - virMutexLock(&eventLoop.lock); - for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { - if (eventLoop.timeouts[i].timer == timer) { - eventLoop.timeouts[i].frequency = frequency; - eventLoop.timeouts[i].expiresAt = - frequency >= 0 ? frequency + - (((unsigned long long)tv.tv_sec)*1000) + - (((unsigned long long)tv.tv_usec)/1000) : 0; - virEventInterruptLocked(); - break; - } - } - virMutexUnlock(&eventLoop.lock); -} - -/* - * Unregister a callback for a timer - * NB, it *must* be safe to call this from within a callback - * For this reason we only ever set a flag in the existing list. - * Actual deletion will be done out-of-band - */ -int virEventRemoveTimeoutImpl(int timer) { - int i; - EVENT_DEBUG("Remove timer %d", timer); - - if (timer <= 0) { - VIR_WARN("Ignoring invalid remove timer %d", timer); - return -1; - } - - virMutexLock(&eventLoop.lock); - for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { - if (eventLoop.timeouts[i].deleted) - continue; - - if (eventLoop.timeouts[i].timer == timer) { - eventLoop.timeouts[i].deleted = 1; - virEventInterruptLocked(); - virMutexUnlock(&eventLoop.lock); - return 0; - } - } - virMutexUnlock(&eventLoop.lock); - return -1; -} - -/* Iterates over all registered timeouts and determine which - * will be the first to expire. - * @timeout: filled with expiry time of soonest timer, or -1 if - * no timeout is pending - * returns: 0 on success, -1 on error - */ -static int virEventCalculateTimeout(int *timeout) { - unsigned long long then = 0; - int i; - EVENT_DEBUG("Calculate expiry of %zu timers", eventLoop.timeoutsCount); - /* Figure out if we need a timeout */ - for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { - if (eventLoop.timeouts[i].frequency < 0) - continue; - - EVENT_DEBUG("Got a timeout scheduled for %llu", eventLoop.timeouts[i].expiresAt); - if (then == 0 || - eventLoop.timeouts[i].expiresAt < then) - then = eventLoop.timeouts[i].expiresAt; - } - - /* Calculate how long we should wait for a timeout if needed */ - if (then > 0) { - struct timeval tv; - - if (gettimeofday(&tv, NULL) < 0) { - return -1; - } - - *timeout = then - - ((((unsigned long long)tv.tv_sec)*1000) + - (((unsigned long long)tv.tv_usec)/1000)); - - if (*timeout < 0) - *timeout = 0; - } else { - *timeout = -1; - } - - EVENT_DEBUG("Timeout at %llu due in %d ms", then, *timeout); - - return 0; -} - -/* - * Allocate a pollfd array containing data for all registered - * file handles. The caller must free the returned data struct - * returns: the pollfd array, or NULL on error - */ -static struct pollfd *virEventMakePollFDs(int *nfds) { - struct pollfd *fds; - int i; - - *nfds = 0; - for (i = 0 ; i < eventLoop.handlesCount ; i++) { - if (eventLoop.handles[i].events) - (*nfds)++; - } - - /* Setup the poll file handle data structs */ - if (VIR_ALLOC_N(fds, *nfds) < 0) - return NULL; - - *nfds = 0; - for (i = 0 ; i < eventLoop.handlesCount ; i++) { - EVENT_DEBUG("Prepare n=%d w=%d, f=%d e=%d", i, - eventLoop.handles[i].watch, - eventLoop.handles[i].fd, - eventLoop.handles[i].events); - if (!eventLoop.handles[i].events) - continue; - fds[*nfds].fd = eventLoop.handles[i].fd; - fds[*nfds].events = eventLoop.handles[i].events; - fds[*nfds].revents = 0; - (*nfds)++; - //EVENT_DEBUG("Wait for %d %d", eventLoop.handles[i].fd, eventLoop.handles[i].events); - } - - return fds; -} - - -/* - * Iterate over all timers and determine if any have expired. - * Invoke the user supplied callback for each timer whose - * expiry time is met, and schedule the next timeout. Does - * not try to 'catch up' on time if the actual expiry time - * was later than the requested time. - * - * This method must cope with new timers being registered - * by a callback, and must skip any timers marked as deleted. - * - * Returns 0 upon success, -1 if an error occurred - */ -static int virEventDispatchTimeouts(void) { - struct timeval tv; - unsigned long long now; - int i; - /* Save this now - it may be changed during dispatch */ - int ntimeouts = eventLoop.timeoutsCount; - DEBUG("Dispatch %d", ntimeouts); - - if (gettimeofday(&tv, NULL) < 0) { - return -1; - } - now = (((unsigned long long)tv.tv_sec)*1000) + - (((unsigned long long)tv.tv_usec)/1000); - - for (i = 0 ; i < ntimeouts ; i++) { - if (eventLoop.timeouts[i].deleted || eventLoop.timeouts[i].frequency < 0) - continue; - - /* Add 20ms fuzz so we don't pointlessly spin doing - * <10ms sleeps, particularly on kernels with low HZ - * it is fine that a timer expires 20ms earlier than - * requested - */ - if (eventLoop.timeouts[i].expiresAt <= (now+20)) { - virEventTimeoutCallback cb = eventLoop.timeouts[i].cb; - int timer = eventLoop.timeouts[i].timer; - void *opaque = eventLoop.timeouts[i].opaque; - eventLoop.timeouts[i].expiresAt = - now + eventLoop.timeouts[i].frequency; - - virMutexUnlock(&eventLoop.lock); - (cb)(timer, opaque); - virMutexLock(&eventLoop.lock); - } - } - return 0; -} - - -/* Iterate over all file handles and dispatch any which - * have pending events listed in the poll() data. Invoke - * the user supplied callback for each handle which has - * pending events - * - * This method must cope with new handles being registered - * by a callback, and must skip any handles marked as deleted. - * - * Returns 0 upon success, -1 if an error occurred - */ -static int virEventDispatchHandles(int nfds, struct pollfd *fds) { - int i, n; - DEBUG("Dispatch %d", nfds); - - /* NB, use nfds not eventLoop.handlesCount, because new - * fds might be added on end of list, and they're not - * in the fds array we've got */ - for (i = 0, n = 0 ; n < nfds && i < eventLoop.handlesCount ; n++) { - while ((eventLoop.handles[i].fd != fds[n].fd || - eventLoop.handles[i].events == 0) && - i < eventLoop.handlesCount) { - i++; - } - if (i == eventLoop.handlesCount) - break; - - DEBUG("i=%d w=%d", i, eventLoop.handles[i].watch); - if (eventLoop.handles[i].deleted) { - EVENT_DEBUG("Skip deleted n=%d w=%d f=%d", i, - eventLoop.handles[i].watch, eventLoop.handles[i].fd); - continue; - } - - if (fds[n].revents) { - virEventHandleCallback cb = eventLoop.handles[i].cb; - void *opaque = eventLoop.handles[i].opaque; - int hEvents = virPollEventToEventHandleType(fds[n].revents); - EVENT_DEBUG("Dispatch n=%d f=%d w=%d e=%d %p", i, - fds[n].fd, eventLoop.handles[i].watch, - fds[n].revents, eventLoop.handles[i].opaque); - virMutexUnlock(&eventLoop.lock); - (cb)(eventLoop.handles[i].watch, - fds[n].fd, hEvents, opaque); - virMutexLock(&eventLoop.lock); - } - } - - return 0; -} - - -/* Used post dispatch to actually remove any timers that - * were previously marked as deleted. This asynchronous - * cleanup is needed to make dispatch re-entrant safe. - */ -static int virEventCleanupTimeouts(void) { - int i; - DEBUG("Cleanup %zu", eventLoop.timeoutsCount); - - /* Remove deleted entries, shuffling down remaining - * entries as needed to form contiguous series - */ - for (i = 0 ; i < eventLoop.timeoutsCount ; ) { - if (!eventLoop.timeouts[i].deleted) { - i++; - continue; - } - - EVENT_DEBUG("Purging timeout %d with id %d", i, eventLoop.timeouts[i].timer); - if (eventLoop.timeouts[i].ff) - (eventLoop.timeouts[i].ff)(eventLoop.timeouts[i].opaque); - - if ((i+1) < eventLoop.timeoutsCount) { - memmove(eventLoop.timeouts+i, - eventLoop.timeouts+i+1, - sizeof(struct virEventTimeout)*(eventLoop.timeoutsCount-(i+1))); - } - eventLoop.timeoutsCount--; - } - - /* Release some memory if we've got a big chunk free */ - if ((eventLoop.timeoutsAlloc - EVENT_ALLOC_EXTENT) > eventLoop.timeoutsCount) { - EVENT_DEBUG("Releasing %zu out of %zu timeout slots used, releasing %d", - eventLoop.timeoutsCount, eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); - VIR_SHRINK_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, - EVENT_ALLOC_EXTENT); - } - return 0; -} - -/* Used post dispatch to actually remove any handles that - * were previously marked as deleted. This asynchronous - * cleanup is needed to make dispatch re-entrant safe. - */ -static int virEventCleanupHandles(void) { - int i; - DEBUG("Cleanup %zu", eventLoop.handlesCount); - - /* Remove deleted entries, shuffling down remaining - * entries as needed to form contiguous series - */ - for (i = 0 ; i < eventLoop.handlesCount ; ) { - if (!eventLoop.handles[i].deleted) { - i++; - continue; - } - - if (eventLoop.handles[i].ff) - (eventLoop.handles[i].ff)(eventLoop.handles[i].opaque); - - if ((i+1) < eventLoop.handlesCount) { - memmove(eventLoop.handles+i, - eventLoop.handles+i+1, - sizeof(struct virEventHandle)*(eventLoop.handlesCount-(i+1))); - } - eventLoop.handlesCount--; - } - - /* Release some memory if we've got a big chunk free */ - if ((eventLoop.handlesAlloc - EVENT_ALLOC_EXTENT) > eventLoop.handlesCount) { - EVENT_DEBUG("Releasing %zu out of %zu handles slots used, releasing %d", - eventLoop.handlesCount, eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); - VIR_SHRINK_N(eventLoop.handles, eventLoop.handlesAlloc, - EVENT_ALLOC_EXTENT); - } - return 0; -} - -/* - * Run a single iteration of the event loop, blocking until - * at least one file handle has an event, or a timer expires - */ -int virEventRunOnce(void) { - struct pollfd *fds = NULL; - int ret, timeout, nfds; - - virMutexLock(&eventLoop.lock); - eventLoop.running = 1; - virThreadSelf(&eventLoop.leader); - - if (virEventCleanupTimeouts() < 0 || - virEventCleanupHandles() < 0) - goto error; - - if (!(fds = virEventMakePollFDs(&nfds)) || - virEventCalculateTimeout(&timeout) < 0) - goto error; - - virMutexUnlock(&eventLoop.lock); - - retry: - EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout); - ret = poll(fds, nfds, timeout); - if (ret < 0) { - EVENT_DEBUG("Poll got error event %d", errno); - if (errno == EINTR) { - goto retry; - } - goto error_unlocked; - } - EVENT_DEBUG("Poll got %d event(s)", ret); - - virMutexLock(&eventLoop.lock); - if (virEventDispatchTimeouts() < 0) - goto error; - - if (ret > 0 && - virEventDispatchHandles(nfds, fds) < 0) - goto error; - - if (virEventCleanupTimeouts() < 0 || - virEventCleanupHandles() < 0) - goto error; - - eventLoop.running = 0; - virMutexUnlock(&eventLoop.lock); - VIR_FREE(fds); - return 0; - -error: - virMutexUnlock(&eventLoop.lock); -error_unlocked: - VIR_FREE(fds); - return -1; -} - - -static void virEventHandleWakeup(int watch ATTRIBUTE_UNUSED, - int fd, - int events ATTRIBUTE_UNUSED, - void *opaque ATTRIBUTE_UNUSED) -{ - char c; - virMutexLock(&eventLoop.lock); - ignore_value(saferead(fd, &c, sizeof(c))); - virMutexUnlock(&eventLoop.lock); -} - -int virEventInit(void) -{ - if (virMutexInit(&eventLoop.lock) < 0) - return -1; - - if (pipe(eventLoop.wakeupfd) < 0 || - virSetNonBlock(eventLoop.wakeupfd[0]) < 0 || - virSetNonBlock(eventLoop.wakeupfd[1]) < 0 || - virSetCloseExec(eventLoop.wakeupfd[0]) < 0 || - virSetCloseExec(eventLoop.wakeupfd[1]) < 0) - return -1; - - if (virEventAddHandleImpl(eventLoop.wakeupfd[0], - VIR_EVENT_HANDLE_READABLE, - virEventHandleWakeup, NULL, NULL) < 0) - return -1; - - return 0; -} - -static int virEventInterruptLocked(void) -{ - char c = '\0'; - - if (!eventLoop.running || - virThreadIsSelf(&eventLoop.leader)) { - VIR_DEBUG("Skip interrupt, %d %d", eventLoop.running, - virThreadID(&eventLoop.leader)); - return 0; - } - - VIR_DEBUG0("Interrupting"); - if (safewrite(eventLoop.wakeupfd[1], &c, sizeof(c)) != sizeof(c)) - return -1; - return 0; -} - -int virEventInterrupt(void) -{ - int ret; - virMutexLock(&eventLoop.lock); - ret = virEventInterruptLocked(); - virMutexUnlock(&eventLoop.lock); - return ret; -} - -int -virEventHandleTypeToPollEvent(int events) -{ - int ret = 0; - if(events & VIR_EVENT_HANDLE_READABLE) - ret |= POLLIN; - if(events & VIR_EVENT_HANDLE_WRITABLE) - ret |= POLLOUT; - if(events & VIR_EVENT_HANDLE_ERROR) - ret |= POLLERR; - if(events & VIR_EVENT_HANDLE_HANGUP) - ret |= POLLHUP; - return ret; -} - -int -virPollEventToEventHandleType(int events) -{ - int ret = 0; - if(events & POLLIN) - ret |= VIR_EVENT_HANDLE_READABLE; - if(events & POLLOUT) - ret |= VIR_EVENT_HANDLE_WRITABLE; - if(events & POLLERR) - ret |= VIR_EVENT_HANDLE_ERROR; - if(events & POLLNVAL) /* Treat NVAL as error, since libvirt doesn't distinguish */ - ret |= VIR_EVENT_HANDLE_ERROR; - if(events & POLLHUP) - ret |= VIR_EVENT_HANDLE_HANGUP; - return ret; -} diff --git a/daemon/event.h b/daemon/event.h deleted file mode 100644 index dc03589..0000000 --- a/daemon/event.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * event.h: event loop for monitoring file handles - * - * Copyright (C) 2007 Daniel P. Berrange - * Copyright (C) 2007 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Author: Daniel P. Berrange <berrange@redhat.com> - */ - -#ifndef __VIRTD_EVENT_H__ -# define __VIRTD_EVENT_H__ - -# include "internal.h" - -/** - * virEventAddHandleImpl: register a callback for monitoring file handle events - * - * @fd: file handle to monitor for events - * @events: bitset of events to watch from POLLnnn constants - * @cb: callback to invoke when an event occurs - * @opaque: user data to pass to callback - * - * returns -1 if the file handle cannot be registered, 0 upon success - */ -int virEventAddHandleImpl(int fd, int events, - virEventHandleCallback cb, - void *opaque, - virFreeCallback ff); - -/** - * virEventUpdateHandleImpl: change event set for a monitored file handle - * - * @watch: watch whose handle to update - * @events: bitset of events to watch from POLLnnn constants - * - * Will not fail if fd exists - */ -void virEventUpdateHandleImpl(int watch, int events); - -/** - * virEventRemoveHandleImpl: unregister a callback from a file handle - * - * @watch: watch whose handle to remove - * - * returns -1 if the file handle was not registered, 0 upon success - */ -int virEventRemoveHandleImpl(int watch); - -/** - * virEventAddTimeoutImpl: register a callback for a timer event - * - * @frequency: time between events in milliseconds - * @cb: callback to invoke when an event occurs - * @opaque: user data to pass to callback - * - * Setting frequency to -1 will disable the timer. Setting the frequency - * to zero will cause it to fire on every event loop iteration. - * - * returns -1 if the file handle cannot be registered, a positive - * integer timer id upon success - */ -int virEventAddTimeoutImpl(int frequency, - virEventTimeoutCallback cb, - void *opaque, - virFreeCallback ff); - -/** - * virEventUpdateTimeoutImpl: change frequency for a timer - * - * @timer: timer id to change - * @frequency: time between events in milliseconds - * - * Setting frequency to -1 will disable the timer. Setting the frequency - * to zero will cause it to fire on every event loop iteration. - * - * Will not fail if timer exists - */ -void virEventUpdateTimeoutImpl(int timer, int frequency); - -/** - * virEventRemoveTimeoutImpl: unregister a callback for a timer - * - * @timer: the timer id to remove - * - * returns -1 if the timer was not registered, 0 upon success - */ -int virEventRemoveTimeoutImpl(int timer); - -/** - * virEventInit: Initialize the event loop - * - * returns -1 if initialization failed - */ -int virEventInit(void); - -/** - * virEventRunOnce: run a single iteration of the event loop. - * - * Blocks the caller until at least one file handle has an - * event or the first timer expires. - * - * returns -1 if the event monitoring failed - */ -int virEventRunOnce(void); - -int -virEventHandleTypeToPollEvent(int events); -int -virPollEventToEventHandleType(int events); - - -/** - * virEventInterrupt: wakeup any thread waiting in poll() - * - * return -1 if wakup failed - */ -int virEventInterrupt(void); - - -#endif /* __VIRTD_EVENT_H__ */ diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index 2df9337..554d78b 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -62,7 +62,7 @@ #include "uuid.h" #include "remote_driver.h" #include "conf.h" -#include "event.h" +#include "event_impl.h" #include "memory.h" #include "stream.h" #include "hooks.h" diff --git a/src/Makefile.am b/src/Makefile.am index 41d4b34..1eefd39 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -79,7 +79,8 @@ UTIL_SOURCES = \ util/util.c util/util.h \ util/xml.c util/xml.h \ util/virtaudit.c util/virtaudit.h \ - util/virterror.c util/virterror_internal.h + util/virterror.c util/virterror_internal.h \ + util/event_impl.c util/event_impl.h EXTRA_DIST += util/threads-pthread.c util/threads-win32.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0e3033d..21829fa 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -377,6 +377,20 @@ virEventUpdateHandle; virEventUpdateTimeout; +# event_impl.h +virEventAddHandleImpl; +virEventUpdateHandleImpl; +virEventRemoveHandleImpl; +virEventAddTimeoutImpl; +virEventUpdateTimeoutImpl; +virEventRemoveTimeoutImpl; +virEventInit; +virEventRunOnce; +virEventHandleTypeToPollEvent; +virPollEventToEventHandleType; +virEventInterrupt; + + # fdstream.h virFDStreamOpen; virFDStreamConnectUNIX; diff --git a/src/util/event_impl.c b/src/util/event_impl.c new file mode 100644 index 0000000..4876088 --- /dev/null +++ b/src/util/event_impl.c @@ -0,0 +1,700 @@ +/* + * event.c: event loop for monitoring file handles + * + * Copyright (C) 2007, 2010 Red Hat, Inc. + * Copyright (C) 2007 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <string.h> +#include <poll.h> +#include <sys/time.h> +#include <errno.h> +#include <unistd.h> + +#include "threads.h" +#include "logging.h" +#include "event_impl.h" +#include "memory.h" +#include "util.h" +#include "ignore-value.h" + +#define EVENT_DEBUG(fmt, ...) DEBUG(fmt, __VA_ARGS__) + +static int virEventInterruptLocked(void); + +/* State for a single file handle being monitored */ +struct virEventHandle { + int watch; + int fd; + int events; + virEventHandleCallback cb; + virFreeCallback ff; + void *opaque; + int deleted; +}; + +/* State for a single timer being generated */ +struct virEventTimeout { + int timer; + int frequency; + unsigned long long expiresAt; + virEventTimeoutCallback cb; + virFreeCallback ff; + void *opaque; + int deleted; +}; + +/* Allocate extra slots for virEventHandle/virEventTimeout + records in this multiple */ +#define EVENT_ALLOC_EXTENT 10 + +/* State for the main event loop */ +struct virEventLoop { + virMutex lock; + int running; + virThread leader; + int wakeupfd[2]; + size_t handlesCount; + size_t handlesAlloc; + struct virEventHandle *handles; + size_t timeoutsCount; + size_t timeoutsAlloc; + struct virEventTimeout *timeouts; +}; + +/* Only have one event loop */ +static struct virEventLoop eventLoop; + +/* Unique ID for the next FD watch to be registered */ +static int nextWatch = 1; + +/* Unique ID for the next timer to be registered */ +static int nextTimer = 1; + +/* + * Register a callback for monitoring file handle events. + * NB, it *must* be safe to call this from within a callback + * For this reason we only ever append to existing list. + */ +int virEventAddHandleImpl(int fd, int events, + virEventHandleCallback cb, + void *opaque, + virFreeCallback ff) { + int watch; + EVENT_DEBUG("Add handle fd=%d events=%d cb=%p opaque=%p", fd, events, cb, opaque); + virMutexLock(&eventLoop.lock); + if (eventLoop.handlesCount == eventLoop.handlesAlloc) { + EVENT_DEBUG("Used %zu handle slots, adding at least %d more", + eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); + if (VIR_RESIZE_N(eventLoop.handles, eventLoop.handlesAlloc, + eventLoop.handlesCount, EVENT_ALLOC_EXTENT) < 0) { + virMutexUnlock(&eventLoop.lock); + return -1; + } + } + + watch = nextWatch++; + + eventLoop.handles[eventLoop.handlesCount].watch = watch; + eventLoop.handles[eventLoop.handlesCount].fd = fd; + eventLoop.handles[eventLoop.handlesCount].events = + virEventHandleTypeToPollEvent(events); + eventLoop.handles[eventLoop.handlesCount].cb = cb; + eventLoop.handles[eventLoop.handlesCount].ff = ff; + eventLoop.handles[eventLoop.handlesCount].opaque = opaque; + eventLoop.handles[eventLoop.handlesCount].deleted = 0; + + eventLoop.handlesCount++; + + virEventInterruptLocked(); + virMutexUnlock(&eventLoop.lock); + + return watch; +} + +void virEventUpdateHandleImpl(int watch, int events) { + int i; + EVENT_DEBUG("Update handle w=%d e=%d", watch, events); + + if (watch <= 0) { + VIR_WARN("Ignoring invalid update watch %d", watch); + return; + } + + virMutexLock(&eventLoop.lock); + for (i = 0 ; i < eventLoop.handlesCount ; i++) { + if (eventLoop.handles[i].watch == watch) { + eventLoop.handles[i].events = + virEventHandleTypeToPollEvent(events); + virEventInterruptLocked(); + break; + } + } + virMutexUnlock(&eventLoop.lock); +} + +/* + * Unregister a callback from a file handle + * NB, it *must* be safe to call this from within a callback + * For this reason we only ever set a flag in the existing list. + * Actual deletion will be done out-of-band + */ +int virEventRemoveHandleImpl(int watch) { + int i; + EVENT_DEBUG("Remove handle w=%d", watch); + + if (watch <= 0) { + VIR_WARN("Ignoring invalid remove watch %d", watch); + return -1; + } + + virMutexLock(&eventLoop.lock); + for (i = 0 ; i < eventLoop.handlesCount ; i++) { + if (eventLoop.handles[i].deleted) + continue; + + if (eventLoop.handles[i].watch == watch) { + EVENT_DEBUG("mark delete %d %d", i, eventLoop.handles[i].fd); + eventLoop.handles[i].deleted = 1; + virEventInterruptLocked(); + virMutexUnlock(&eventLoop.lock); + return 0; + } + } + virMutexUnlock(&eventLoop.lock); + return -1; +} + + +/* + * Register a callback for a timer event + * NB, it *must* be safe to call this from within a callback + * For this reason we only ever append to existing list. + */ +int virEventAddTimeoutImpl(int frequency, + virEventTimeoutCallback cb, + void *opaque, + virFreeCallback ff) { + struct timeval now; + int ret; + EVENT_DEBUG("Adding timer %d with %d ms freq", nextTimer, frequency); + if (gettimeofday(&now, NULL) < 0) { + return -1; + } + + virMutexLock(&eventLoop.lock); + if (eventLoop.timeoutsCount == eventLoop.timeoutsAlloc) { + EVENT_DEBUG("Used %zu timeout slots, adding at least %d more", + eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); + if (VIR_RESIZE_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, + eventLoop.timeoutsCount, EVENT_ALLOC_EXTENT) < 0) { + virMutexUnlock(&eventLoop.lock); + return -1; + } + } + + eventLoop.timeouts[eventLoop.timeoutsCount].timer = nextTimer++; + eventLoop.timeouts[eventLoop.timeoutsCount].frequency = frequency; + eventLoop.timeouts[eventLoop.timeoutsCount].cb = cb; + eventLoop.timeouts[eventLoop.timeoutsCount].ff = ff; + eventLoop.timeouts[eventLoop.timeoutsCount].opaque = opaque; + eventLoop.timeouts[eventLoop.timeoutsCount].deleted = 0; + eventLoop.timeouts[eventLoop.timeoutsCount].expiresAt = + frequency >= 0 ? frequency + + (((unsigned long long)now.tv_sec)*1000) + + (((unsigned long long)now.tv_usec)/1000) : 0; + + eventLoop.timeoutsCount++; + ret = nextTimer-1; + virEventInterruptLocked(); + virMutexUnlock(&eventLoop.lock); + return ret; +} + +void virEventUpdateTimeoutImpl(int timer, int frequency) { + struct timeval tv; + int i; + EVENT_DEBUG("Updating timer %d timeout with %d ms freq", timer, frequency); + + if (timer <= 0) { + VIR_WARN("Ignoring invalid update timer %d", timer); + return; + } + + if (gettimeofday(&tv, NULL) < 0) { + return; + } + + virMutexLock(&eventLoop.lock); + for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { + if (eventLoop.timeouts[i].timer == timer) { + eventLoop.timeouts[i].frequency = frequency; + eventLoop.timeouts[i].expiresAt = + frequency >= 0 ? frequency + + (((unsigned long long)tv.tv_sec)*1000) + + (((unsigned long long)tv.tv_usec)/1000) : 0; + virEventInterruptLocked(); + break; + } + } + virMutexUnlock(&eventLoop.lock); +} + +/* + * Unregister a callback for a timer + * NB, it *must* be safe to call this from within a callback + * For this reason we only ever set a flag in the existing list. + * Actual deletion will be done out-of-band + */ +int virEventRemoveTimeoutImpl(int timer) { + int i; + EVENT_DEBUG("Remove timer %d", timer); + + if (timer <= 0) { + VIR_WARN("Ignoring invalid remove timer %d", timer); + return -1; + } + + virMutexLock(&eventLoop.lock); + for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { + if (eventLoop.timeouts[i].deleted) + continue; + + if (eventLoop.timeouts[i].timer == timer) { + eventLoop.timeouts[i].deleted = 1; + virEventInterruptLocked(); + virMutexUnlock(&eventLoop.lock); + return 0; + } + } + virMutexUnlock(&eventLoop.lock); + return -1; +} + +/* Iterates over all registered timeouts and determine which + * will be the first to expire. + * @timeout: filled with expiry time of soonest timer, or -1 if + * no timeout is pending + * returns: 0 on success, -1 on error + */ +static int virEventCalculateTimeout(int *timeout) { + unsigned long long then = 0; + int i; + EVENT_DEBUG("Calculate expiry of %zu timers", eventLoop.timeoutsCount); + /* Figure out if we need a timeout */ + for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { + if (eventLoop.timeouts[i].frequency < 0) + continue; + + EVENT_DEBUG("Got a timeout scheduled for %llu", eventLoop.timeouts[i].expiresAt); + if (then == 0 || + eventLoop.timeouts[i].expiresAt < then) + then = eventLoop.timeouts[i].expiresAt; + } + + /* Calculate how long we should wait for a timeout if needed */ + if (then > 0) { + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + return -1; + } + + *timeout = then - + ((((unsigned long long)tv.tv_sec)*1000) + + (((unsigned long long)tv.tv_usec)/1000)); + + if (*timeout < 0) + *timeout = 0; + } else { + *timeout = -1; + } + + EVENT_DEBUG("Timeout at %llu due in %d ms", then, *timeout); + + return 0; +} + +/* + * Allocate a pollfd array containing data for all registered + * file handles. The caller must free the returned data struct + * returns: the pollfd array, or NULL on error + */ +static struct pollfd *virEventMakePollFDs(int *nfds) { + struct pollfd *fds; + int i; + + *nfds = 0; + for (i = 0 ; i < eventLoop.handlesCount ; i++) { + if (eventLoop.handles[i].events) + (*nfds)++; + } + + /* Setup the poll file handle data structs */ + if (VIR_ALLOC_N(fds, *nfds) < 0) + return NULL; + + *nfds = 0; + for (i = 0 ; i < eventLoop.handlesCount ; i++) { + EVENT_DEBUG("Prepare n=%d w=%d, f=%d e=%d", i, + eventLoop.handles[i].watch, + eventLoop.handles[i].fd, + eventLoop.handles[i].events); + if (!eventLoop.handles[i].events) + continue; + fds[*nfds].fd = eventLoop.handles[i].fd; + fds[*nfds].events = eventLoop.handles[i].events; + fds[*nfds].revents = 0; + (*nfds)++; + //EVENT_DEBUG("Wait for %d %d", eventLoop.handles[i].fd, eventLoop.handles[i].events); + } + + return fds; +} + + +/* + * Iterate over all timers and determine if any have expired. + * Invoke the user supplied callback for each timer whose + * expiry time is met, and schedule the next timeout. Does + * not try to 'catch up' on time if the actual expiry time + * was later than the requested time. + * + * This method must cope with new timers being registered + * by a callback, and must skip any timers marked as deleted. + * + * Returns 0 upon success, -1 if an error occurred + */ +static int virEventDispatchTimeouts(void) { + struct timeval tv; + unsigned long long now; + int i; + /* Save this now - it may be changed during dispatch */ + int ntimeouts = eventLoop.timeoutsCount; + DEBUG("Dispatch %d", ntimeouts); + + if (gettimeofday(&tv, NULL) < 0) { + return -1; + } + now = (((unsigned long long)tv.tv_sec)*1000) + + (((unsigned long long)tv.tv_usec)/1000); + + for (i = 0 ; i < ntimeouts ; i++) { + if (eventLoop.timeouts[i].deleted || eventLoop.timeouts[i].frequency < 0) + continue; + + /* Add 20ms fuzz so we don't pointlessly spin doing + * <10ms sleeps, particularly on kernels with low HZ + * it is fine that a timer expires 20ms earlier than + * requested + */ + if (eventLoop.timeouts[i].expiresAt <= (now+20)) { + virEventTimeoutCallback cb = eventLoop.timeouts[i].cb; + int timer = eventLoop.timeouts[i].timer; + void *opaque = eventLoop.timeouts[i].opaque; + eventLoop.timeouts[i].expiresAt = + now + eventLoop.timeouts[i].frequency; + + virMutexUnlock(&eventLoop.lock); + (cb)(timer, opaque); + virMutexLock(&eventLoop.lock); + } + } + return 0; +} + + +/* Iterate over all file handles and dispatch any which + * have pending events listed in the poll() data. Invoke + * the user supplied callback for each handle which has + * pending events + * + * This method must cope with new handles being registered + * by a callback, and must skip any handles marked as deleted. + * + * Returns 0 upon success, -1 if an error occurred + */ +static int virEventDispatchHandles(int nfds, struct pollfd *fds) { + int i, n; + DEBUG("Dispatch %d", nfds); + + /* NB, use nfds not eventLoop.handlesCount, because new + * fds might be added on end of list, and they're not + * in the fds array we've got */ + for (i = 0, n = 0 ; n < nfds && i < eventLoop.handlesCount ; n++) { + while ((eventLoop.handles[i].fd != fds[n].fd || + eventLoop.handles[i].events == 0) && + i < eventLoop.handlesCount) { + i++; + } + if (i == eventLoop.handlesCount) + break; + + DEBUG("i=%d w=%d", i, eventLoop.handles[i].watch); + if (eventLoop.handles[i].deleted) { + EVENT_DEBUG("Skip deleted n=%d w=%d f=%d", i, + eventLoop.handles[i].watch, eventLoop.handles[i].fd); + continue; + } + + if (fds[n].revents) { + virEventHandleCallback cb = eventLoop.handles[i].cb; + void *opaque = eventLoop.handles[i].opaque; + int hEvents = virPollEventToEventHandleType(fds[n].revents); + EVENT_DEBUG("Dispatch n=%d f=%d w=%d e=%d %p", i, + fds[n].fd, eventLoop.handles[i].watch, + fds[n].revents, eventLoop.handles[i].opaque); + virMutexUnlock(&eventLoop.lock); + (cb)(eventLoop.handles[i].watch, + fds[n].fd, hEvents, opaque); + virMutexLock(&eventLoop.lock); + } + } + + return 0; +} + + +/* Used post dispatch to actually remove any timers that + * were previously marked as deleted. This asynchronous + * cleanup is needed to make dispatch re-entrant safe. + */ +static int virEventCleanupTimeouts(void) { + int i; + DEBUG("Cleanup %zu", eventLoop.timeoutsCount); + + /* Remove deleted entries, shuffling down remaining + * entries as needed to form contiguous series + */ + for (i = 0 ; i < eventLoop.timeoutsCount ; ) { + if (!eventLoop.timeouts[i].deleted) { + i++; + continue; + } + + EVENT_DEBUG("Purging timeout %d with id %d", i, eventLoop.timeouts[i].timer); + if (eventLoop.timeouts[i].ff) + (eventLoop.timeouts[i].ff)(eventLoop.timeouts[i].opaque); + + if ((i+1) < eventLoop.timeoutsCount) { + memmove(eventLoop.timeouts+i, + eventLoop.timeouts+i+1, + sizeof(struct virEventTimeout)*(eventLoop.timeoutsCount-(i+1))); + } + eventLoop.timeoutsCount--; + } + + /* Release some memory if we've got a big chunk free */ + if ((eventLoop.timeoutsAlloc - EVENT_ALLOC_EXTENT) > eventLoop.timeoutsCount) { + EVENT_DEBUG("Releasing %zu out of %zu timeout slots used, releasing %d", + eventLoop.timeoutsCount, eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); + VIR_SHRINK_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, + EVENT_ALLOC_EXTENT); + } + return 0; +} + +/* Used post dispatch to actually remove any handles that + * were previously marked as deleted. This asynchronous + * cleanup is needed to make dispatch re-entrant safe. + */ +static int virEventCleanupHandles(void) { + int i; + DEBUG("Cleanup %zu", eventLoop.handlesCount); + + /* Remove deleted entries, shuffling down remaining + * entries as needed to form contiguous series + */ + for (i = 0 ; i < eventLoop.handlesCount ; ) { + if (!eventLoop.handles[i].deleted) { + i++; + continue; + } + + if (eventLoop.handles[i].ff) + (eventLoop.handles[i].ff)(eventLoop.handles[i].opaque); + + if ((i+1) < eventLoop.handlesCount) { + memmove(eventLoop.handles+i, + eventLoop.handles+i+1, + sizeof(struct virEventHandle)*(eventLoop.handlesCount-(i+1))); + } + eventLoop.handlesCount--; + } + + /* Release some memory if we've got a big chunk free */ + if ((eventLoop.handlesAlloc - EVENT_ALLOC_EXTENT) > eventLoop.handlesCount) { + EVENT_DEBUG("Releasing %zu out of %zu handles slots used, releasing %d", + eventLoop.handlesCount, eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); + VIR_SHRINK_N(eventLoop.handles, eventLoop.handlesAlloc, + EVENT_ALLOC_EXTENT); + } + return 0; +} + +/* + * Run a single iteration of the event loop, blocking until + * at least one file handle has an event, or a timer expires + */ +int virEventRunOnce(void) { + struct pollfd *fds = NULL; + int ret, timeout, nfds; + + virMutexLock(&eventLoop.lock); + eventLoop.running = 1; + virThreadSelf(&eventLoop.leader); + + if (virEventCleanupTimeouts() < 0 || + virEventCleanupHandles() < 0) + goto error; + + if (!(fds = virEventMakePollFDs(&nfds)) || + virEventCalculateTimeout(&timeout) < 0) + goto error; + + virMutexUnlock(&eventLoop.lock); + + retry: + EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout); + ret = poll(fds, nfds, timeout); + if (ret < 0) { + EVENT_DEBUG("Poll got error event %d", errno); + if (errno == EINTR) { + goto retry; + } + goto error_unlocked; + } + EVENT_DEBUG("Poll got %d event(s)", ret); + + virMutexLock(&eventLoop.lock); + if (virEventDispatchTimeouts() < 0) + goto error; + + if (ret > 0 && + virEventDispatchHandles(nfds, fds) < 0) + goto error; + + if (virEventCleanupTimeouts() < 0 || + virEventCleanupHandles() < 0) + goto error; + + eventLoop.running = 0; + virMutexUnlock(&eventLoop.lock); + VIR_FREE(fds); + return 0; + +error: + virMutexUnlock(&eventLoop.lock); +error_unlocked: + VIR_FREE(fds); + return -1; +} + + +static void virEventHandleWakeup(int watch ATTRIBUTE_UNUSED, + int fd, + int events ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + char c; + virMutexLock(&eventLoop.lock); + ignore_value(saferead(fd, &c, sizeof(c))); + virMutexUnlock(&eventLoop.lock); +} + +int virEventInit(void) +{ + if (virMutexInit(&eventLoop.lock) < 0) + return -1; + + if (pipe(eventLoop.wakeupfd) < 0 || + virSetNonBlock(eventLoop.wakeupfd[0]) < 0 || + virSetNonBlock(eventLoop.wakeupfd[1]) < 0 || + virSetCloseExec(eventLoop.wakeupfd[0]) < 0 || + virSetCloseExec(eventLoop.wakeupfd[1]) < 0) + return -1; + + if (virEventAddHandleImpl(eventLoop.wakeupfd[0], + VIR_EVENT_HANDLE_READABLE, + virEventHandleWakeup, NULL, NULL) < 0) + return -1; + + return 0; +} + +static int virEventInterruptLocked(void) +{ + char c = '\0'; + + if (!eventLoop.running || + virThreadIsSelf(&eventLoop.leader)) { + VIR_DEBUG("Skip interrupt, %d %d", eventLoop.running, + virThreadID(&eventLoop.leader)); + return 0; + } + + VIR_DEBUG0("Interrupting"); + if (safewrite(eventLoop.wakeupfd[1], &c, sizeof(c)) != sizeof(c)) + return -1; + return 0; +} + +int virEventInterrupt(void) +{ + int ret; + virMutexLock(&eventLoop.lock); + ret = virEventInterruptLocked(); + virMutexUnlock(&eventLoop.lock); + return ret; +} + +int +virEventHandleTypeToPollEvent(int events) +{ + int ret = 0; + if(events & VIR_EVENT_HANDLE_READABLE) + ret |= POLLIN; + if(events & VIR_EVENT_HANDLE_WRITABLE) + ret |= POLLOUT; + if(events & VIR_EVENT_HANDLE_ERROR) + ret |= POLLERR; + if(events & VIR_EVENT_HANDLE_HANGUP) + ret |= POLLHUP; + return ret; +} + +int +virPollEventToEventHandleType(int events) +{ + int ret = 0; + if(events & POLLIN) + ret |= VIR_EVENT_HANDLE_READABLE; + if(events & POLLOUT) + ret |= VIR_EVENT_HANDLE_WRITABLE; + if(events & POLLERR) + ret |= VIR_EVENT_HANDLE_ERROR; + if(events & POLLNVAL) /* Treat NVAL as error, since libvirt doesn't distinguish */ + ret |= VIR_EVENT_HANDLE_ERROR; + if(events & POLLHUP) + ret |= VIR_EVENT_HANDLE_HANGUP; + return ret; +} diff --git a/src/util/event_impl.h b/src/util/event_impl.h new file mode 100644 index 0000000..dc03589 --- /dev/null +++ b/src/util/event_impl.h @@ -0,0 +1,134 @@ +/* + * event.h: event loop for monitoring file handles + * + * Copyright (C) 2007 Daniel P. Berrange + * Copyright (C) 2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __VIRTD_EVENT_H__ +# define __VIRTD_EVENT_H__ + +# include "internal.h" + +/** + * virEventAddHandleImpl: register a callback for monitoring file handle events + * + * @fd: file handle to monitor for events + * @events: bitset of events to watch from POLLnnn constants + * @cb: callback to invoke when an event occurs + * @opaque: user data to pass to callback + * + * returns -1 if the file handle cannot be registered, 0 upon success + */ +int virEventAddHandleImpl(int fd, int events, + virEventHandleCallback cb, + void *opaque, + virFreeCallback ff); + +/** + * virEventUpdateHandleImpl: change event set for a monitored file handle + * + * @watch: watch whose handle to update + * @events: bitset of events to watch from POLLnnn constants + * + * Will not fail if fd exists + */ +void virEventUpdateHandleImpl(int watch, int events); + +/** + * virEventRemoveHandleImpl: unregister a callback from a file handle + * + * @watch: watch whose handle to remove + * + * returns -1 if the file handle was not registered, 0 upon success + */ +int virEventRemoveHandleImpl(int watch); + +/** + * virEventAddTimeoutImpl: register a callback for a timer event + * + * @frequency: time between events in milliseconds + * @cb: callback to invoke when an event occurs + * @opaque: user data to pass to callback + * + * Setting frequency to -1 will disable the timer. Setting the frequency + * to zero will cause it to fire on every event loop iteration. + * + * returns -1 if the file handle cannot be registered, a positive + * integer timer id upon success + */ +int virEventAddTimeoutImpl(int frequency, + virEventTimeoutCallback cb, + void *opaque, + virFreeCallback ff); + +/** + * virEventUpdateTimeoutImpl: change frequency for a timer + * + * @timer: timer id to change + * @frequency: time between events in milliseconds + * + * Setting frequency to -1 will disable the timer. Setting the frequency + * to zero will cause it to fire on every event loop iteration. + * + * Will not fail if timer exists + */ +void virEventUpdateTimeoutImpl(int timer, int frequency); + +/** + * virEventRemoveTimeoutImpl: unregister a callback for a timer + * + * @timer: the timer id to remove + * + * returns -1 if the timer was not registered, 0 upon success + */ +int virEventRemoveTimeoutImpl(int timer); + +/** + * virEventInit: Initialize the event loop + * + * returns -1 if initialization failed + */ +int virEventInit(void); + +/** + * virEventRunOnce: run a single iteration of the event loop. + * + * Blocks the caller until at least one file handle has an + * event or the first timer expires. + * + * returns -1 if the event monitoring failed + */ +int virEventRunOnce(void); + +int +virEventHandleTypeToPollEvent(int events); +int +virPollEventToEventHandleType(int events); + + +/** + * virEventInterrupt: wakeup any thread waiting in poll() + * + * return -1 if wakup failed + */ +int virEventInterrupt(void); + + +#endif /* __VIRTD_EVENT_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 697b401..746cfa5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -402,7 +402,7 @@ virbuftest_LDADD = $(LDADDS) if WITH_LIBVIRTD eventtest_SOURCES = \ - eventtest.c testutils.h testutils.c ../daemon/event.c + eventtest.c testutils.h testutils.c eventtest_LDADD = -lrt $(LDADDS) endif diff --git a/tests/eventtest.c b/tests/eventtest.c index 067e365..bbf609b 100644 --- a/tests/eventtest.c +++ b/tests/eventtest.c @@ -31,7 +31,7 @@ #include "threads.h" #include "logging.h" #include "util.h" -#include "../daemon/event.h" +#include "event_impl.h" #define NUM_FDS 5 #define NUM_TIME 5 diff --git a/tools/Makefile.am b/tools/Makefile.am index 271c11b..4f56712 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -38,7 +38,6 @@ virt-pki-validate.1: virt-pki-validate virsh_SOURCES = \ console.c console.h \ - ../daemon/event.c ../daemon/event.h \ virsh.c virsh_LDFLAGS = $(WARN_LDFLAGS) $(COVERAGE_LDFLAGS) diff --git a/tools/console.c b/tools/console.c index e126320..18294a7 100644 --- a/tools/console.c +++ b/tools/console.c @@ -44,7 +44,7 @@ # include "memory.h" # include "virterror_internal.h" -# include "daemon/event.h" +# include "event_impl.h" /* ie Ctrl-] as per telnet */ # define CTRL_CLOSE_BRACKET '\35' diff --git a/tools/virsh.c b/tools/virsh.c index 8c123bb..fb182a5 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -52,7 +52,7 @@ #include "xml.h" #include "libvirt/libvirt-qemu.h" #include "files.h" -#include "../daemon/event.h" +#include "event_impl.h" #include "configmake.h" static char *progname; -- 1.7.1

On 12/23/2010 01:56 AM, Wen Congyang wrote:
move daemon/event.* into src/util/ directory because the timer needs the API virEventRunOnce().
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
--- daemon/Makefile.am | 1 - daemon/event.c | 700 ---------------------------------------------- daemon/event.h | 134 --------- daemon/libvirtd.c | 2 +- src/Makefile.am | 3 +- src/libvirt_private.syms | 14 + src/util/event_impl.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/event_impl.h | 134 +++++++++ tests/Makefile.am | 2 +- tests/eventtest.c | 2 +- tools/Makefile.am | 1 - tools/console.c | 2 +- tools/virsh.c | 2 +- 13 files changed, 855 insertions(+), 842 deletions(-) delete mode 100644 daemon/event.c delete mode 100644 daemon/event.h create mode 100644 src/util/event_impl.c create mode 100644 src/util/event_impl.h
If you use 'git config diff.renames true', then this email would have dropped from about 60k to just under 5k, by showing code motion instead of delete and recreation. As it is, I find that style easier to review, so I'm taking the liberty of using it in my reply:
+++ b/daemon/libvirtd.c @@ -62,7 +62,7 @@ #include "uuid.h" #include "remote_driver.h" #include "conf.h" -#include "event.h" +#include "event_impl.h"
Why the rename? Oh, because src/util/event.c already exists. Can we merge those into one file, rather than adding the _impl variant?
+++ b/src/libvirt_private.syms @@ -377,6 +377,20 @@ virEventUpdateHandle; virEventUpdateTimeout;
+# event_impl.h +virEventAddHandleImpl; +virEventUpdateHandleImpl; +virEventRemoveHandleImpl; +virEventAddTimeoutImpl; +virEventUpdateTimeoutImpl; +virEventRemoveTimeoutImpl; +virEventInit; +virEventRunOnce; +virEventHandleTypeToPollEvent; +virPollEventToEventHandleType; +virEventInterrupt;
Keeping this list sorted makes it easier to maintain. Wow, now that we're actually exporting these symbol names, I wonder if it's also time for a bulk rename to drop the Impl suffix. (It's best to separate function renames into a different patch from file motion, so that git diff rename detection has a decent chance of compact diff representations). Also, do all of them need to be in src/util/, or can some of them still remain in just daemon/ (for example, virPollEventToEventHandleType is only referenced by daemon/mdns.c and examples/domain-events/events-c/event-test.c). For example, maybe all the virEvent APIs should take a virEventPtr as the first parameter. Then you could add a new function virEventRegisterFD(virEventPtr ptr) that associates ptr with the standard three virEvent*HandleImpl callbacks, thus allowing those callbacks to be static to event.c rather than having multiple files have to even be aware of how those particular callbacks are named. That would imply making the storage for those callbacks belong to a struct, rather than being static variables in src/util/event.c. At any rate, it seems like there could still be a lot of beneficial refactoring done to this code.
diff --git a/daemon/event.c b/src/util/event_impl.c similarity index 99% rename from daemon/event.c rename to src/util/event_impl.c index 89ca9f0..4876088 100644 --- a/daemon/event.c +++ b/src/util/event_impl.c @@ -32,7 +32,7 @@
#include "threads.h" #include "logging.h" -#include "event.h" +#include "event_impl.h" #include "memory.h" #include "util.h" #include "ignore-value.h" diff --git a/daemon/event.h b/src/util/event_impl.h
See how compact that is? :)
+++ b/tests/Makefile.am @@ -402,7 +402,7 @@ virbuftest_LDADD = $(LDADDS)
if WITH_LIBVIRTD eventtest_SOURCES = \ - eventtest.c testutils.h testutils.c ../daemon/event.c + eventtest.c testutils.h testutils.c eventtest_LDADD = -lrt $(LDADDS) endif
Hmm, I wonder if this test only previously depended on WITH_LIBVIRTD because it used ../daemon/event.c, and if that's the case, we can probably simplify the Makefile to remove the conditionals. In fact, that's a good idea anyways, since someone using ./autogen.sh --without-libvirtd should still be able to use events. Finally - this failed to build for me: make[2]: Entering directory `/home/remote/eblake/libvirt/daemon' CC libvirtd-libvirtd.o CC libvirtd-mdns.o cc1: warnings being treated as errors mdns.c: In function 'libvirtd_mdns_watch_dispatch': mdns.c:233:5: error: implicit declaration of function 'virEventHandleTypeToPollEvent' [-Wimplicit-function-declaration] so you missed at least one affected file. This appears to fix it for me, but it's worth double-checking for anyone else that might be conditionally built and relies on the old header location: diff --git i/daemon/mdns.c w/daemon/mdns.c index ae8dc40..29a164b 100644 --- i/daemon/mdns.c +++ w/daemon/mdns.c @@ -1,6 +1,7 @@ /* * mdns.c: advertise libvirt hypervisor connections * + * Copyright (C) 2010 Red Hat, Inc. * Copyright (C) 2007 Daniel P. Berrange * * Derived from Avahi example service provider code. @@ -39,7 +40,7 @@ #include "libvirtd.h" #include "mdns.h" -#include "event.h" +#include "event_impl.h" #include "memory.h" #define AVAHI_DEBUG(fmt, ...) DEBUG(fmt, __VA_ARGS__) -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

At 12/24/2010 08:25 AM, Eric Blake Write:
On 12/23/2010 01:56 AM, Wen Congyang wrote:
move daemon/event.* into src/util/ directory because the timer needs the API virEventRunOnce().
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
--- daemon/Makefile.am | 1 - daemon/event.c | 700 ---------------------------------------------- daemon/event.h | 134 --------- daemon/libvirtd.c | 2 +- src/Makefile.am | 3 +- src/libvirt_private.syms | 14 + src/util/event_impl.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/event_impl.h | 134 +++++++++ tests/Makefile.am | 2 +- tests/eventtest.c | 2 +- tools/Makefile.am | 1 - tools/console.c | 2 +- tools/virsh.c | 2 +- 13 files changed, 855 insertions(+), 842 deletions(-) delete mode 100644 daemon/event.c delete mode 100644 daemon/event.h create mode 100644 src/util/event_impl.c create mode 100644 src/util/event_impl.h
If you use 'git config diff.renames true', then this email would have dropped from about 60k to just under 5k, by showing code motion instead of delete and recreation. As it is, I find that style easier to review, so I'm taking the liberty of using it in my reply:
+++ b/daemon/libvirtd.c @@ -62,7 +62,7 @@ #include "uuid.h" #include "remote_driver.h" #include "conf.h" -#include "event.h" +#include "event_impl.h"
Why the rename? Oh, because src/util/event.c already exists. Can we merge those into one file, rather than adding the _impl variant? Yes, we can merge those into one file, but git diff rename detection does not work if we merge those into one file...
+++ b/src/libvirt_private.syms @@ -377,6 +377,20 @@ virEventUpdateHandle; virEventUpdateTimeout;
+# event_impl.h +virEventAddHandleImpl; +virEventUpdateHandleImpl; +virEventRemoveHandleImpl; +virEventAddTimeoutImpl; +virEventUpdateTimeoutImpl; +virEventRemoveTimeoutImpl; +virEventInit; +virEventRunOnce; +virEventHandleTypeToPollEvent; +virPollEventToEventHandleType; +virEventInterrupt;
Keeping this list sorted makes it easier to maintain.
OK.
Wow, now that we're actually exporting these symbol names, I wonder if it's also time for a bulk rename to drop the Impl suffix. (It's best to separate function renames into a different patch from file motion, so that git diff rename detection has a decent chance of compact diff representations). Also, do all of them need to be in src/util/, or can some of them still remain in just daemon/ (for example, virPollEventToEventHandleType is only referenced by daemon/mdns.c and examples/domain-events/events-c/event-test.c).
virPollEventToEventHandleType is referenced by daemon/event.c too.
For example, maybe all the virEvent APIs should take a virEventPtr as the first parameter. Then you could add a new function virEventRegisterFD(virEventPtr ptr) that associates ptr with the standard three virEvent*HandleImpl callbacks, thus allowing those callbacks to be static to event.c rather than having multiple files have to even be aware of how those particular callbacks are named. That would imply making the storage for those callbacks belong to a struct, rather than being static variables in src/util/event.c. At any rate, it seems like there could still be a lot of beneficial refactoring done to this code.
diff --git a/daemon/event.c b/src/util/event_impl.c similarity index 99% rename from daemon/event.c rename to src/util/event_impl.c index 89ca9f0..4876088 100644 --- a/daemon/event.c +++ b/src/util/event_impl.c @@ -32,7 +32,7 @@
#include "threads.h" #include "logging.h" -#include "event.h" +#include "event_impl.h" #include "memory.h" #include "util.h" #include "ignore-value.h" diff --git a/daemon/event.h b/src/util/event_impl.h
See how compact that is? :)
+++ b/tests/Makefile.am @@ -402,7 +402,7 @@ virbuftest_LDADD = $(LDADDS)
if WITH_LIBVIRTD eventtest_SOURCES = \ - eventtest.c testutils.h testutils.c ../daemon/event.c + eventtest.c testutils.h testutils.c eventtest_LDADD = -lrt $(LDADDS) endif
Hmm, I wonder if this test only previously depended on WITH_LIBVIRTD because it used ../daemon/event.c, and if that's the case, we can probably simplify the Makefile to remove the conditionals. In fact, that's a good idea anyways, since someone using ./autogen.sh --without-libvirtd should still be able to use events.
OK.
Finally - this failed to build for me:
make[2]: Entering directory `/home/remote/eblake/libvirt/daemon' CC libvirtd-libvirtd.o CC libvirtd-mdns.o cc1: warnings being treated as errors mdns.c: In function 'libvirtd_mdns_watch_dispatch': mdns.c:233:5: error: implicit declaration of function 'virEventHandleTypeToPollEvent' [-Wimplicit-function-declaration]
It is my mistake, I only test this patch on Linux.
so you missed at least one affected file. This appears to fix it for me, but it's worth double-checking for anyone else that might be conditionally built and relies on the old header location:
diff --git i/daemon/mdns.c w/daemon/mdns.c index ae8dc40..29a164b 100644 --- i/daemon/mdns.c +++ w/daemon/mdns.c @@ -1,6 +1,7 @@ /* * mdns.c: advertise libvirt hypervisor connections * + * Copyright (C) 2010 Red Hat, Inc. * Copyright (C) 2007 Daniel P. Berrange * * Derived from Avahi example service provider code. @@ -39,7 +40,7 @@
#include "libvirtd.h" #include "mdns.h" -#include "event.h" +#include "event_impl.h" #include "memory.h"
#define AVAHI_DEBUG(fmt, ...) DEBUG(fmt, __VA_ARGS__)

* src/util/timer.c src/util/timer.h: timer implementation * src/libvirt.c: Initialize timer * src/Makefile.am: build timer * src/libvirt_private.syms: Export public functions Signed-off-by: Wen Congyang <wency@cn.fujitsu.com> --- cfg.mk | 1 + src/Makefile.am | 3 +- src/libvirt.c | 2 + src/libvirt_private.syms | 6 ++ src/util/timer.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/timer.h | 34 ++++++++++ 6 files changed, 204 insertions(+), 1 deletions(-) create mode 100644 src/util/timer.c create mode 100644 src/util/timer.h diff --git a/cfg.mk b/cfg.mk index 03186b3..6909876 100644 --- a/cfg.mk +++ b/cfg.mk @@ -129,6 +129,7 @@ useless_free_options = \ --name=virStoragePoolSourceFree \ --name=virStorageVolDefFree \ --name=virThreadPoolFree \ + --name=virTimerFree \ --name=xmlFree \ --name=xmlXPathFreeContext \ --name=xmlXPathFreeObject diff --git a/src/Makefile.am b/src/Makefile.am index 1eefd39..d2a7c30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -80,7 +80,8 @@ UTIL_SOURCES = \ util/xml.c util/xml.h \ util/virtaudit.c util/virtaudit.h \ util/virterror.c util/virterror_internal.h \ - util/event_impl.c util/event_impl.h + util/event_impl.c util/event_impl.h \ + util/timer.c util/timer.h EXTRA_DIST += util/threads-pthread.c util/threads-win32.c diff --git a/src/libvirt.c b/src/libvirt.c index ee2495a..b938a60 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -40,6 +40,7 @@ #include "util.h" #include "memory.h" #include "configmake.h" +#include "timer.h" #ifndef WITH_DRIVER_MODULES # ifdef WITH_TEST @@ -332,6 +333,7 @@ virInitialize(void) if (virThreadInitialize() < 0 || virErrorInitialize() < 0 || + virTimerInitialize() < 0 || virRandomInitialize(time(NULL) ^ getpid())) return -1; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 21829fa..c9ee742 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -802,6 +802,12 @@ virThreadSelf; virThreadSelfID; +# timer.h +virTimerNew; +virSetTimeout; +virTimerFree; + + # usb.h usbDeviceFileIterate; usbDeviceGetBus; diff --git a/src/util/timer.c b/src/util/timer.c new file mode 100644 index 0000000..20b8b77 --- /dev/null +++ b/src/util/timer.c @@ -0,0 +1,159 @@ +/* + * timer.c: timer functions + * + * Copyright (C) 2010 Fujitsu Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Wen Congyang <wency@cn.fujitsu.com> + */ + +#include <config.h> + +#include "event.h" +#include "event_impl.h" +#include "memory.h" +#include "threads.h" +#include "timer.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +struct _virTimer { + int timer_id; + + virTimerCallback function; + void *opaque; +}; + +/* use timerFunc to prevent the user know timer id. */ +static void timerFunc(int timer_id ATTRIBUTE_UNUSED, void *opaque) +{ + virTimerPtr timer = opaque; + timer->function(timer->opaque); +} + +/** + * virTimerNew: + * @callback: the callback to call when timeout has expired + * @opaque: user data to pass to the callback + * + * Create a new timer object. + * + * Returns the poitner to new timer object, or NULL upon error + */ +virTimerPtr virTimerNew(virTimerCallback callback, void *opaque) +{ + virTimerPtr timer = NULL; + + if (VIR_ALLOC(timer) < 0) { + virReportOOMError(); + return NULL; + } + + timer->timer_id = -1; + timer->function = callback; + timer->opaque = opaque; + + return timer; +} + +/** + * virSetTimeout: + * @timer: pointer to timer object + * @expire_time: the new timeout value(must be 0 or above) + * + * Alarm or disalarm the timer. When expire_time is more than 0, alarm the + * timer with the new timeout value. When expire_time is 0, disalarm the timer. + * + * Returns 0 in case of success or -1 in case of error. + */ +int virSetTimeout(virTimerPtr timer, int expire_time) +{ + if (expire_time < 0) { + /* expire_time is invalid */ + return -1; + } + + if (expire_time == 0) { + /* delete the timer */ + if (timer->timer_id == -1) + return 0; + + if (virEventRemoveTimeout(timer->timer_id) < 0) + return -1; + + timer->timer_id = -1; + return 0; + } + + if (timer->timer_id == -1) { + /* add new timer */ + int ret; + + if ((ret = virEventAddTimeout(expire_time, timerFunc, + timer, NULL)) < 0) { + return -1; + } + timer->timer_id = ret; + return 0; + } + + /* update the timer */ + virEventUpdateTimeout(timer->timer_id, expire_time); + return 0; +} + +/** + * virTimerFree: + * @timer: pointer to timer object + * + * Free the associated timer object. + */ +void virTimerFree(virTimerPtr timer) +{ + if (!timer) + return; + + VIR_FREE(timer); +} + +static int timer_initialized = 0; +static virThread timer_thread; +static bool timer_quit = false; + +static void timerThreadFunc(void *opaque ATTRIBUTE_UNUSED) +{ + while(!timer_quit) { + virEventRunOnce(); + } +} + +/* This init function requires: + * 1. single-thread environment. + * 2. it must be called after virThreadInitialize(). + */ +int virTimerInitialize(void) +{ + if (timer_initialized) + return 0; + + if (virThreadCreate(&timer_thread, true, timerThreadFunc, NULL) < 0) + return -1; + + timer_initialized = 1; + timer_quit = false; + return 0; +} diff --git a/src/util/timer.h b/src/util/timer.h new file mode 100644 index 0000000..2d010b1 --- /dev/null +++ b/src/util/timer.h @@ -0,0 +1,34 @@ +/* + * timer.h: structure and entry points for timer support + * + * Copyright (C) 2010 Fujitsu Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Wen Congyang <wency@cn.fujitsu.com> + */ + +#ifndef __VIR_TIMER_H__ +# define __VIR_TIMER_H__ + +typedef struct _virTimer virTimer; +typedef virTimer *virTimerPtr; +typedef void (*virTimerCallback)(void *); + +extern virTimerPtr virTimerNew(virTimerCallback, void *); +extern int virSetTimeout(virTimerPtr, int) ATTRIBUTE_NONNULL(1); +extern void virTimerFree(virTimerPtr); +extern int virTimerInitialize(void) ATTRIBUTE_RETURN_CHECK; +#endif /* __VIR_TIMER_H__ */ -- 1.7.1

If a guest has not completed live migration before timeout, then auto-suspend the guest, where the migration will complete offline. Signed-off-by: Wen Congyang <wency@cn.fujitsu.com> --- tools/virsh.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 4 +++ 2 files changed, 71 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index fb182a5..99d9c30 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -54,6 +54,7 @@ #include "files.h" #include "event_impl.h" #include "configmake.h" +#include "timer.h" static char *progname; @@ -3358,6 +3359,28 @@ cmdDomuuid(vshControl *ctl, const vshCmd *cmd) /* * "migrate" command */ +static void migrateTimeoutHandler(void *data) +{ + virDomainPtr dom = data; + virDomainInfo info; + unsigned int id; + + id = virDomainGetID(dom); + if (id == ((unsigned int)-1)) + return; + + /* The error reason has been reported in virDomainGetInfo() and + * virDomainSuspend() when it fails. So we do not check the return value. + */ + if (virDomainGetInfo(dom, &info) == 0) { + if (info.state == VIR_DOMAIN_SHUTOFF) + return; + + /* suspend the domain when migration timeouts. */ + virDomainSuspend(dom); + } +} + static const vshCmdInfo info_migrate[] = { {"help", N_("migrate domain to another host")}, {"desc", N_("Migrate domain to another host. Add --live for live migration.")}, @@ -3378,6 +3401,7 @@ static const vshCmdOptDef opts_migrate[] = { {"desturi", VSH_OT_DATA, VSH_OFLAG_REQ, N_("connection URI of the destination host")}, {"migrateuri", VSH_OT_DATA, 0, N_("migration URI, usually can be omitted")}, {"dname", VSH_OT_DATA, 0, N_("rename to new name during migration (if supported)")}, + {"timeout", VSH_OT_INT, 0, N_("force guest to suspend if live migration exceeds timeout (in seconds)")}, {NULL, 0, 0, NULL} }; @@ -3388,6 +3412,8 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd) const char *desturi; const char *migrateuri; const char *dname; + long long timeout; + virTimerPtr migratetimer = NULL; int flags = 0, found, ret = FALSE; if (!vshConnectionUsability (ctl, ctl->conn)) @@ -3425,6 +3451,29 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool (cmd, "copy-storage-inc")) flags |= VIR_MIGRATE_NON_SHARED_INC; + timeout = vshCommandOptInt(cmd, "timeout", &found); + if (found) { + if (! flags & VIR_MIGRATE_LIVE) { + vshError(ctl, "%s", _("migrate: Unexpected timeout for offline migration")); + goto done; + } + + if (timeout < 1) { + vshError(ctl, "%s", _("migrate: Invalid timeout")); + goto done; + } + + /* Ensure that we can multiply by 1000 without overflowing. */ + if (timeout > INT_MAX / 1000) { + vshError(ctl, "%s", _("migrate: Timeout is too big")); + goto done; + } + + migratetimer = virTimerNew(migrateTimeoutHandler, (void *)dom); + if (!migratetimer) + goto done; + } + if ((flags & VIR_MIGRATE_PEER2PEER) || vshCommandOptBool (cmd, "direct")) { /* For peer2peer migration or direct migration we only expect one URI @@ -3435,6 +3484,13 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd) goto done; } + if (migratetimer) { + if (virSetTimeout(migratetimer, timeout * 1000) < 0) { + vshError(ctl, "%s", _("migrate: failed to add timer")); + goto done; + } + } + if (virDomainMigrateToURI (dom, desturi, flags, dname, 0) == 0) ret = TRUE; } else { @@ -3445,6 +3501,13 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd) dconn = virConnectOpenAuth (desturi, virConnectAuthPtrDefault, 0); if (!dconn) goto done; + if (migratetimer) { + if (virSetTimeout(migratetimer, timeout * 1000) < 0) { + vshError(ctl, "%s", _("migrate: failed to add timer")); + goto done; + } + } + ddom = virDomainMigrate (dom, dconn, flags, dname, migrateuri, 0); if (ddom) { virDomainFree(ddom); @@ -3454,6 +3517,10 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd) } done: + if (migratetimer) { + virSetTimeout(migratetimer, 0); + virTimerFree(migratetimer); + } if (dom) virDomainFree (dom); return ret; } diff --git a/tools/virsh.pod b/tools/virsh.pod index 9cb6829..f9d6a70 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -490,6 +490,7 @@ type attribute for the <domain> element of XML. =item B<migrate> optional I<--live> I<--p2p> I<--direct> I<--tunnelled> I<--persistent> I<--undefinesource> I<--suspend> I<--copy-storage-all> I<--copy-storage-inc> I<domain-id> I<desturi> I<migrateuri> I<dname> +I<--timeout> Migrate domain to another host. Add I<--live> for live migration; I<--p2p> for peer-2-peer migration; I<--direct> for direct migration; or I<--tunnelled> @@ -505,6 +506,9 @@ I<migrateuri> is the migration URI, which usually can be omitted. I<dname> is used for renaming the domain to new name during migration, which also usually can be omitted. +I<--timeout> forces guest to suspend when live migration exceeds timeout, and +then the migration will complete offline. It can only be used with I<--live>. + =item B<migrate-setmaxdowntime> I<domain-id> I<downtime> Set maximum tolerable downtime for a domain which is being live-migrated to -- 1.7.1
participants (2)
-
Eric Blake
-
Wen Congyang