From: John Snow <jsnow@redhat.com>
To: Max Reitz <mreitz@redhat.com>, qemu-block@nongnu.org
Cc: kwolf@redhat.com, vsementsov@parallels.com, famz@redhat.com,
qemu-devel@nongnu.org, stefanha@redhat.com
Subject: Re: [Qemu-devel] [PATCH v2 05/11] block: add transactional callbacks feature
Date: Fri, 17 Apr 2015 17:55:56 -0400 [thread overview]
Message-ID: <5531816C.7010708@redhat.com> (raw)
In-Reply-To: <55312993.2090103@redhat.com>
On 04/17/2015 11:41 AM, Max Reitz wrote:
> 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! :-)
>
I do, too.
> 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.
>
Or just not needed at all, as you point out.
>> +
>> + state->refcount--;
>> + if (state->refcount == 0) {
>> +
>
> Superfluous empty line.
>
As an American, I like wide open spaces.
>> + 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
>
It's a holdover from the earlier revision where there were two lists (a
list of completed items and a list of all items), because QTAILQ_REMOVE
does not properly accommodate the case where the key to remove is not
present within the list.
Since I unified the lists, though, there's no confusion about which
actions are in which lists. It's now just a superfluous safety dance.
>> + 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);
>> }
>
>
next prev parent reply other threads:[~2015-04-17 23: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
2015-04-17 21:55 ` John Snow [this message]
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=5531816C.7010708@redhat.com \
--to=jsnow@redhat.com \
--cc=famz@redhat.com \
--cc=kwolf@redhat.com \
--cc=mreitz@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).