[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Xen-devel] [PATCH 08/11] libxl: Asynchronous/long-running operation infrastructure



2012/1/26 Ian Jackson <ian.jackson@xxxxxxxxxxxxx>:
> Provide a new set of machinery for writing public libxl functions
> which may take a long time. ÂThe application gets to decide whether
> they want the function to be synchronous, or whether they'd prefer to
> get a callback, or an event, when the operation is complete.
>
> User(s) of this machinery will be introduced in later patch(es).
>
> Signed-off-by: Ian Jackson <ian.jackson@xxxxxxxxxxxxx>
> ---
> Âtools/libxl/libxl.h     Â|  53 ++++++++++++
> Âtools/libxl/libxl_event.c  Â| Â188 
> ++++++++++++++++++++++++++++++++++++++++++
> Âtools/libxl/libxl_internal.h | Â107 ++++++++++++++++++++++++
> Âtools/libxl/libxl_types.idl Â| Â Â4 +
> Â4 files changed, 352 insertions(+), 0 deletions(-)
>
> diff --git a/tools/libxl/libxl.h b/tools/libxl/libxl.h
> index e32881b..fa79c24 100644
> --- a/tools/libxl/libxl.h
> +++ b/tools/libxl/libxl.h
> @@ -237,6 +237,59 @@ enum {
> Â Â ERROR_BUFFERFULL = -13,
> Â};
>
> +
> +/*
> + * Some libxl operations can take a long time. ÂThese functions take a
> + * parameter to control their concurrency:
> + * Â Â libxl_asyncop_how *ao_how
> + *
> + * If ao_how==NULL, the function will be synchronous.
> + *
> + * If ao_how!=NULL, the function will set the operation going, and if
> + * this is successful will return 0. ÂIn this case the zero error
> + * response does NOT mean that the operation was successful; it just
> + * means that it has been successfully started. ÂIt will finish later,
> + * perhaps with an error.
> + *
> + * If ao_how->callback!=NULL, the callback will be called when the
> + * operation completes. ÂThe same rules as for libxl_event_hooks
> + * apply, including the reentrancy rules and the possibility of
> + * "disaster", except that libxl calls ao_how->callback instead of
> + * libxl_event_hooks.event_occurs. Â(See libxl_event.h.)
> + *
> + * If ao_how->callback==NULL, a libxl_event will be generated which
> + * can be obtained from libxl_event_wait or libxl_event_check. ÂThe
> + * event will have type OPERATION_COMPLETE (which is not used
> + * elsewhere).
> + *
> + * Note that it is possible for an asynchronous operation which is to
> + * result in a callback to complete during its initiating function
> + * call. ÂIn this case the initiating function will return 0
> + * indicating the at the operation is "in progress", even though by
> + * the time it returns the operation is complete and the callback has
> + * already happened.
> + *
> + * The application must set and use ao_how->for_event (which will be
> + * copied into libxl_event.for_user) or ao_how->for_callback (passed
> + * to the callback) to determine which operation finished, and it must
> + * of course check the rc value for errors.
> + *
> + * *ao_how does not need to remain valid after the initiating function
> + * returns.
> + *
> + * Callbacks may occur on any thread in which the application calls
> + * libxl.
> + */
> +
> +typedef struct {
> + Â Âvoid (*callback)(libxl_ctx *ctx, int rc, void *for_callback);
> + Â Âunion {
> + Â Â Â Âlibxl_ev_user for_event; /* used if callback==NULL */
> + Â Â Â Âvoid *for_callback; /* passed to callback */
> + Â Â} u;
> +} libxl_asyncop_how;
> +
> +
> Â#define LIBXL_VERSION 0
>
> Âtypedef struct {
> diff --git a/tools/libxl/libxl_event.c b/tools/libxl/libxl_event.c
> index 73dfd9d..9e1ab56 100644
> --- a/tools/libxl/libxl_event.c
> +++ b/tools/libxl/libxl_event.c
> @@ -771,10 +771,21 @@ static void egc_run_callbacks(libxl__egc *egc)
> Â{
> Â Â EGC_GC;
> Â Â libxl_event *ev, *ev_tmp;
> +
> Â Â LIBXL_TAILQ_FOREACH_SAFE(ev, &egc->occurred_for_callback, link, ev_tmp) {
> Â Â Â Â LIBXL_TAILQ_REMOVE(&egc->occurred_for_callback, ev, link);
> Â Â Â Â CTX->event_hooks->event_occurs(CTX->event_hooks_user, ev);
> Â Â }
> +
> + Â Âlibxl__ao *ao, *ao_tmp;
> + Â ÂLIBXL_TAILQ_FOREACH_SAFE(ao, &egc->aos_for_callback,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â entry_for_callback, ao_tmp) {
> + Â Â Â ÂLIBXL_TAILQ_REMOVE(&egc->aos_for_callback, ao, entry_for_callback);
> + Â Â Â Âao->how.callback(CTX, ao->rc, ao->how.u.for_callback);
> + Â Â Â Âao->notified = 1;
> + Â Â Â Âif (!ao->in_initiator)
> + Â Â Â Â Â Âlibxl__ao__destroy(CTX, ao);
> + Â Â}
> Â}
>
> Âvoid libxl__egc_cleanup(libxl__egc *egc)
> @@ -1061,6 +1072,183 @@ int libxl_event_wait(libxl_ctx *ctx, libxl_event 
> **event_r,
> Â Â return rc;
> Â}
>
> +
> +
> +/*
> + * The two possible state flow of an ao:
> + *
> + * Completion before initiator return:
> + *
> + *   Initiator thread            Possible other threads
> + *
> + * Â * ao_create allocates memory and
> + * Â Â initialises the struct
> + *
> + * Â * the initiator function does its
> + * Â Â work, setting up various internal
> + * Â Â asynchronous operations -----------> * asynchronous operations
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âstart to take place and
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âmight cause ao completion
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â|
> + *  * initiator calls ao_inprogress       Â|
> + *   - if synchronous, run event loop      |
> + *    until the ao completes          |
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â- ao completes on some thread
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â- completing thread releases the lock
> + * Â Â Â Â Â Â Â Â Â Â <--------------'
> + * Â Â - ao_inprogress takes the lock
> + * Â Â - destroy the ao
> + *
> + *
> + * Completion after initiator return (asynch. only):
> + *
> + *
> + *   Initiator thread            Possible other threads
> + *
> + * Â * ao_create allocates memory and
> + * Â Â initialises the struct
> + *
> + * Â * the initiator function does its
> + * Â Â work, setting up various internal
> + * Â Â asynchronous operations -----------> * asynchronous operations
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âstart to take place and
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âmight cause ao completion
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â|
> + *  * initiator calls ao_inprogress       Â|
> + * Â Â - observes event not yet done, Â Â Â Â Â Â |
> + *   - returns to caller            Â|
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â|
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â- ao completes on some thread
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â- generate the event or call the callback
> + * Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â- destroy the ao
> + */
> +
> +void libxl__ao__destroy(libxl_ctx *ctx, libxl__ao *ao)
> +{
> + Â Âif (!ao) return;
> + Â Âif (ao->poller) libxl__poller_put(ctx, ao->poller);
> + Â Âao->magic = LIBXL__AO_MAGIC_DESTROYED;
> + Â Âlibxl__free_all(&ao->gc);
> + Â Âfree(ao);
> +}
> +
> +void libxl__ao_abort(libxl__ao *ao)
> +{
> + Â ÂAO_GC;
> + Â Âassert(ao->magic == LIBXL__AO_MAGIC);
> + Â Âassert(ao->in_initiator);
> + Â Âassert(!ao->complete);
> + Â Âlibxl__ao__destroy(CTX, ao);
> +}
> +
> +void libxl__ao_complete(libxl__egc *egc, libxl__ao *ao, int rc)
> +{
> + Â Âassert(ao->magic == LIBXL__AO_MAGIC);
> + Â Âassert(!ao->complete);
> + Â Âao->complete = 1;
> + Â Âao->rc = rc;
> +
> + Â Âif (ao->poller) {
> + Â Â Â Âassert(ao->in_initiator);
> + Â Â Â Âlibxl__poller_wakeup(egc, ao->poller);
> + Â Â} else if (ao->how.callback) {
> + Â Â Â ÂLIBXL_TAILQ_INSERT_TAIL(&egc->aos_for_callback, ao, 
> entry_for_callback);
> + Â Â} else {
> + Â Â Â Âlibxl_event *ev;
> + Â Â Â Âev = NEW_EVENT(egc, OPERATION_COMPLETE, ao->domid);
> + Â Â Â Âif (ev) {
> + Â Â Â Â Â Âev->for_user = ao->how.u.for_event;
> + Â Â Â Â Â Âev->u.operation_complete.rc = ao->rc;
> + Â Â Â Â Â Âlibxl__event_occurred(egc, ev);
> + Â Â Â Â}
> + Â Â Â Âao->notified = 1;
> + Â Â}
> + Â Âif (!ao->in_initiator && ao->notified)
> + Â Â Â Âlibxl__ao__destroy(libxl__gc_owner(&egc->gc), ao);
> +}
> +
> +libxl__ao *libxl__ao_create(libxl_ctx *ctx, uint32_t domid,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Âconst libxl_asyncop_how *how)
> +{
> + Â Âlibxl__ao *ao;
> +
> + Â Âao = calloc(1, sizeof(*ao));
> + Â Âif (!ao) goto out;
> +
> + Â Âao->magic = LIBXL__AO_MAGIC;
> + Â Âao->in_initiator = 1;
> + Â Âao->poller = 0;
> + Â Âao->domid = domid;
> + Â ÂLIBXL_INIT_GC(ao->gc, ctx);
> +
> + Â Âif (how) {
> + Â Â Â Âao->how = *how;
> + Â Â} else {
> + Â Â Â Âao->poller = libxl__poller_get(ctx);
> + Â Â Â Âif (!ao->poller) goto out;
> + Â Â}
> + Â Âreturn ao;
> +
> + out:
> + Â Âif (ao) libxl__ao__destroy(ctx, ao);
> + Â Âreturn NULL;
> +}
> +
> +int libxl__ao_inprogress(libxl__ao *ao)
> +{
> + Â ÂAO_GC;
> + Â Âint rc;

I've just started playing with this, but I've found that if you call
AO_INPROGRESS without adding any event it blocks forever, and I'm not
sure if that is a design decision or a bug. Anyway, sometimes like in
libxl__initiate_device_remove you might call AO_INPROGRESS without
adding any event, so I think it will be safe to add something like:

if (!CTX->watch_counter)
    return 0;

To prevent waiting forever, if not we need to be sure we only call
AO_INPROGRESS when we have added some events (which is not as
practical as this simple fix). Since I've just started playing with
the events API, I might be doing an incorrect usage of this, if this
sounds ok please drop a line and I will send a formal patch.

Thanks, Roger.

> +
> + Â Âassert(ao->magic == LIBXL__AO_MAGIC);
> + Â Âassert(ao->in_initiator);
> +
> + Â Âif (ao->poller) {
> + Â Â Â Â/* Caller wants it done synchronously. */
> + Â Â Â Â/* We use a fresh gc, so that we can free things
> + Â Â Â Â * each time round the loop. */
> + Â Â Â Âlibxl__egc egc;
> + Â Â Â ÂLIBXL_INIT_EGC(egc,CTX);
> +
> + Â Â Â Âfor (;;) {
> + Â Â Â Â Â Âassert(ao->magic == LIBXL__AO_MAGIC);
> +
> + Â Â Â Â Â Âif (ao->complete) {
> + Â Â Â Â Â Â Â Ârc = ao->rc;
> + Â Â Â Â Â Â Â Âao->notified = 1;
> + Â Â Â Â Â Â Â Âbreak;
> + Â Â Â Â Â Â}
> +
> + Â Â Â Â Â Ârc = eventloop_iteration(&egc,ao->poller);
> + Â Â Â Â Â Âif (rc) {
> + Â Â Â Â Â Â Â Â/* Oh dear, this is quite unfortunate. */
> + Â Â Â Â Â Â Â ÂLIBXL__LOG(CTX, LIBXL__LOG_ERROR, "Error waiting for"
> + Â Â Â Â Â Â Â Â Â Â Â Â Â " event during long-running operation (rc=%d)", 
> rc);
> + Â Â Â Â Â Â Â Âsleep(1);
> + Â Â Â Â Â Â Â Â/* It's either this or return ERROR_I_DONT_KNOW_WHETHER
> + Â Â Â Â Â Â Â Â * _THE_THING_YOU_ASKED_FOR_WILL_BE_DONE_LATER_WHEN
> + Â Â Â Â Â Â Â Â * _YOU_DIDNT_EXPECT_IT, since we don't have any kind of
> + Â Â Â Â Â Â Â Â * cancellation ability. */
> + Â Â Â Â Â Â}
> +
> + Â Â Â Â Â ÂCTX_UNLOCK;
> + Â Â Â Â Â Âlibxl__egc_cleanup(&egc);
> + Â Â Â Â Â ÂCTX_LOCK;
> + Â Â Â Â}
> + Â Â} else {
> + Â Â Â Ârc = 0;
> + Â Â}
> +
> + Â Âao->in_initiator = 0;
> +
> + Â Âif (ao->notified) {
> + Â Â Â Âassert(ao->complete);
> + Â Â Â Âlibxl__ao__destroy(CTX,ao);
> + Â Â}
> +
> + Â Âreturn rc;
> +}
> +
> +
> Â/*
> Â* Local variables:
> Â* mode: C
> diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
> index d1b96c1..044eeb4 100644
> --- a/tools/libxl/libxl_internal.h
> +++ b/tools/libxl/libxl_internal.h
> @@ -114,6 +114,7 @@ _hidden void libxl__log(libxl_ctx *ctx, xentoollog_level 
> msglevel, int errnoval,
>
> Âtypedef struct libxl__gc libxl__gc;
> Âtypedef struct libxl__egc libxl__egc;
> +typedef struct libxl__ao libxl__ao;
>
> Âtypedef struct libxl__ev_fd libxl__ev_fd;
> Âtypedef void libxl__ev_fd_callback(libxl__egc *egc, libxl__ev_fd *ev,
> @@ -218,6 +219,10 @@ struct libxl__poller {
> Â Â Â* releasing the ctx lock and going into poll; when it comes out
> Â Â Â* of poll it will take the poller off the pollers_event list.
> Â Â Â*
> + Â Â * A thread which is waiting for completion of a synchronous ao
> + Â Â * will allocate a poller and record it in the ao, so that other
> + Â Â * threads can wake it up.
> + Â Â *
> Â Â Â* When a thread is done with a poller it should put it onto
> Â Â Â* pollers_idle, where it can be reused later.
> Â Â Â*
> @@ -324,6 +329,21 @@ struct libxl__egc {
> Â Â /* for event-generating functions only */
> Â Â struct libxl__gc gc;
> Â Â struct libxl__event_list occurred_for_callback;
> + Â ÂLIBXL_TAILQ_HEAD(, libxl__ao) aos_for_callback;
> +};
> +
> +#define LIBXL__AO_MAGIC Â Â Â Â Â Â Â0xA0FACE00ul
> +#define LIBXL__AO_MAGIC_DESTROYED Â Â0xA0DEAD00ul
> +
> +struct libxl__ao {
> + Â Âuint32_t magic;
> + Â Âunsigned in_initiator:1, complete:1, notified:1;
> + Â Âint rc;
> + Â Âlibxl__gc gc;
> + Â Âlibxl_asyncop_how how;
> + Â Âlibxl__poller *poller;
> + Â Âuint32_t domid;
> + Â ÂLIBXL_TAILQ_ENTRY(libxl__ao) entry_for_callback;
> Â};
>
> Â#define LIBXL_INIT_GC(gc,ctx) do{ Â Â Â Â Â Â Â \
> @@ -1108,6 +1128,7 @@ libxl__device_model_version_running(libxl__gc *gc, 
> uint32_t domid);
> Â#define LIBXL_INIT_EGC(egc,ctx) do{ Â Â Â Â Â Â Â Â Â Â \
> Â Â Â Â LIBXL_INIT_GC((egc).gc,ctx); Â Â Â Â Â Â Â Â Â Â\
> Â Â Â Â LIBXL_TAILQ_INIT(&(egc).occurred_for_callback); \
> + Â Â Â ÂLIBXL_TAILQ_INIT(&(egc).aos_for_callback); Â Â Â\
> Â Â } while(0)
>
> Â_hidden void libxl__egc_cleanup(libxl__egc *egc);
> @@ -1125,6 +1146,92 @@ _hidden void libxl__egc_cleanup(libxl__egc *egc);
>
>
> Â/*
> + * Machinery for asynchronous operations ("ao")
> + *
> + * All "slow" functions (includes anything that might block on a
> + * guest or an external script) need to use the asynchronous
> + * operation ("ao") machinery. ÂThe function should take a parameter
> + * const libxl_asyncop_how *ao_how and must start with a call to
> + * AO_INITIATOR_ENTRY. ÂThese functions MAY NOT be called from
> + * outside libxl, because they can cause reentrancy callbacks.
> + *
> + * Lifecycle of an ao:
> + *
> + * - Created by libxl__ao_create (or the AO_CREATE convenience macro).
> + *
> + * - After creation, can be used by code which implements
> + * Â the operation as follows:
> + * Â Â Â- the ao's gc, for allocating memory for the lifetime
> + * Â Â Â Âof the operation (possibly with the help of the AO_GC
> + * Â Â Â Âmacro to introduce the gc into scope)
> + * Â Â Â- the ao itself may be passed about to sub-functions
> + * Â Â Â Âso that they can stash it away etc.
> + * Â Â Â- in particular, the ao pointer must be stashed in some
> + * Â Â Â Âper-operation structure which is also passed as a user
> + * Â Â Â Âpointer to the internal event generation request routines
> + * Â Â Â Âlibxl__evgen_FOO, so that at some point a CALLBACK will be
> + * Â Â Â Âmade when the operation is complete.
> + *
> + * - If initiation is successful, the initiating function needs
> + * Â to run libxl__ao_inprogress right before unlocking and
> + * Â returning, and return whatever it returns (AO_INPROGRESS macro).
> + *
> + * - If the initiation is unsuccessful, the initiating function must
> + * Â call libxl__ao_abort before unlocking and returning whatever
> + * Â error code is appropriate (AO_ABORT macro).
> + *
> + * - Later, some callback function, whose callback has been requested
> + * Â directly or indirectly, should call libxl__ao_complete (with the
> + * Â ctx locked, as it will generally already be in any event callback
> + * Â function). ÂThis must happen exactly once for each ao (and not if
> + * Â the ao has been destroyed, obviously), and it may not happen
> + * Â until libxl__ao_inprogress has been called on the ao.
> + *
> + * - Note that during callback functions, two gcs are available:
> + * Â Â- The one in egc, whose lifetime is only this callback
> + * Â Â- The one in ao, whose lifetime is the asynchronous operation
> + * Â Usually callback function should use CONTAINER_OF
> + * Â to obtain its own structure, containing a pointer to the ao,
> + * Â and then use the gc from that ao.
> + */
> +
> +#define AO_CREATE(ctx, domid, ao_how) Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â Âlibxl__ctx_lock(ctx); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â Âlibxl__ao *ao = libxl__ao_create(ctx, domid, ao_how); Â Â Â \
> + Â Âif (!ao) { libxl__ctx_unlock(ctx); return ERROR_NOMEM; } Â Â\
> + Â ÂAO_GC;
> +
> +#define AO_INPROGRESS ({ Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â\
> + Â Â Â Âlibxl_ctx *ao__ctx = libxl__gc_owner(&ao->gc); Â Â Â Â Â\
> + Â Â Â Âint ao__rc = libxl__ao_inprogress(ao); Â Â Â Â Â Â Â Â Â\
> + Â Â Â Âlibxl__ctx_unlock(ao__ctx); /* gc is now invalid */ Â Â \
> + Â Â Â Â(ao__rc); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â })
> +
> +#define AO_ABORT(rc) ({ Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â Â Â Âlibxl_ctx *ao__ctx = libxl__gc_owner(&ao->gc); Â Â Â Â Â\
> + Â Â Â Âassert(rc); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â Â Â Âlibxl__ao_abort(ao); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â\
> + Â Â Â Âlibxl__ctx_unlock(ao__ctx); /* gc is now invalid */ Â Â \
> + Â Â Â Â(rc); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â Â})
> +
> +#define AO_GC Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \
> + Â Âlibxl__gc *const gc = &ao->gc
> +
> +
> +/* All of these MUST be called with the ctx locked.
> + * libxl__ao_inprogress MUST be called with the ctx locked exactly once. */
> +_hidden libxl__ao *libxl__ao_create(libxl_ctx*, uint32_t domid,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âconst libxl_asyncop_how*);
> +_hidden int libxl__ao_inprogress(libxl__ao *ao);
> +_hidden void libxl__ao_abort(libxl__ao *ao);
> +_hidden void libxl__ao_complete(libxl__egc *egc, libxl__ao *ao, int rc);
> +
> +/* For use by ao machinery ONLY */
> +_hidden void libxl__ao__destroy(libxl_ctx*, libxl__ao *ao);
> +
> +/*
> Â* Convenience macros.
> Â*/
>
> diff --git a/tools/libxl/libxl_types.idl b/tools/libxl/libxl_types.idl
> index 35b53d6..8871c17 100644
> --- a/tools/libxl/libxl_types.idl
> +++ b/tools/libxl/libxl_types.idl
> @@ -395,6 +395,7 @@ libxl_event_type = Enumeration("event_type", [
> Â Â (1, "DOMAIN_SHUTDOWN"),
> Â Â (2, "DOMAIN_DESTROY"),
> Â Â (3, "DISK_EJECT"),
> + Â Â(4, "OPERATION_COMPLETE"),
> Â Â ])
>
> Âlibxl_ev_user = UInt(64)
> @@ -418,4 +419,7 @@ libxl_event = Struct("event",[
> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â ("vdev", string),
> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â ("disk", libxl_device_disk),
> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â])),
> + Â Â Â Â Â ("operation_complete", Struct(None, [
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â("rc", integer),
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â ])),
> Â Â Â Â Â Â]))])
> --
> 1.7.2.5
>
>
> _______________________________________________
> Xen-devel mailing list
> Xen-devel@xxxxxxxxxxxxxxxxxxx
> http://lists.xensource.com/xen-devel

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.