All of lore.kernel.org
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: John Snow <jsnow@redhat.com>, qemu-block@nongnu.org
Cc: kwolf@redhat.com, famz@redhat.com, qemu-devel@nongnu.org,
	vsementsov@parallels.com, stefanha@redhat.com
Subject: Re: [Qemu-devel] [PATCH v2 05/11] block: add transactional callbacks feature
Date: Fri, 17 Apr 2015 17:41:07 +0200	[thread overview]
Message-ID: <55312993.2090103@redhat.com> (raw)
In-Reply-To: <1427484005-31120-6-git-send-email-jsnow@redhat.com>

On 27.03.2015 20:19, John Snow wrote:
> The goal here is to add a new method to transactions that allows
> developers to specify a callback that will get invoked only once
> all jobs spawned by a transaction are completed, allowing developers
> the chance to perform actions conditionally pending complete success,
> partial failure, or complete failure.
>
> In order to register the new callback to be invoked, a user must request
> a callback pointer and closure by calling new_action_cb_wrapper, which
> creates a wrapper around an opaque pointer and callback that would have
> originally been passed to e.g. backup_start().
>
> The function will return a function pointer and a new opaque pointer to
> be passed instead. The transaction system will effectively intercept the
> original callbacks and perform book-keeping on the transaction after it
> has delivered the original enveloped callback.
>
> This means that Transaction Action callback methods will be called after
> all callbacks triggered by all Actions in the Transactional group have
> been received.
>
> This feature has no knowledge of any jobs spawned by Actions that do not
> inform the system via new_action_cb_wrapper().
>
> For an example of how to use the feature, please skip ahead to:
> 'block: drive_backup transaction callback support' which serves as an example
> for how to hook up a post-transaction callback to the Drive Backup action.
>
>
> Note 1: Defining a callback method alone is not sufficient to have the new
>          method invoked. You must call new_action_cb_wrapper() AND ensure the
>          callback it returns is the one used as the callback for the job
>          launched by the action.
>
> Note 2: You can use this feature for any system that registers completions of
>          an asynchronous task via a callback of the form
>          (void *opaque, int ret), not just block job callbacks.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   blockdev.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
>   1 file changed, 187 insertions(+), 4 deletions(-)

I like it much better than v1! :-)

Some minor comments below. (But just as in v1, I need to look into the 
following patches to know exactly how some of the functions introduced 
here are used)

> diff --git a/blockdev.c b/blockdev.c
> index f806d40..d404251 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1239,6 +1239,8 @@ typedef struct BlkActionState BlkActionState;
>    * @abort: Abort the changes on fail, can be NULL.
>    * @clean: Clean up resources after all transaction actions have called
>    *         commit() or abort(). Can be NULL.
> + * @cb: Executed after all jobs launched by actions in the transaction finish,
> + *      but only if requested by new_action_cb_wrapper() prior to clean().
>    *
>    * Only prepare() may fail. In a single transaction, only one of commit() or
>    * abort() will be called. clean() will always be called if it is present.
> @@ -1249,6 +1251,7 @@ typedef struct BlkActionOps {
>       void (*commit)(BlkActionState *common);
>       void (*abort)(BlkActionState *common);
>       void (*clean)(BlkActionState *common);
> +    void (*cb)(BlkActionState *common);
>   } BlkActionOps;
>   
>   /**
> @@ -1257,19 +1260,46 @@ typedef struct BlkActionOps {
>    * by a transaction group.
>    *
>    * @jobs: A reference count that tracks how many jobs still need to complete.
> + * @status: A cumulative return code for all actions that have reported
> + *          a return code via callback in the transaction.
>    * @actions: A list of all Actions in the Transaction.
> + *           However, once the transaction has completed, it will be only a list
> + *           of transactions that have registered a post-transaction callback.
>    */
>   typedef struct BlkTransactionState {
>       int jobs;
> +    int status;
>       QTAILQ_HEAD(actions, BlkActionState) actions;
>   } BlkTransactionState;
>   
> +typedef void (CallbackFn)(void *opaque, int ret);
> +
> +/**
> + * BlkActionCallbackData:
> + * Necessary state for intercepting and
> + * re-delivering a callback triggered by an Action.
> + *
> + * @opaque: The data to be given to the encapsulated callback when
> + *          a job launched by an Action completes.
> + * @ret: The status code that was delivered to the encapsulated callback.
> + * @callback: The encapsulated callback to invoke upon completion of
> + *            the Job launched by the Action.
> + */
> +typedef struct BlkActionCallbackData {
> +    void *opaque;
> +    int ret;
> +    CallbackFn *callback;
> +} BlkActionCallbackData;
> +
>   /**
>    * BlkActionState:
>    * Describes one Action's state within a Transaction.
>    *
>    * @action: QAPI-defined enum identifying which Action to perform.
>    * @ops: Table of ActionOps this Action can perform.
> + * @transaction: A pointer back to the Transaction this Action belongs to.
> + * @cb_data: Information on this Action's encapsulated callback, if any.
> + * @refcount: reference count, allowing access to this state beyond clean().
>    * @entry: List membership for all Actions in this Transaction.
>    *
>    * This structure must be arranged as first member in a subclassed type,
> @@ -1279,6 +1309,9 @@ typedef struct BlkTransactionState {
>   struct BlkActionState {
>       TransactionAction *action;
>       const BlkActionOps *ops;
> +    BlkTransactionState *transaction;
> +    BlkActionCallbackData *cb_data;
> +    int refcount;
>       QTAILQ_ENTRY(BlkActionState) entry;
>   };
>   
> @@ -1294,6 +1327,26 @@ static BlkTransactionState *new_blk_transaction_state(void)
>       return bts;
>   }
>   
> +static BlkActionState *put_blk_action_state(BlkActionState *state)
> +{
> +    BlkActionState *bas = state;

I don't think this variable needs to be initialized.

> +
> +    state->refcount--;
> +    if (state->refcount == 0) {
> +

Superfluous empty line.

> +        QTAILQ_FOREACH(bas, &state->transaction->actions, entry) {
> +            if (bas == state) {
> +                QTAILQ_REMOVE(&state->transaction->actions, bas, entry);
> +                break;
> +            }
> +        }

Wouldn't QTAILQ_REMOVE(&state->transaction->actions, state, entry); do 
exactly the same thing as this loop? There'd be a difference only if 
"state" is not element of the list of actions, but think that should be 
impossible.

Max

> +        g_free(state->cb_data);
> +        g_free(state);
> +        return NULL;
> +    }
> +    return state;
> +}
> +
>   static void destroy_blk_transaction_state(BlkTransactionState *bts)
>   {
>       BlkActionState *bas, *bas_next;
> @@ -1301,23 +1354,151 @@ static void destroy_blk_transaction_state(BlkTransactionState *bts)
>       /* The list should in normal cases be empty,
>        * but in case someone really just wants to kibosh the whole deal: */
>       QTAILQ_FOREACH_SAFE(bas, &bts->actions, entry, bas_next) {
> -        QTAILQ_REMOVE(&bts->actions, bas, entry);
> -        g_free(bas);
> +        put_blk_action_state(bas);
>       }
>   
>       g_free(bts);
>   }
>   
> +static void transaction_run_cb_action_ops(BlkTransactionState *bts)
> +{
> +    BlkActionState *bas, *bas_next;
> +
> +    QTAILQ_FOREACH_SAFE(bas, &bts->actions, entry, bas_next) {
> +        if (bas->ops->cb) {
> +            bas->ops->cb(bas);
> +        }
> +
> +        g_free(bas->cb_data);
> +        bas->cb_data = NULL;
> +        put_blk_action_state(bas);
> +    }
> +}
> +
>   static BlkTransactionState *transaction_job_complete(BlkTransactionState *bts)
>   {
>       bts->jobs--;
>       if (bts->jobs == 0) {
> +        transaction_run_cb_action_ops(bts);
>           destroy_blk_transaction_state(bts);
>           return NULL;
>       }
>       return bts;
>   }
>   
> +static void transaction_job_cancel(BlkActionState *bas)
> +{
> +    assert(bas->transaction->jobs > 1);
> +    transaction_job_complete(bas->transaction);
> +}
> +
> +/**
> + * Intercept a callback that was issued due to a transactional action.
> + */
> +static void transaction_action_callback(void *opaque, int ret)
> +{
> +    BlkActionState *common = opaque;
> +    BlkActionCallbackData *cb_data = common->cb_data;
> +    CallbackFn *callback = cb_data->callback;
> +
> +    /* Prepare data for ops->cb() */
> +    cb_data->callback = NULL;
> +    cb_data->ret = ret;
> +
> +    /* Cumulative return code will track the first error discovered */
> +    if (!common->transaction->status) {
> +        common->transaction->status = ret;
> +    }
> +
> +    /* Deliver the intercepted callback prior to marking it as completed */
> +    callback(cb_data->opaque, cb_data->ret);
> +    transaction_job_complete(common->transaction);
> +}
> +
> +/**
> + * Create a new transactional callback wrapper.
> + *
> + * Given a callback and a closure, generate a new
> + * callback and closure that will invoke the
> + * given callback with the given closure.
> + *
> + * After all wrappers in the transactional group have
> + * been processed, each action's .cb() method will be
> + * invoked.
> + *
> + * @common The transaction action state to set a callback for.
> + * @opaque A closure intended for the encapsulated callback.
> + * @callback The callback we are encapsulating.
> + * @new_opaque[out]: The closure to be used instead of @opaque.
> + *
> + * @return The callback to be used instead of @callback.
> + */
> +__attribute__((__unused__))
> +static CallbackFn *new_action_cb_wrapper(BlkActionState *common,
> +                                         void *opaque,
> +                                         CallbackFn *callback,
> +                                         void **new_opaque)
> +{
> +    BlkActionCallbackData *cb_data = g_new0(BlkActionCallbackData, 1);
> +    assert(new_opaque);
> +
> +    /* Stash the original callback information */
> +    cb_data->opaque = opaque;
> +    cb_data->callback = callback;
> +    common->cb_data = cb_data;
> +
> +    /* The BAS itself will serve as our new closure */
> +    *new_opaque = common;
> +    common->refcount++;
> +
> +    /* Inform the transaction to expect one more return */
> +    common->transaction->jobs++;
> +
> +    /* Lastly, the actual callback function to handle the interception. */
> +    return transaction_action_callback;
> +}
> +
> +/**
> + * Undo any actions performed by the above call.
> + */
> +__attribute__((__unused__))
> +static void cancel_action_cb_wrapper(BlkActionState *common)
> +{
> +    /* Stage 0: Wrapper was never created: */
> +    if (common->cb_data == NULL) {
> +        assert(common->refcount == 1);
> +        return;
> +    }
> +
> +    /* Stage 1: Callback was created, but the job never launched.
> +     * (We assume that the job cannot possibly be running, because
> +     *  we assume it has been synchronously canceled if it was.)
> +     *
> +     * We also assume that any cleanup normally handled by cb()
> +     * is not needed, because it should have been prepared after
> +     * the job launched.
> +     */
> +    if (common->cb_data && common->cb_data->callback) {
> +        transaction_job_cancel(common);
> +        goto cleanup;
> +    }
> +
> +    /* Stage 2: Job already completed, or was canceled.
> +     *
> +     * Force an error in the callback data and just invoke the completion
> +     * handler to perform appropriate cleanup for us.
> +     */
> +    if (common->cb_data && !common->cb_data->callback) {
> +        common->cb_data->ret = -ECANCELED;
> +        common->ops->cb(common);
> +    }
> +
> + cleanup:
> +    g_free(common->cb_data);
> +    common->cb_data = NULL;
> +    put_blk_action_state(common);
> +}
> +
>   /* internal snapshot private data */
>   typedef struct InternalSnapshotState {
>       BlkActionState common;
> @@ -1927,8 +2108,10 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)
>           assert(ops->instance_size > 0);
>   
>           state = g_malloc0(ops->instance_size);
> +        state->refcount = 1;
>           state->ops = ops;
>           state->action = dev_info;
> +        state->transaction = bts;
>           QTAILQ_INSERT_TAIL(&bts->actions, state, entry);
>   
>           state->ops->prepare(state, &local_err);
> @@ -1959,10 +2142,10 @@ exit:
>           if (state->ops->clean) {
>               state->ops->clean(state);
>           }
> -        QTAILQ_REMOVE(&bts->actions, state, entry);
> -        g_free(state);
> +        put_blk_action_state(state);
>       }
>   
> +    /* The implicit 'job' of the transaction itself is complete: */
>       transaction_job_complete(bts);
>   }
>   

  reply	other threads:[~2015-04-17 15:47 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-03-27 19:19 [Qemu-devel] [PATCH v2 00/11] block: incremental backup transactions John Snow
2015-03-27 19:19 ` [Qemu-devel] [PATCH v2 01/11] qapi: Add transaction support to block-dirty-bitmap operations John Snow
2015-04-17 14:41   ` Max Reitz
2015-04-17 14:50   ` Eric Blake
2015-03-27 19:19 ` [Qemu-devel] [PATCH v2 02/11] iotests: add transactional incremental backup test John Snow
2015-04-17 14:42   ` Max Reitz
2015-03-27 19:19 ` [Qemu-devel] [PATCH v2 03/11] block: rename BlkTransactionState and BdrvActionOps John Snow
2015-04-17 14:51   ` Max Reitz
2015-03-27 19:19 ` [Qemu-devel] [PATCH v2 04/11] block: re-add BlkTransactionState John Snow
2015-04-17 15:11   ` Max Reitz
2015-03-27 19:19 ` [Qemu-devel] [PATCH v2 05/11] block: add transactional callbacks feature John Snow
2015-04-17 15:41   ` Max Reitz [this message]
2015-04-17 21:55     ` John Snow
2015-03-27 19:20 ` [Qemu-devel] [PATCH v2 06/11] block: add refcount to Job object John Snow
2015-04-17 15:43   ` Max Reitz
2015-03-27 19:20 ` [Qemu-devel] [PATCH v2 07/11] block: add delayed bitmap successor cleanup John Snow
2015-04-17 15:49   ` Max Reitz
2015-03-27 19:20 ` [Qemu-devel] [PATCH v2 08/11] block: move transactions beneath qmp interfaces John Snow
2015-04-17 16:01   ` Max Reitz
2015-04-17 16:40     ` John Snow
2015-04-17 16:43     ` Eric Blake
2015-03-27 19:20 ` [Qemu-devel] [PATCH v2 09/11] qmp: Add an implementation wrapper for qmp_drive_backup John Snow
2015-04-17 16:12   ` Max Reitz
2015-03-27 19:20 ` [Qemu-devel] [PATCH v2 10/11] block: drive_backup transaction callback support John Snow
2015-04-17 16:55   ` Max Reitz
2015-03-27 19:20 ` [Qemu-devel] [PATCH v2 11/11] iotests: 124 - transactional failure test John Snow
2015-04-17 17:04   ` Max Reitz
2015-04-18  1:01 ` [Qemu-devel] [PATCH v2.5 00/10] block: incremental backup transactions John Snow
2015-04-21 13:53   ` Kashyap Chamarthy
2015-04-21 14:48     ` Kashyap Chamarthy
2015-04-21 20:33     ` Kashyap Chamarthy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=55312993.2090103@redhat.com \
    --to=mreitz@redhat.com \
    --cc=famz@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    --cc=vsementsov@parallels.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.