All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 00/11] block: incremental backup transactions
@ 2015-03-05  4:15 John Snow
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations John Snow
                   ` (10 more replies)
  0 siblings, 11 replies; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

This series adds support for incremental backup primitives in QMP
transactions. It requires my transactionless incremental backup series.

Patch 1 adds basic support for add and clear transactions.
Patch 2 tests this basic support.

Patches 3-7 add support for error scenarios where only
some incremental backup jobs complete during a transaction.
See commits for patches #3 and #7 for more details, as these
are likely to be the most controversial additions.

Patches 8-11 test the feature added in 3-7.

--js

John Snow (11):
  qapi: Add transaction support to block-dirty-bitmap operations
  iotests: add transactional incremental backup test
  block: add transactional callbacks feature
  block: add refcount to Job object
  block: add delayed bitmap successor cleanup
  qmp: Add an implementation wrapper for qmp_drive_backup
  block: drive_backup transaction callback support
  iotests: add QMP event waiting queue
  iotests: test 124 - drive object refactoring
  iotests: 124 - backup_prepare refactoring
  iotests: 124 - transactional failure test

 block.c                       |  79 +++++++-
 block/backup.c                |  32 +--
 blockdev.c                    | 441 ++++++++++++++++++++++++++++++++++++++++--
 blockjob.c                    |  18 +-
 include/block/block.h         |  11 +-
 include/block/block_int.h     |   8 +
 include/block/blockjob.h      |  21 ++
 qapi-schema.json              |   6 +-
 tests/qemu-iotests/124        | 363 +++++++++++++++++++++++++++-------
 tests/qemu-iotests/124.out    |   4 +-
 tests/qemu-iotests/iotests.py |  38 ++++
 11 files changed, 896 insertions(+), 125 deletions(-)

-- 
1.9.3

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 15:14   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test John Snow
                   ` (9 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

This adds two qmp commands to transactions.

block-dirty-bitmap-add allows you to create a bitmap simultaneously
alongside a new full backup to accomplish a clean synchronization
point.

block-dirty-bitmap-clear allows you to reset a bitmap back to as-if
it were new, which can also be used alongside a full backup to
accomplish a clean synchronization point.

Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
 blockdev.c       | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qapi-schema.json |   6 +++-
 2 files changed, 106 insertions(+), 1 deletion(-)

diff --git a/blockdev.c b/blockdev.c
index e0671be..5120af1 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1664,6 +1664,96 @@ static void blockdev_backup_clean(BlkTransactionState *common)
     }
 }
 
+typedef struct BlockDirtyBitmapState {
+    BlkTransactionState common;
+    BdrvDirtyBitmap *bitmap;
+    BlockDriverState *bs;
+    AioContext *aio_context;
+    bool prepared;
+} BlockDirtyBitmapState;
+
+static void block_dirty_bitmap_add_prepare(BlkTransactionState *common,
+                                           Error **errp)
+{
+    Error *local_err = NULL;
+    BlockDirtyBitmapAdd *action;
+    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+                                             common, common);
+
+    action = common->action->block_dirty_bitmap_add;
+    /* AIO context taken within qmp_block_dirty_bitmap_add */
+    qmp_block_dirty_bitmap_add(action->node, action->name,
+                               action->has_granularity, action->granularity,
+                               &local_err);
+
+    if (!local_err) {
+        state->prepared = true;
+    } else {
+        error_propagate(errp, local_err);
+    }
+}
+
+static void block_dirty_bitmap_add_abort(BlkTransactionState *common)
+{
+    BlockDirtyBitmapAdd *action;
+   BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+                                             common, common);
+
+    action = common->action->block_dirty_bitmap_add;
+    /* Should not be able to fail: IF the bitmap was added via .prepare(),
+     * then the node reference and bitmap name must have been valid.
+     */
+    if (state->prepared) {
+        qmp_block_dirty_bitmap_remove(action->node, action->name, &error_abort);
+    }
+}
+
+static void block_dirty_bitmap_clear_prepare(BlkTransactionState *common,
+                                             Error **errp)
+{
+    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+                                             common, common);
+    BlockDirtyBitmap *action;
+
+    action = common->action->block_dirty_bitmap_clear;
+    state->bitmap = block_dirty_bitmap_lookup(action->node,
+                                              action->name,
+                                              &state->bs,
+                                              errp);
+    if (!state->bitmap) {
+        return;
+    }
+
+    if (bdrv_dirty_bitmap_frozen(state->bitmap)) {
+        error_setg(errp, "Cannot modify a frozen bitmap");
+        return;
+    } else if (!bdrv_dirty_bitmap_enabled(state->bitmap)) {
+        error_setg(errp, "Cannot clear a disabled bitmap");
+        return;
+    }
+
+    /* AioContext is released in .clean() */
+    state->aio_context = bdrv_get_aio_context(state->bs);
+    aio_context_acquire(state->aio_context);
+}
+
+static void block_dirty_bitmap_clear_commit(BlkTransactionState *common)
+{
+    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+                                             common, common);
+    bdrv_clear_dirty_bitmap(state->bitmap);
+}
+
+static void block_dirty_bitmap_clear_clean(BlkTransactionState *common)
+{
+    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+                                             common, common);
+
+    if (state->aio_context) {
+        aio_context_release(state->aio_context);
+    }
+}
+
 static void abort_prepare(BlkTransactionState *common, Error **errp)
 {
     error_setg(errp, "Transaction aborted using Abort action");
@@ -1704,6 +1794,17 @@ static const BdrvActionOps actions[] = {
         .abort = internal_snapshot_abort,
         .clean = internal_snapshot_clean,
     },
+    [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = {
+        .instance_size = sizeof(BlockDirtyBitmapState),
+        .prepare = block_dirty_bitmap_add_prepare,
+        .abort = block_dirty_bitmap_add_abort,
+    },
+    [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = {
+        .instance_size = sizeof(BlockDirtyBitmapState),
+        .prepare = block_dirty_bitmap_clear_prepare,
+        .commit = block_dirty_bitmap_clear_commit,
+        .clean = block_dirty_bitmap_clear_clean,
+    }
 };
 
 /*
diff --git a/qapi-schema.json b/qapi-schema.json
index e16f8eb..12c61f3 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1332,6 +1332,8 @@
 # abort since 1.6
 # blockdev-snapshot-internal-sync since 1.7
 # blockdev-backup since 2.3
+# block-dirty-bitmap-add since 2.3
+# block-dirty-bitmap-clear since 2.3
 ##
 { 'union': 'TransactionAction',
   'data': {
@@ -1339,7 +1341,9 @@
        'drive-backup': 'DriveBackup',
        'blockdev-backup': 'BlockdevBackup',
        'abort': 'Abort',
-       'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal'
+       'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
+       'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
+       'block-dirty-bitmap-clear': 'BlockDirtyBitmap'
    } }
 
 ##
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-11 12:11   ` Kashyap Chamarthy
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature John Snow
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

Reviewed-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
 tests/qemu-iotests/124     | 49 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/124.out |  4 ++--
 2 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 1c07387..da0bf6d 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -184,6 +184,55 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         return True
 
 
+    def test_incremental_transaction(self):
+        '''Test: Verify backups made from transactionally created bitmaps.
+
+        Create a bitmap "before" VM execution begins, then create a second
+        bitmap AFTER writes have already occurred. Use transactions to create
+        a full backup and synchronize both bitmaps to this backup.
+        Create an incremental backup through both bitmaps and verify that
+        both backups match the full backup.
+        '''
+        bitmap0 = self.add_bitmap('bitmap0', 'drive0')
+        self.hmp_io_writes('drive0', (('0xab', 0, 512),
+                                      ('0xfe', '16M', '256k'),
+                                      ('0x64', '32736k', '64k')))
+        bitmap1 = self.add_bitmap('bitmap1', 'drive0')
+
+        result = self.vm.qmp('transaction', actions=[
+            {
+                'type': 'block-dirty-bitmap-clear',
+                'data': { 'node': 'drive0',
+                          'name': 'bitmap0' },
+            },
+            {
+                'type': 'block-dirty-bitmap-clear',
+                'data': { 'node': 'drive0',
+                          'name': 'bitmap1' },
+            },
+            {
+                'type': 'drive-backup',
+                'data': { 'device': 'drive0',
+                          'sync': 'full',
+                          'format': iotests.imgfmt,
+                          'target': self.full_bak },
+            }
+        ])
+        self.assert_qmp(result, 'return', {})
+        self.wait_until_completed()
+        self.files.append(self.full_bak)
+        self.check_full_backup()
+
+        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
+                                      ('0x55', '8M', '352k'),
+                                      ('0x78', '15872k', '1M')))
+        # Both bitmaps should be in sync and create fully valid
+        # incremental backups
+        res1 = self.create_incremental(bitmap0)
+        res2 = self.create_incremental(bitmap1)
+        self.assertTrue(res1 and res2)
+
+
     def test_incremental_failure(self):
         '''Test: Verify backups made after a failure are correct.
 
diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
index 89968f3..914e373 100644
--- a/tests/qemu-iotests/124.out
+++ b/tests/qemu-iotests/124.out
@@ -1,5 +1,5 @@
-....
+.....
 ----------------------------------------------------------------------
-Ran 4 tests
+Ran 5 tests
 
 OK
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations John Snow
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 17:47   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 04/11] block: add refcount to Job object John Snow
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

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
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_transaction_wrapper, which
creates a wrapper around a closure and callback that would originally have
been passed to e.g. backup_start().

The function will return a function pointer and a new closure to be passed
instead. The transaction system will effectively intercept these callbacks
and execute the desired actions upon reception of all intercepted callbacks.

This means that the registered callbacks will be called after all other
transaction actions that requested a callback have completed. The feature
has no knowledge of jobs spawned without informing the BlkTransactionList.

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 in drive_backup (or any block job launched by transactions).


Note 1: Defining a callback method alone is not sufficient to have the new
        method invoked. You must call new_transaction_wrapper AND ensure the
        callback it returns to you is used as the callback for the job.

Note 2: You can use this feature for any system that registers completions of
        an action via a callback of the form (void *opaque, int ret), not just
        block job callbacks.

Note 3: new_blk_transaction has no users in this patch, but will in
        the next patch where it will become static and local to blockdev.c.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 blockdev.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 223 insertions(+), 2 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index 5120af1..3153ee7 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
 /* New and old BlockDriverState structs for atomic group operations */
 
 typedef struct BlkTransactionState BlkTransactionState;
+typedef struct BlkTransactionList BlkTransactionList;
+typedef struct BlkTransactionData BlkTransactionData;
 
 /* Only prepare() may fail. In a single transaction, only one of commit() or
    abort() will be called, clean() will always be called if it present. */
@@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {
     void (*abort)(BlkTransactionState *common);
     /* Clean up resource in the end, can be NULL. */
     void (*clean)(BlkTransactionState *common);
+    /* Execute this after +all+ jobs in the transaction finish */
+    void (*cb)(BlkTransactionState *common);
 } BdrvActionOps;
 
 /*
@@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {
 struct BlkTransactionState {
     TransactionAction *action;
     const BdrvActionOps *ops;
+    BlkTransactionList *list;
+    void *opaque;
+    /* Allow external users (callbacks) to reference this obj past .clean() */
+    int refcount;
+    /* All transactions in the current group */
     QSIMPLEQ_ENTRY(BlkTransactionState) entry;
+    /* Transactions in the current group with callbacks */
+    QSIMPLEQ_ENTRY(BlkTransactionState) list_entry;
 };
 
+struct BlkTransactionList {
+    int jobs;     /* Effectively: A refcount */
+    int status;   /* Cumulative retcode */
+    QSIMPLEQ_HEAD(actions, BlkTransactionState) actions;
+};
+
+typedef struct BlkTransactionData {
+    void *opaque; /* Data given to encapsulated callback */
+    int ret;      /* Return code given to encapsulated callback */
+} BlkTransactionData;
+
+typedef struct BlkTransactionWrapper {
+    void *opaque; /* Data to be given to encapsulated callback */
+    void (*callback)(void *opaque, int ret); /* Encapsulated callback */
+} BlkTransactionWrapper;
+
+static BlkTransactionList *new_blk_transaction_list(void)
+{
+    BlkTransactionList *btl = g_malloc0(sizeof(*btl));
+
+    /* Implicit 'job' for qmp_transaction itself */
+    btl->jobs = 1;
+    QSIMPLEQ_INIT(&btl->actions);
+    return btl;
+}
+
+static BlkTransactionState *blk_put_transaction_state(BlkTransactionState *bts)
+{
+    bts->refcount--;
+    if (bts->refcount == 0) {
+        g_free(bts);
+        return NULL;
+    }
+    return bts;
+}
+
+static void del_blk_transaction_list(BlkTransactionList *btl)
+{
+    BlkTransactionState *bts, *bts_next;
+
+    /* The list should in normal cases be empty,
+     * but in case someone really just wants to kibosh the whole deal: */
+    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
+        g_free(bts->opaque);
+        blk_put_transaction_state(bts);
+    }
+
+    g_free(btl);
+}
+
+static void blk_run_transaction_callbacks(BlkTransactionList *btl)
+{
+    BlkTransactionState *bts, *bts_next;
+
+    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
+        if (bts->ops->cb) {
+            bts->ops->cb(bts);
+        }
+
+        /* Free the BlkTransactionData */
+        g_free(bts->opaque);
+        bts->opaque = NULL;
+        blk_put_transaction_state(bts);
+    }
+
+    QSIMPLEQ_INIT(&btl->actions);
+}
+
+static BlkTransactionList *put_blk_transaction_list(BlkTransactionList *btl)
+{
+    btl->jobs--;
+    if (btl->jobs == 0) {
+        blk_run_transaction_callbacks(btl);
+        del_blk_transaction_list(btl);
+        return NULL;
+    }
+    return btl;
+}
+
+static void blk_transaction_complete(BlkTransactionState *common)
+{
+    BlkTransactionList *btl = common->list;
+
+    /* Add this action into the pile to be completed */
+    QSIMPLEQ_INSERT_TAIL(&btl->actions, common, list_entry);
+
+    /* Inform the list that we have a completion;
+     * possibly run all the pending actions. */
+    put_blk_transaction_list(btl);
+}
+
+/**
+ * Intercept a callback that was issued due to a transactional action.
+ */
+static void transaction_callback(void *opaque, int ret)
+{
+    BlkTransactionState *common = opaque;
+    BlkTransactionWrapper *btw = common->opaque;
+
+    /* Prepare data for ops->cb() */
+    BlkTransactionData *btd = g_malloc0(sizeof(*btd));
+    btd->opaque = btw->opaque;
+    btd->ret = ret;
+
+    /* Transaction state now tracks OUR data */
+    common->opaque = btd;
+
+    /* Keep track of the amalgamated return code */
+    common->list->status |= ret;
+
+    /* Deliver the intercepted callback FIRST */
+    btw->callback(btw->opaque, ret);
+    blk_transaction_complete(common);
+    g_free(btw);
+}
+
+typedef void (CallbackFn)(void *opaque, int ret);
+
+/* Temporary. Removed in the next patch. */
+CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
+                                    void *opaque,
+                                    void (*callback)(void *, int),
+                                    void **new_opaque);
+
+void undo_transaction_wrapper(BlkTransactionState *common);
+
+/**
+ * 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 transactional state to set a callback for.
+ * @opaque A closure intended for the encapsulated callback.
+ * @callback The callback we are encapsulating.
+ * @new_opaque The closure to be used instead of @opaque.
+ *
+ * @return The callback to be used instead of @callback.
+ */
+CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
+                                           void *opaque,
+                                           CallbackFn *callback,
+                                           void **new_opaque)
+{
+    BlkTransactionWrapper *btw = g_malloc0(sizeof(*btw));
+    assert(new_opaque);
+
+    /* Stash the original callback information */
+    btw->opaque = opaque;
+    btw->callback = callback;
+    common->opaque = btw;
+
+    /* The BTS will serve as our new closure */
+    *new_opaque = common;
+    common->refcount++;
+
+    /* Inform the transaction BTL to expect one more return */
+    common->list->jobs++;
+
+    /* Lastly, the actual callback function to handle the interception. */
+    return transaction_callback;
+}
+
+/**
+ * Undo any actions performed by the above call.
+ */
+void undo_transaction_wrapper(BlkTransactionState *common)
+{
+    BlkTransactionList *btl = common->list;
+    BlkTransactionState *bts;
+    BlkTransactionData *btd;
+
+    /* Stage 0: Wrapper was never created: */
+    if (common->opaque == NULL && common->refcount == 1) {
+        return;
+    }
+
+    /* 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.
+     */
+    QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) {
+        if (bts == common) {
+            btd = common->opaque;
+            /* Force error for callback */
+            btd->ret = -1;
+            common->ops->cb(common);
+            QSIMPLEQ_REMOVE(&btl->actions, common,
+                            BlkTransactionState, list_entry);
+            goto cleanup;
+        }
+    }
+    /* Stage 1: Callback created, but job never launched */
+    put_blk_transaction_list(common->list);
+ cleanup:
+    g_free(common->opaque);
+    blk_put_transaction_state(common);
+}
+
 /* internal snapshot private data */
 typedef struct InternalSnapshotState {
     BlkTransactionState common;
@@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = {
         .instance_size = sizeof(DriveBackupState),
         .prepare = drive_backup_prepare,
         .abort = drive_backup_abort,
-        .clean = drive_backup_clean,
+        .clean = drive_backup_clean
     },
     [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
         .instance_size = sizeof(BlockdevBackupState),
@@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)
 {
     TransactionActionList *dev_entry = dev_list;
     BlkTransactionState *state, *next;
+    BlkTransactionList *btl;
     Error *local_err = NULL;
 
     QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states;
     QSIMPLEQ_INIT(&snap_bdrv_states);
+    btl = new_blk_transaction_list();
 
     /* drain all i/o before any operations */
     bdrv_drain_all();
@@ -1837,8 +2054,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->list = btl;
         QSIMPLEQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
 
         state->ops->prepare(state, &local_err);
@@ -1869,8 +2088,10 @@ exit:
         if (state->ops->clean) {
             state->ops->clean(state);
         }
-        g_free(state);
+        blk_put_transaction_state(state);
     }
+
+    put_blk_transaction_list(btl);
 }
 
 
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 04/11] block: add refcount to Job object
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (2 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 17:54   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup John Snow
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

If we want to get at the job after the life of the job,
we'll need a refcount for this object.

This may occur for example if we wish to inspect the actions
taken by a particular job after a transactional group of jobs
runs, and further actions are required.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 blockjob.c               | 18 ++++++++++++++++--
 include/block/blockjob.h | 21 +++++++++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/blockjob.c b/blockjob.c
index ba2255d..d620082 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -35,6 +35,19 @@
 #include "qemu/timer.h"
 #include "qapi-event.h"
 
+void block_job_incref(BlockJob *job)
+{
+    job->refcount++;
+}
+
+void block_job_decref(BlockJob *job)
+{
+    job->refcount--;
+    if (job->refcount == 0) {
+        g_free(job);
+    }
+}
+
 void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
                        int64_t speed, BlockCompletionFunc *cb,
                        void *opaque, Error **errp)
@@ -57,6 +70,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
     job->cb            = cb;
     job->opaque        = opaque;
     job->busy          = true;
+    job->refcount      = 1;
     bs->job = job;
 
     /* Only set speed when necessary to avoid NotSupported error */
@@ -68,7 +82,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
             bs->job = NULL;
             bdrv_op_unblock_all(bs, job->blocker);
             error_free(job->blocker);
-            g_free(job);
+            block_job_decref(job);
             error_propagate(errp, local_err);
             return NULL;
         }
@@ -85,7 +99,7 @@ void block_job_completed(BlockJob *job, int ret)
     bs->job = NULL;
     bdrv_op_unblock_all(bs, job->blocker);
     error_free(job->blocker);
-    g_free(job);
+    block_job_decref(job);
 }
 
 void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index b6d4ebb..8fe1c95 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -116,6 +116,9 @@ struct BlockJob {
 
     /** The opaque value that is passed to the completion function.  */
     void *opaque;
+
+    /** A reference count, allowing for post-job actions in e.g. transactions */
+    int refcount;
 };
 
 /**
@@ -141,6 +144,24 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
                        void *opaque, Error **errp);
 
 /**
+ * block_job_incref:
+ * @job: The job to pick up a handle to
+ *
+ * Increment the refcount on @job, to be able to use it asynchronously
+ * from the job it is being used for. Put down the reference when done
+ * with @block_job_unref
+ */
+void block_job_incref(BlockJob *job);
+
+/**
+ * block_job_decref:
+ * @job: The job to unreference and delete.
+ *
+ * Decrement the refcount on @job, and delete it if there are no users.
+ */
+void block_job_decref(BlockJob *job);
+
+/**
  * block_job_sleep_ns:
  * @job: The job that calls the function.
  * @clock: The clock to sleep on.
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (3 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 04/11] block: add refcount to Job object John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 18:44   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup John Snow
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

Allow bitmap successors to carry reference counts.

We can in a later patch use this ability to clean up the dirty bitmap
according to both the individual job's success and the success of all
jobs in the transaction group.

The code for cleaning up a bitmap is also moved from backup_run to
backup_complete.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 block.c               | 79 +++++++++++++++++++++++++++++++++++++++++++++++----
 block/backup.c        | 23 ++++++---------
 include/block/block.h | 11 ++++---
 3 files changed, 87 insertions(+), 26 deletions(-)

diff --git a/block.c b/block.c
index 5eaa874..a0036af 100644
--- a/block.c
+++ b/block.c
@@ -51,6 +51,12 @@
 #include <windows.h>
 #endif
 
+typedef enum BitmapSuccessorAction {
+    SUCCESSOR_ACTION_UNDEFINED = 0,
+    SUCCESSOR_ACTION_ABDICATE,
+    SUCCESSOR_ACTION_RECLAIM
+} BitmapSuccessorAction;
+
 /**
  * A BdrvDirtyBitmap can be in three possible states:
  * (1) successor is false and disabled is false: full r/w mode
@@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {
     char *name;                 /* Optional non-empty unique ID */
     int64_t size;               /* Size of the bitmap (Number of sectors) */
     bool disabled;              /* Bitmap is read-only */
+    int successor_refcount;     /* Number of active handles to the successor */
+    BitmapSuccessorAction act;  /* Action to take on successor upon release */
     QLIST_ENTRY(BdrvDirtyBitmap) list;
 };
 
@@ -5508,6 +5516,7 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
 
     /* Install the successor and freeze the parent */
     bitmap->successor = child;
+    bitmap->successor_refcount = 1;
     return 0;
 }
 
@@ -5515,9 +5524,9 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
  * For a bitmap with a successor, yield our name to the successor,
  * Delete the old bitmap, and return a handle to the new bitmap.
  */
-BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
-                                            BdrvDirtyBitmap *bitmap,
-                                            Error **errp)
+static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
+                                                   BdrvDirtyBitmap *bitmap,
+                                                   Error **errp)
 {
     char *name;
     BdrvDirtyBitmap *successor = bitmap->successor;
@@ -5542,9 +5551,9 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
  * We may wish to re-join the parent and child/successor.
  * The merged parent will be un-frozen, but not explicitly re-enabled.
  */
-BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
-                                           BdrvDirtyBitmap *parent,
-                                           Error **errp)
+static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
+                                                  BdrvDirtyBitmap *parent,
+                                                  Error **errp)
 {
     BdrvDirtyBitmap *successor = parent->successor;
 
@@ -5563,6 +5572,64 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
     return parent;
 }
 
+static BdrvDirtyBitmap *bdrv_free_bitmap_successor(BlockDriverState *bs,
+                                                   BdrvDirtyBitmap *parent,
+                                                   Error **errp)
+{
+    if (parent->successor_refcount) {
+        error_setg(errp, "Cannot free the successor for bitmap '%s', "
+                   "because the refcount is non-zero.", parent->name);
+        return NULL;
+    }
+
+    switch (parent->act) {
+    case SUCCESSOR_ACTION_UNDEFINED:
+        error_setg(errp, "Cannot free the successor for bitmap '%s', "
+                   "because the successor action has not yet been set.",
+                   parent->name);
+        return NULL;
+    case SUCCESSOR_ACTION_RECLAIM:
+        return bdrv_reclaim_dirty_bitmap(bs, parent, errp);
+    case SUCCESSOR_ACTION_ABDICATE:
+        return bdrv_dirty_bitmap_abdicate(bs, parent, errp);
+    default:
+        error_setg(errp,
+                   "Unrecognized successor action (%d), "
+                   "cannot free successor for bitmap '%s'",
+                   parent->act, parent->name);
+        return NULL;
+    }
+}
+
+BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
+                                          BdrvDirtyBitmap *parent,
+                                          int ret,
+                                          Error **errp)
+{
+    assert(bdrv_dirty_bitmap_frozen(parent));
+    assert(parent->successor);
+
+    if (ret) {
+        parent->act = SUCCESSOR_ACTION_RECLAIM;
+    } else if (parent->act != SUCCESSOR_ACTION_RECLAIM) {
+        parent->act = SUCCESSOR_ACTION_ABDICATE;
+    }
+
+    parent->successor_refcount--;
+    if (parent->successor_refcount == 0) {
+        return bdrv_free_bitmap_successor(bs, parent, errp);
+    }
+    return parent;
+}
+
+void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent)
+{
+    assert(bdrv_dirty_bitmap_frozen(parent));
+    assert(parent->successor);
+
+    parent->successor_refcount++;
+}
+
 static void dirty_bitmap_truncate(BdrvDirtyBitmap *bitmap, uint64_t size)
 {
     /* Should only be frozen during a block backup job, which should have
diff --git a/block/backup.c b/block/backup.c
index 41bd9af..4332df4 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)
 
     bdrv_unref(s->target);
 
+    if (s->sync_bitmap) {
+        BdrvDirtyBitmap *bm;
+        bm = bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, data->ret, NULL);
+        assert(bm);
+    }
+
     block_job_completed(job, data->ret);
     g_free(data);
 }
@@ -419,19 +425,6 @@ leave:
     qemu_co_rwlock_wrlock(&job->flush_rwlock);
     qemu_co_rwlock_unlock(&job->flush_rwlock);
 
-    if (job->sync_bitmap) {
-        BdrvDirtyBitmap *bm;
-        if (ret < 0) {
-            /* Merge the successor back into the parent, delete nothing. */
-            bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
-            assert(bm);
-            bdrv_enable_dirty_bitmap(job->sync_bitmap);
-        } else {
-            /* Everything is fine, delete this bitmap and install the backup. */
-            bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
-            assert(bm);
-        }
-    }
     hbitmap_free(job->bitmap);
 
     bdrv_iostatus_disable(target);
@@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
 
  error:
     if (sync_bitmap) {
-        bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
+        BdrvDirtyBitmap *ret;
+        ret = bdrv_dirty_bitmap_decref(bs, sync_bitmap, -1, NULL);
+        assert(ret);
     }
 }
diff --git a/include/block/block.h b/include/block/block.h
index 3a85690..d7859a7 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);
 int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
                                        BdrvDirtyBitmap *bitmap,
                                        Error **errp);
-BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
-                                            BdrvDirtyBitmap *bitmap,
-                                            Error **errp);
-BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
-                                           BdrvDirtyBitmap *bitmap,
-                                           Error **errp);
+BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
+                                          BdrvDirtyBitmap *parent,
+                                          int ret,
+                                          Error **errp);
+void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent);
 BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
                                         const char *name);
 void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (4 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 18:51   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support John Snow
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

We'd like to be able to specify the callback given to backup_start
manually in the case of transactions, so split apart qmp_drive_backup
into an implementation and a wrapper.

Switch drive_backup_prepare to use the new wrapper, but don't overload
the callback and closure yet.

This is kind of gross, but we need to forward-declare the wrapper for
the drive_backup transaction to be able to use. I could try moving
the transaction blocks lower instead, but I wanted (for v1, at least)
to minimize code movement so that the core changes were easiest to see.

If anyone has suggestions on organization, please suggest them.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 blockdev.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 58 insertions(+), 19 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index 3153ee7..9e3b9e9 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1449,6 +1449,19 @@ void undo_transaction_wrapper(BlkTransactionState *common)
     blk_put_transaction_state(common);
 }
 
+static void _drive_backup(const char *device, const char *target,
+                          bool has_format, const char *format,
+                          enum MirrorSyncMode sync,
+                          bool has_mode, enum NewImageMode mode,
+                          bool has_speed, int64_t speed,
+                          bool has_bitmap, const char *bitmap,
+                          bool has_on_source_error,
+                          BlockdevOnError on_source_error,
+                          bool has_on_target_error,
+                          BlockdevOnError on_target_error,
+                          BlockCompletionFunc *cb, void *opaque,
+                          Error **errp);
+
 /* internal snapshot private data */
 typedef struct InternalSnapshotState {
     BlkTransactionState common;
@@ -1768,15 +1781,16 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
     state->aio_context = bdrv_get_aio_context(bs);
     aio_context_acquire(state->aio_context);
 
-    qmp_drive_backup(backup->device, backup->target,
-                     backup->has_format, backup->format,
-                     backup->sync,
-                     backup->has_mode, backup->mode,
-                     backup->has_speed, backup->speed,
-                     backup->has_bitmap, backup->bitmap,
-                     backup->has_on_source_error, backup->on_source_error,
-                     backup->has_on_target_error, backup->on_target_error,
-                     &local_err);
+    _drive_backup(backup->device, backup->target,
+                  backup->has_format, backup->format,
+                  backup->sync,
+                  backup->has_mode, backup->mode,
+                  backup->has_speed, backup->speed,
+                  backup->has_bitmap, backup->bitmap,
+                  backup->has_on_source_error, backup->on_source_error,
+                  backup->has_on_target_error, backup->on_target_error,
+                  NULL, NULL,
+                  &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
         return;
@@ -2717,15 +2731,18 @@ out:
     aio_context_release(aio_context);
 }
 
-void qmp_drive_backup(const char *device, const char *target,
-                      bool has_format, const char *format,
-                      enum MirrorSyncMode sync,
-                      bool has_mode, enum NewImageMode mode,
-                      bool has_speed, int64_t speed,
-                      bool has_bitmap, const char *bitmap,
-                      bool has_on_source_error, BlockdevOnError on_source_error,
-                      bool has_on_target_error, BlockdevOnError on_target_error,
-                      Error **errp)
+static void _drive_backup(const char *device, const char *target,
+                          bool has_format, const char *format,
+                          enum MirrorSyncMode sync,
+                          bool has_mode, enum NewImageMode mode,
+                          bool has_speed, int64_t speed,
+                          bool has_bitmap, const char *bitmap,
+                          bool has_on_source_error,
+                          BlockdevOnError on_source_error,
+                          bool has_on_target_error,
+                          BlockdevOnError on_target_error,
+                          BlockCompletionFunc *cb, void *opaque,
+                          Error **errp)
 {
     BlockDriverState *bs;
     BlockDriverState *target_bs;
@@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const char *target,
         }
     }
 
+    if (cb == NULL) {
+        cb = block_job_cb;
+    }
+    if (opaque == NULL) {
+        opaque = bs;
+    }
     backup_start(bs, target_bs, speed, sync, bmap,
                  on_source_error, on_target_error,
-                 block_job_cb, bs, &local_err);
+                 cb, opaque, &local_err);
     if (local_err != NULL) {
         bdrv_unref(target_bs);
         error_propagate(errp, local_err);
@@ -2850,6 +2873,22 @@ out:
     aio_context_release(aio_context);
 }
 
+void qmp_drive_backup(const char *device, const char *target,
+                      bool has_format, const char *format,
+                      enum MirrorSyncMode sync,
+                      bool has_mode, enum NewImageMode mode,
+                      bool has_speed, int64_t speed,
+                      bool has_bitmap, const char *bitmap,
+                      bool has_on_source_error, BlockdevOnError on_source_error,
+                      bool has_on_target_error, BlockdevOnError on_target_error,
+                      Error **errp)
+{
+    _drive_backup(device, target, has_format, format, sync, has_mode, mode,
+                  has_speed, speed, has_bitmap, bitmap, has_on_source_error,
+                  on_source_error, has_on_target_error, on_target_error,
+                  NULL, NULL, errp);
+}
+
 BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
 {
     return bdrv_named_nodes_list();
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (5 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 19:49   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue John Snow
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

This patch actually implements the transactional callback system
for the drive_backup transaction.

(1) We manually pick up a reference to the bitmap if present to allow
    its cleanup to be delayed until after all drive_backup jobs launched
    by the transaction have fully completed.

(2) We create a functional closure that envelops the original drive_backup
    callback, to be able to intercept the completion status and return code
    for the job.

(3) We add the drive_backup_cb method for the drive_backup action, which
    unpacks the completion information and invokes the final cleanup.

(4) backup_transaction_complete will perform the final cleanup on the
    backup job.

(5) In the case of transaction cancellation, drive_backup_cb is still
    responsible for cleaning up the mess we may have already made.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 block/backup.c            |  9 +++++++
 blockdev.c                | 64 ++++++++++++++++++++++++++++++++++++++---------
 include/block/block_int.h |  8 ++++++
 3 files changed, 69 insertions(+), 12 deletions(-)

diff --git a/block/backup.c b/block/backup.c
index 4332df4..3673fb0 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -233,6 +233,15 @@ typedef struct {
     int ret;
 } BackupCompleteData;
 
+void backup_transaction_complete(BlockJob *job, int ret)
+{
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+
+    if (s->sync_bitmap) {
+        bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, ret, NULL);
+    }
+}
+
 static void backup_complete(BlockJob *job, void *opaque)
 {
     BackupBlockJob *s = container_of(job, BackupBlockJob, common);
diff --git a/blockdev.c b/blockdev.c
index 9e3b9e9..3ff14a7 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)
 
 typedef void (CallbackFn)(void *opaque, int ret);
 
-/* Temporary. Removed in the next patch. */
-CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
-                                    void *opaque,
-                                    void (*callback)(void *, int),
-                                    void **new_opaque);
-
-void undo_transaction_wrapper(BlkTransactionState *common);
-
 /**
  * Create a new transactional callback wrapper.
  *
@@ -1389,7 +1381,7 @@ void undo_transaction_wrapper(BlkTransactionState *common);
  *
  * @return The callback to be used instead of @callback.
  */
-CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
+static CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
                                            void *opaque,
                                            CallbackFn *callback,
                                            void **new_opaque)
@@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
 /**
  * Undo any actions performed by the above call.
  */
-void undo_transaction_wrapper(BlkTransactionState *common)
+static void undo_transaction_wrapper(BlkTransactionState *common)
 {
     BlkTransactionList *btl = common->list;
     BlkTransactionState *bts;
@@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)
     blk_put_transaction_state(common);
 }
 
+static void block_job_cb(void *opaque, int ret);
 static void _drive_backup(const char *device, const char *target,
                           bool has_format, const char *format,
                           enum MirrorSyncMode sync,
@@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
     BlockDriverState *bs;
     DriveBackup *backup;
     Error *local_err = NULL;
+    CallbackFn *cb;
+    void *opaque;
+    BdrvDirtyBitmap *bmap = NULL;
 
     assert(common->action->kind == TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
     backup = common->action->drive_backup;
@@ -1777,6 +1773,19 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
         return;
     }
 
+    /* BackupBlockJob is opaque to us, so look up the bitmap ourselves */
+    if (backup->has_bitmap) {
+        bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap);
+        if (!bmap) {
+            error_setg(errp, "Bitmap '%s' could not be found", backup->bitmap);
+            return;
+        }
+    }
+
+    /* Create our transactional callback wrapper,
+       and register that we'd like to call .cb() later. */
+    cb = new_transaction_wrapper(common, bs, block_job_cb, &opaque);
+
     /* AioContext is released in .clean() */
     state->aio_context = bdrv_get_aio_context(bs);
     aio_context_acquire(state->aio_context);
@@ -1789,7 +1798,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
                   backup->has_bitmap, backup->bitmap,
                   backup->has_on_source_error, backup->on_source_error,
                   backup->has_on_target_error, backup->on_target_error,
-                  NULL, NULL,
+                  cb, opaque,
                   &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
@@ -1798,6 +1807,11 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
 
     state->bs = bs;
     state->job = state->bs->job;
+    /* Keep the job alive until .cb(), too. */
+    block_job_incref(state->job);
+    if (bmap) {
+        bdrv_dirty_bitmap_incref(bmap);
+    }
 }
 
 static void drive_backup_abort(BlkTransactionState *common)
@@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)
     if (bs && bs->job && bs->job == state->job) {
         block_job_cancel_sync(bs->job);
     }
+
+    /* Undo any callback actions we may have done. Putting down references
+     * obtained during prepare() is handled by cb(). */
+    undo_transaction_wrapper(common);
 }
 
 static void drive_backup_clean(BlkTransactionState *common)
@@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common)
     }
 }
 
+static void drive_backup_cb(BlkTransactionState *common)
+{
+    BlkTransactionData *btd = common->opaque;
+    BlockDriverState *bs = btd->opaque;
+    DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common);
+
+    assert(state->bs == bs);
+    if (bs->job) {
+        assert(state->job == bs->job);
+    }
+
+    state->aio_context = bdrv_get_aio_context(bs);
+    aio_context_acquire(state->aio_context);
+
+    /* Note: We also have the individual job's return code in btd->ret */
+    backup_transaction_complete(state->job, common->list->status);
+    block_job_decref(state->job);
+
+    aio_context_release(state->aio_context);
+}
+
 typedef struct BlockdevBackupState {
     BlkTransactionState common;
     BlockDriverState *bs;
@@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = {
         .instance_size = sizeof(DriveBackupState),
         .prepare = drive_backup_prepare,
         .abort = drive_backup_abort,
-        .clean = drive_backup_clean
+        .clean = drive_backup_clean,
+        .cb = drive_backup_cb
     },
     [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
         .instance_size = sizeof(BlockdevBackupState),
diff --git a/include/block/block_int.h b/include/block/block_int.h
index e0d5561..731684d 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
                   BlockCompletionFunc *cb, void *opaque,
                   Error **errp);
 
+/*
+ * backup_transaction_complete
+ * @job The BackupJob that was associated with a transaction
+ * @ret Amalgamated return code for the entire transaction
+ */
+void backup_transaction_complete(BlockJob *job, int ret);
+
+
 void blk_dev_change_media_cb(BlockBackend *blk, bool load);
 bool blk_dev_has_removable_media(BlockBackend *blk);
 void blk_dev_eject_request(BlockBackend *blk, bool force);
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (6 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 20:04   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring John Snow
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

A filter is added to allow callers to request very specific
events to be pulled from the event queue, while leaving undesired
events still in the stream.

This allows to poll for completion data for multiple asynchronous
events in any arbitrary order.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 0522501..98d399f 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -78,6 +78,23 @@ def create_image(name, size):
         i = i + 512
     file.close()
 
+# Test if 'match' is a recursive subset of 'event'
+def event_match(event, match = None):
+    if match is None:
+        return True
+
+    for key in match:
+        if key in event:
+            if isinstance(event[key], dict):
+                if not event_match(event[key], match[key]):
+                    return False
+            elif event[key] != match[key]:
+                return False
+        else:
+            return False
+
+    return True
+
 class VM(object):
     '''A QEMU VM'''
 
@@ -92,6 +109,7 @@ class VM(object):
                      '-machine', 'accel=qtest',
                      '-display', 'none', '-vga', 'none']
         self._num_drives = 0
+        self._events = []
 
     # This can be used to add an unused monitor instance.
     def add_monitor_telnet(self, ip, port):
@@ -202,14 +220,34 @@ class VM(object):
 
     def get_qmp_event(self, wait=False):
         '''Poll for one queued QMP events and return it'''
+        if len(self._events) > 0:
+            return self._events.pop(0)
         return self._qmp.pull_event(wait=wait)
 
     def get_qmp_events(self, wait=False):
         '''Poll for queued QMP events and return a list of dicts'''
         events = self._qmp.get_events(wait=wait)
+        events.extend(self._events)
+        del self._events[:]
         self._qmp.clear_events()
         return events
 
+    def event_wait(self, name='BLOCK_JOB_COMPLETED', maxtries=3, match=None):
+        # Search cached events
+        for event in self._events:
+            if (event['event'] == name) and event_match(event, match):
+                self._events.remove(event)
+                return event
+
+        # Poll for new events
+        for _ in range(maxtries):
+            event = self._qmp.pull_event(wait=True)
+            if (event['event'] == name) and event_match(event, match):
+                return event
+            self._events.append(event)
+
+        return None
+
 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
 
 class QMPTestCase(unittest.TestCase):
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (7 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 20:44   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring John Snow
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test John Snow
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

The original test was not particularly good about keeping the
relationships between bitmaps, drives, and images very explicit,
so this patch adds an explicit 'drive' dict that is used to
keep these relationships explicit.

This is necessary in order to test two full backup chains
simultaneously for two drives, which will happen in a forthcoming
test that examines failure scenarios for incremental backups created
for multiple drives in a single transaction.

Highlights:

- Each test case carries around a list of drives
- Each bitmap now acknowledges the full backup belonging to the drive
  as its "last target" if it hasn't made an incremental backup yet.
- Most functions generally try to accept a drive argument instead of
  target or format arguments.
- Filenames are now based on their formatting and id name.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 tests/qemu-iotests/124 | 212 +++++++++++++++++++++++++++++--------------------
 1 file changed, 126 insertions(+), 86 deletions(-)

diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index da0bf6d..2eccc3e 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -29,14 +29,18 @@ def io_write_patterns(img, patterns):
         iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
 
 class Bitmap:
-    def __init__(self, name, node):
+    def __init__(self, name, drive):
         self.name = name
-        self.node = node
+        self.drive = drive
         self.pattern = os.path.join(iotests.test_dir.replace('%', '%%'),
-                                    '%s.backup.%%i.img' % name)
+                                    '%s.%s.backup.%%i.img' % (drive['id'],
+                                                              name))
         self.num = 0
         self.backups = list()
 
+    def base_target(self):
+        return self.drive['backup']
+
     def new_target(self, num=None):
         if num is None:
             num = self.num
@@ -46,7 +50,9 @@ class Bitmap:
         return target
 
     def last_target(self):
-        return self.backups[-1]
+        if self.backups:
+            return self.backups[-1]
+        return self.base_target()
 
     def del_target(self):
         os.remove(self.backups.pop())
@@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase):
     def setUp(self):
         self.bitmaps = list()
         self.files = list()
+        self.drives = list()
         self.vm = iotests.VM()
-        self.test_img = os.path.join(iotests.test_dir, 'base.img')
-        self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
-        self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
-        self.img_create(self.test_img, iotests.imgfmt)
-        self.vm.add_drive(self.test_img)
+        self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
+
         # Create a base image with a distinctive patterning
-        io_write_patterns(self.test_img, (('0x41', 0, 512),
-                                          ('0xd5', '1M', '32k'),
-                                          ('0xdc', '32M', '124k')))
+        drive0 = self.add_node('drive0')
+        self.img_create(drive0['file'], drive0['fmt'])
+        self.vm.add_drive(drive0['file'])
+        io_write_patterns(drive0['file'], (('0x41', 0, 512),
+                                           ('0xd5', '1M', '32k'),
+                                           ('0xdc', '32M', '124k')))
         self.vm.launch()
 
 
+    def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
+        if path is None:
+            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
+        if backup is None:
+            backup = os.path.join(iotests.test_dir,
+                                  '%s.full.backup.%s' % (node_id, fmt))
+
+        self.drives.append({
+            'id': node_id,
+            'file': path,
+            'backup': backup,
+            'fmt': fmt })
+        return self.drives[-1]
+
+
     def img_create(self, img, fmt=iotests.imgfmt, size='64M',
                    parent=None, parentFormat=None):
         plist = list()
@@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         self.files.append(img)
 
 
-    def create_full_backup(self, drive='drive0'):
-        res = self.vm.qmp('drive-backup', device=drive,
-                          sync='full', format=iotests.imgfmt,
-                          target=self.full_bak)
+    def create_full_backup(self, drive=None):
+        if drive is None:
+            drive = self.drives[-1]
+
+        res = self.vm.qmp('drive-backup', device=drive['id'],
+                          sync='full', format=drive['fmt'],
+                          target=drive['backup'])
         self.assert_qmp(res, 'return', {})
-        self.wait_until_completed(drive)
-        self.check_full_backup()
-        self.files.append(self.full_bak)
+        self.wait_until_completed(drive['id'])
+        self.check_full_backup(drive)
+        self.files.append(drive['backup'])
+        return drive['backup']
 
 
-    def check_full_backup(self):
-        self.assertTrue(iotests.compare_images(self.test_img, self.full_bak))
+    def check_full_backup(self, drive=None):
+        if drive is None:
+            drive = self.drives[-1]
+        self.assertTrue(iotests.compare_images(drive['file'], drive['backup']))
 
 
-    def add_bitmap(self, name, node='drive0'):
-        bitmap = Bitmap(name, node)
+    def add_bitmap(self, name, drive):
+        bitmap = Bitmap(name, drive)
         self.bitmaps.append(bitmap)
-        result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node,
-                             name=bitmap.name)
+        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
+                             name=name)
         self.assert_qmp(result, 'return', {})
         return bitmap
 
@@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         if bitmap is None:
             bitmap = self.bitmaps[-1]
 
-        # If this is the first incremental backup for a bitmap,
-        # use the full backup as a backing image. Otherwise, use
-        # the last incremental backup.
         if parent is None:
-            if bitmap.num == 0:
-                parent = self.full_bak
-            else:
-                parent = bitmap.last_target()
+            parent = bitmap.last_target()
 
         target = bitmap.new_target(num)
-        self.img_create(target, iotests.imgfmt, parent=parent)
+        self.img_create(target, bitmap.drive['fmt'], parent=parent)
 
-        result = self.vm.qmp('drive-backup', device=bitmap.node,
+        result = self.vm.qmp('drive-backup', device=bitmap.drive['id'],
                              sync='dirty-bitmap', bitmap=bitmap.name,
-                             format=iotests.imgfmt, target=target,
+                             format=bitmap.drive['fmt'], target=target,
                              mode='existing')
         self.assert_qmp(result, 'return', {})
 
-        event = self.wait_until_completed(bitmap.node, check_offset=validate,
-                                          allow_failures=(not validate))
-        if 'error' in event['data']:
+        return self.wait_incremental(bitmap, validate)
+
+
+    def wait_incremental(self, bitmap=None,
+                         validate=True, error='Input/output error'):
+        if bitmap is None:
+            bitmap = self.bitmaps[-1]
+
+        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
+                                   match={'data': {'device':
+                                                   bitmap.drive['id']}})
+        if validate:
+            self.assert_qmp_absent(event, 'data/error')
+            return self.check_incremental(bitmap)
+        else:
+            self.assert_qmp(event, 'data/error', error)
             bitmap.del_target()
             return False
-        if validate:
-            return self.check_incremental(target)
 
 
-    def check_incremental(self, target=None):
-        if target is None:
-            target = self.bitmaps[-1].last_target()
-        self.assertTrue(iotests.compare_images(self.test_img, target))
+    def check_incremental(self, bitmap=None):
+        if bitmap is None:
+            bitmap = self.bitmaps[-1]
+        self.assertTrue(iotests.compare_images(bitmap.drive['file'],
+                                               bitmap.last_target()))
         return True
 
 
@@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         i.e.; after IO requests begin modifying the drive.
         '''
         self.create_full_backup()
-        self.add_bitmap('bitmap0', 'drive0')
+        self.add_bitmap('bitmap0', self.drives[0])
 
         # Sanity: Create a "hollow" incremental backup
         self.create_incremental()
         # Three writes: One complete overwrite, one new segment,
         # and one partial overlap.
-        self.hmp_io_writes('drive0', (('0xab', 0, 512),
-                                      ('0xfe', '16M', '256k'),
-                                      ('0x64', '32736k', '64k')))
+        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
+                                                  ('0xfe', '16M', '256k'),
+                                                  ('0x64', '32736k', '64k')))
         self.create_incremental()
         # Three more writes, one of each kind, like above
-        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
-                                      ('0x55', '8M', '352k'),
-                                      ('0x78', '15872k', '1M')))
+        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
+                                                  ('0x55', '8M', '352k'),
+                                                  ('0x78', '15872k', '1M')))
         self.create_incremental()
         return True
 
@@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         bitmap AFTER writes have already occurred. Use transactions to create
         a full backup and synchronize both bitmaps to this backup.
         Create an incremental backup through both bitmaps and verify that
-        both backups match the full backup.
+        both backups match the current drive0 image.
         '''
-        bitmap0 = self.add_bitmap('bitmap0', 'drive0')
-        self.hmp_io_writes('drive0', (('0xab', 0, 512),
-                                      ('0xfe', '16M', '256k'),
-                                      ('0x64', '32736k', '64k')))
-        bitmap1 = self.add_bitmap('bitmap1', 'drive0')
+
+        drive0 = self.drives[0]
+        bitmap0 = self.add_bitmap('bitmap0', self.drives[0])
+        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
+                                          ('0xfe', '16M', '256k'),
+                                          ('0x64', '32736k', '64k')))
+        bitmap1 = self.add_bitmap('bitmap1', drive0)
 
         result = self.vm.qmp('transaction', actions=[
             {
                 'type': 'block-dirty-bitmap-clear',
-                'data': { 'node': 'drive0',
-                          'name': 'bitmap0' },
+                'data': { 'node': bitmap0.drive['id'],
+                          'name': bitmap0.name },
             },
             {
                 'type': 'block-dirty-bitmap-clear',
-                'data': { 'node': 'drive0',
-                          'name': 'bitmap1' },
+                'data': { 'node': bitmap1.drive['id'],
+                          'name': bitmap1.name },
             },
             {
                 'type': 'drive-backup',
-                'data': { 'device': 'drive0',
+                'data': { 'device': drive0['id'],
                           'sync': 'full',
-                          'format': iotests.imgfmt,
-                          'target': self.full_bak },
+                          'format': drive0['fmt'],
+                          'target': drive0['backup'] },
             }
         ])
         self.assert_qmp(result, 'return', {})
         self.wait_until_completed()
-        self.files.append(self.full_bak)
+        self.files.append(drive0['backup'])
         self.check_full_backup()
 
-        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
-                                      ('0x55', '8M', '352k'),
-                                      ('0x78', '15872k', '1M')))
+        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
+                                          ('0x55', '8M', '352k'),
+                                          ('0x78', '15872k', '1M')))
         # Both bitmaps should be in sync and create fully valid
         # incremental backups
         res1 = self.create_incremental(bitmap0)
@@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         afterwards and verify that the backup created is correct.
         '''
 
-        # Create a blkdebug interface to this img as 'drive1'
+        # Create a blkdebug interface to this img as 'drive1',
+        # but don't actually create a new image.
+        drive1 = self.add_node('drive1', self.drives[0]['fmt'],
+                               path=self.drives[0]['file'],
+                               backup=self.drives[0]['backup'])
         result = self.vm.qmp('blockdev-add', options={
             'id': 'drive1',
-            'driver': iotests.imgfmt,
+            'driver': drive1['fmt'],
             'file': {
                 'driver': 'blkdebug',
                 'image': {
                     'driver': 'file',
-                    'filename': self.test_img
+                    'filename': drive1['file']
                 },
                 'set-state': [{
                     'event': 'flush_to_disk',
@@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         })
         self.assert_qmp(result, 'return', {})
 
-        self.create_full_backup()
-        self.add_bitmap('bitmap0', 'drive1')
+        self.create_full_backup(self.drives[0])
+        self.add_bitmap('bitmap0', drive1)
         # Note: at this point, during a normal execution,
         # Assume that the VM resumes and begins issuing IO requests here.
 
-        self.hmp_io_writes('drive1', (('0xab', 0, 512),
-                                      ('0xfe', '16M', '256k'),
-                                      ('0x64', '32736k', '64k')))
+        self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
+                                          ('0xfe', '16M', '256k'),
+                                          ('0x64', '32736k', '64k')))
 
         result = self.create_incremental(validate=False)
         self.assertFalse(result)
-        self.hmp_io_writes('drive1', (('0x9a', 0, 512),
-                                      ('0x55', '8M', '352k'),
-                                      ('0x78', '15872k', '1M')))
+        self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
+                                          ('0x55', '8M', '352k'),
+                                          ('0x78', '15872k', '1M')))
         self.create_incremental()
 
 
     def test_sync_dirty_bitmap_missing(self):
         self.assert_no_active_block_jobs()
-        self.files.append(self.foo_img)
-        result = self.vm.qmp('drive-backup', device='drive0',
-                             sync='dirty-bitmap', format=iotests.imgfmt,
-                             target=self.foo_img)
+        self.files.append(self.err_img)
+        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
+                             sync='dirty-bitmap', format=self.drives[0]['fmt'],
+                             target=self.err_img)
         self.assert_qmp(result, 'error/class', 'GenericError')
 
 
     def test_sync_dirty_bitmap_not_found(self):
         self.assert_no_active_block_jobs()
-        self.files.append(self.foo_img)
-        result = self.vm.qmp('drive-backup', device='drive0',
+        self.files.append(self.err_img)
+        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
                              sync='dirty-bitmap', bitmap='unknown',
-                             format=iotests.imgfmt, target=self.foo_img)
+                             format=self.drives[0]['fmt'], target=self.err_img)
         self.assert_qmp(result, 'error/class', 'GenericError')
 
 
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (8 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 20:50   ` Max Reitz
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test John Snow
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

Allow tests to call just the backup preparation routine
without invoking a backup. Useful for transactions where
we want to prepare, but don't wish to issue the QMP command.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 tests/qemu-iotests/124 | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 2eccc3e..4afdca1 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -100,7 +100,6 @@ class TestIncrementalBackup(iotests.QMPTestCase):
 
     def img_create(self, img, fmt=iotests.imgfmt, size='64M',
                    parent=None, parentFormat=None):
-        plist = list()
         if parent:
             if parentFormat is None:
                 parentFormat = fmt
@@ -140,17 +139,25 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         return bitmap
 
 
-    def create_incremental(self, bitmap=None, num=None,
-                           parent=None, parentFormat=None, validate=True):
+    def prepare_backup(self, bitmap=None, parent=None):
         if bitmap is None:
             bitmap = self.bitmaps[-1]
-
         if parent is None:
             parent = bitmap.last_target()
 
-        target = bitmap.new_target(num)
+        target = bitmap.new_target()
         self.img_create(target, bitmap.drive['fmt'], parent=parent)
+        return target
 
+
+    def create_incremental(self, bitmap=None, parent=None,
+                           parentFormat=None, validate=True):
+        if bitmap is None:
+            bitmap = self.bitmaps[-1]
+        if parent is None:
+            parent = bitmap.last_target()
+
+        target = self.prepare_backup(bitmap, parent)
         result = self.vm.qmp('drive-backup', device=bitmap.drive['id'],
                              sync='dirty-bitmap', bitmap=bitmap.name,
                              format=bitmap.drive['fmt'], target=target,
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test
  2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
                   ` (9 preceding siblings ...)
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring John Snow
@ 2015-03-05  4:15 ` John Snow
  2015-03-17 20:59   ` Max Reitz
  10 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-05  4:15 UTC (permalink / raw)
  To: qemu-block
  Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha, mreitz

Use a transaction to request an incremental backup across two drives.
Coerce one of the jobs to fail, and then re-run the transaction.

Verify that no bitmap data was lost due to the partial transaction
failure.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 tests/qemu-iotests/124     | 119 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/124.out |   4 +-
 2 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 4afdca1..48571a5 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -331,6 +331,125 @@ class TestIncrementalBackup(iotests.QMPTestCase):
         self.create_incremental()
 
 
+    def test_transaction_failure(self):
+        '''Test: Verify backups made from a transaction that partially fails.
+
+        Add a second drive with its own unique pattern, and add a bitmap to each
+        drive. Use blkdebug to interfere with the backup on just one drive and
+        attempt to create a coherent incremental backup across both drives.
+
+        verify a failure in one but not both, then delete the failed stubs and
+        re-run the same transaction.
+
+        verify that both incrementals are created successfully.
+        '''
+
+        # Create a second drive, with pattern:
+        drive1 = self.add_node('drive1')
+        self.img_create(drive1['file'], drive1['fmt'])
+        io_write_patterns(drive1['file'], (('0x14', 0, 512),
+                                           ('0x5d', '1M', '32k'),
+                                           ('0xcd', '32M', '124k')))
+
+        # Create a blkdebug interface to this img as 'drive1'
+        result = self.vm.qmp('blockdev-add', options={
+            'id': drive1['id'],
+            'driver': drive1['fmt'],
+            'file': {
+                'driver': 'blkdebug',
+                'image': {
+                    'driver': 'file',
+                    'filename': drive1['file']
+                },
+                'set-state': [{
+                    'event': 'flush_to_disk',
+                    'state': 1,
+                    'new_state': 2
+                }],
+                'inject-error': [{
+                    'event': 'read_aio',
+                    'errno': 5,
+                    'state': 2,
+                    'immediately': False,
+                    'once': True
+                }],
+            }
+        })
+        self.assert_qmp(result, 'return', {})
+
+        # Create bitmaps and full backups for both drives
+        drive0 = self.drives[0]
+        dr0bm0 = self.add_bitmap('bitmap0', drive0)
+        dr1bm0 = self.add_bitmap('bitmap0', drive1)
+        self.create_full_backup(drive0)
+        self.create_full_backup(drive1)
+        self.assert_no_active_block_jobs()
+        self.assertFalse(self.vm.get_qmp_events(wait=False))
+
+        # Emulate some writes
+        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
+                                          ('0xfe', '16M', '256k'),
+                                          ('0x64', '32736k', '64k')))
+        self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
+                                          ('0xef', '16M', '256k'),
+                                          ('0x46', '32736k', '64k')))
+
+        # Create incremental backup targets
+        target0 = self.prepare_backup(dr0bm0)
+        target1 = self.prepare_backup(dr1bm0)
+
+        # Ask for a new incremental backup per-each drive,
+        # expecting drive1's backup to fail:
+        transaction = [
+            {
+                'type': 'drive-backup',
+                'data': { 'device': drive0['id'],
+                          'sync': 'dirty-bitmap',
+                          'format': drive0['fmt'],
+                          'target': target0,
+                          'mode': 'existing',
+                          'bitmap': dr0bm0.name },
+            },
+            {
+                'type': 'drive-backup',
+                'data': { 'device': drive1['id'],
+                          'sync': 'dirty-bitmap',
+                          'format': drive1['fmt'],
+                          'target': target1,
+                          'mode': 'existing',
+                          'bitmap': dr1bm0.name }
+            }
+        ]
+        result = self.vm.qmp('transaction', actions=transaction)
+        self.assert_qmp(result, 'return', {})
+
+        # Observe that drive0's backup completes, but drive1's does not.
+        # Consume drive1's error and ensure all pending actions are completed.
+        self.wait_incremental(dr0bm0, validate=True)
+        self.wait_incremental(dr1bm0, validate=False)
+        error = self.vm.event_wait('BLOCK_JOB_ERROR')
+        self.assert_qmp(error, 'data', {'device': drive1['id'],
+                                        'action': 'report',
+                                        'operation': 'read'})
+        self.assertFalse(self.vm.get_qmp_events(wait=False))
+        self.assert_no_active_block_jobs()
+
+        # Delete drive0's (successful) backup and create two new empty
+        # targets to re-run the transaction.
+        dr0bm0.del_target()
+        target0 = self.prepare_backup(dr0bm0)
+        target1 = self.prepare_backup(dr1bm0)
+
+        # Re-run the exact same transaction.
+        result = self.vm.qmp('transaction', actions=transaction)
+        self.assert_qmp(result, 'return', {})
+        # Both should complete successfully this time.
+        self.wait_incremental(dr0bm0, 'drive0')
+        self.wait_incremental(dr1bm0, 'drive1')
+        self.assertFalse(self.vm.get_qmp_events(wait=False))
+        self.assert_no_active_block_jobs()
+
+
     def test_sync_dirty_bitmap_missing(self):
         self.assert_no_active_block_jobs()
         self.files.append(self.err_img)
diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
index 914e373..3f8a935 100644
--- a/tests/qemu-iotests/124.out
+++ b/tests/qemu-iotests/124.out
@@ -1,5 +1,5 @@
-.....
+......
 ----------------------------------------------------------------------
-Ran 5 tests
+Ran 6 tests
 
 OK
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test John Snow
@ 2015-03-11 12:11   ` Kashyap Chamarthy
  2015-03-11 14:25     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Kashyap Chamarthy @ 2015-03-11 12:11 UTC (permalink / raw)
  To: John Snow
  Cc: kwolf, famz, qemu-block, qemu-devel, armbru, vsementsov,
	stefanha, mreitz

On Wed, Mar 04, 2015 at 11:15:02PM -0500, John Snow wrote:
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>  tests/qemu-iotests/124     | 49 ++++++++++++++++++++++++++++++++++++++++++++++
>  tests/qemu-iotests/124.out |  4 ++--
>  2 files changed, 51 insertions(+), 2 deletions(-)

This tests seems to fail locally:

  . . .
  +======================================================================
  +FAIL: test_incremental_failure (__main__.TestIncrementalBackup)
  +Test: Verify backups made after a failure are correct.
  +----------------------------------------------------------------------
  +Traceback (most recent call last):
  +  File "124", line 331, in test_incremental_failure
  +    self.create_incremental()
  +  File "124", line 167, in create_incremental
  +    return self.wait_incremental(bitmap, validate)
  +  File "124", line 179, in wait_incremental
  +    self.assert_qmp_absent(event, 'data/error')
  +  File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, in assert_qmp_absent
  +    self.fail('path "%s" has value "%s"' % (path, str(result)))
  +AssertionError: path "data/error" has value "Input/output error"
  +
  +======================================================================
  +FAIL: test_transaction_failure (__main__.TestIncrementalBackup)
  +Test: Verify backups made from a transaction that partially fails.
  +----------------------------------------------------------------------
  +Traceback (most recent call last):
  +  File "124", line 448, in test_transaction_failure
  +    self.wait_incremental(dr1bm0, 'drive1')
  +  File "124", line 179, in wait_incremental
  +    self.assert_qmp_absent(event, 'data/error')
  +  File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, in assert_qmp_absent
  +    self.fail('path "%s" has value "%s"' % (path, str(result)))
  +AssertionError: path "data/error" has value "Input/output error"
  +
   ----------------------------------------------------------------------
  . . .

Complete stderr of `./check -qcow2` here:

    https://kashyapc.fedorapeople.org/virt/qemu-incremental-backup-tests/stderr-qemu-io-tests-qcow2-11MAR2015.txt

And, as per the other two failures of tests 051 and 061 (that you
mentioned on #qemu yesterday), Kevin Wolf on IRC said:

    051 was fixed, but the output has changed _again_. That change is from
    armbru_'s commit 7ee6c1e18. We need to update the reference output.

    As for 061, Max sent a patch, but I think we need to fix qemu rather
    than updating the reference output there
    Because the error message has become considerably worse


To test, I applied these two series to yesterday's QEMU git master:

    [PATCH 00/11] block: incremental backup transactions
    [PATCH v2 00/17] block: transactionless incremental backup

So, I'm here (28 commits ahead of commit 3539bbb on master) after
applying the patch series:

    $ git describe
    v2.2.0-1190-g41b7f5f

I need to try w/ today's git though, yet.

-- 
/kashyap

> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
> index 1c07387..da0bf6d 100644
> --- a/tests/qemu-iotests/124
> +++ b/tests/qemu-iotests/124
> @@ -184,6 +184,55 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>          return True
>  
>  
> +    def test_incremental_transaction(self):
> +        '''Test: Verify backups made from transactionally created bitmaps.
> +
> +        Create a bitmap "before" VM execution begins, then create a second
> +        bitmap AFTER writes have already occurred. Use transactions to create
> +        a full backup and synchronize both bitmaps to this backup.
> +        Create an incremental backup through both bitmaps and verify that
> +        both backups match the full backup.
> +        '''
> +        bitmap0 = self.add_bitmap('bitmap0', 'drive0')
> +        self.hmp_io_writes('drive0', (('0xab', 0, 512),
> +                                      ('0xfe', '16M', '256k'),
> +                                      ('0x64', '32736k', '64k')))
> +        bitmap1 = self.add_bitmap('bitmap1', 'drive0')
> +
> +        result = self.vm.qmp('transaction', actions=[
> +            {
> +                'type': 'block-dirty-bitmap-clear',
> +                'data': { 'node': 'drive0',
> +                          'name': 'bitmap0' },
> +            },
> +            {
> +                'type': 'block-dirty-bitmap-clear',
> +                'data': { 'node': 'drive0',
> +                          'name': 'bitmap1' },
> +            },
> +            {
> +                'type': 'drive-backup',
> +                'data': { 'device': 'drive0',
> +                          'sync': 'full',
> +                          'format': iotests.imgfmt,
> +                          'target': self.full_bak },
> +            }
> +        ])
> +        self.assert_qmp(result, 'return', {})
> +        self.wait_until_completed()
> +        self.files.append(self.full_bak)
> +        self.check_full_backup()
> +
> +        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
> +                                      ('0x55', '8M', '352k'),
> +                                      ('0x78', '15872k', '1M')))
> +        # Both bitmaps should be in sync and create fully valid
> +        # incremental backups
> +        res1 = self.create_incremental(bitmap0)
> +        res2 = self.create_incremental(bitmap1)
> +        self.assertTrue(res1 and res2)
> +
> +
>      def test_incremental_failure(self):
>          '''Test: Verify backups made after a failure are correct.
>  
> diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
> index 89968f3..914e373 100644
> --- a/tests/qemu-iotests/124.out
> +++ b/tests/qemu-iotests/124.out
> @@ -1,5 +1,5 @@
> -....
> +.....
>  ----------------------------------------------------------------------
> -Ran 4 tests
> +Ran 5 tests
>  
>  OK
> -- 
> 1.9.3

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
  2015-03-11 12:11   ` Kashyap Chamarthy
@ 2015-03-11 14:25     ` John Snow
  2015-03-11 16:18       ` Kashyap Chamarthy
  0 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-11 14:25 UTC (permalink / raw)
  To: Kashyap Chamarthy
  Cc: kwolf, famz, qemu-block, qemu-devel, armbru, vsementsov,
	stefanha, mreitz



On 03/11/2015 08:11 AM, Kashyap Chamarthy wrote:
> On Wed, Mar 04, 2015 at 11:15:02PM -0500, John Snow wrote:
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   tests/qemu-iotests/124     | 49 ++++++++++++++++++++++++++++++++++++++++++++++
>>   tests/qemu-iotests/124.out |  4 ++--
>>   2 files changed, 51 insertions(+), 2 deletions(-)
>
> This tests seems to fail locally:
>
>    . . .
>    +======================================================================
>    +FAIL: test_incremental_failure (__main__.TestIncrementalBackup)
>    +Test: Verify backups made after a failure are correct.
>    +----------------------------------------------------------------------
>    +Traceback (most recent call last):
>    +  File "124", line 331, in test_incremental_failure
>    +    self.create_incremental()
>    +  File "124", line 167, in create_incremental
>    +    return self.wait_incremental(bitmap, validate)
>    +  File "124", line 179, in wait_incremental
>    +    self.assert_qmp_absent(event, 'data/error')
>    +  File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, in assert_qmp_absent
>    +    self.fail('path "%s" has value "%s"' % (path, str(result)))
>    +AssertionError: path "data/error" has value "Input/output error"
>    +
>    +======================================================================
>    +FAIL: test_transaction_failure (__main__.TestIncrementalBackup)
>    +Test: Verify backups made from a transaction that partially fails.
>    +----------------------------------------------------------------------
>    +Traceback (most recent call last):
>    +  File "124", line 448, in test_transaction_failure
>    +    self.wait_incremental(dr1bm0, 'drive1')
>    +  File "124", line 179, in wait_incremental
>    +    self.assert_qmp_absent(event, 'data/error')
>    +  File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, in assert_qmp_absent
>    +    self.fail('path "%s" has value "%s"' % (path, str(result)))
>    +AssertionError: path "data/error" has value "Input/output error"
>    +
>     ----------------------------------------------------------------------
>    . . .
>
> Complete stderr of `./check -qcow2` here:
>
>      https://kashyapc.fedorapeople.org/virt/qemu-incremental-backup-tests/stderr-qemu-io-tests-qcow2-11MAR2015.txt
>
> And, as per the other two failures of tests 051 and 061 (that you
> mentioned on #qemu yesterday), Kevin Wolf on IRC said:
>
>      051 was fixed, but the output has changed _again_. That change is from
>      armbru_'s commit 7ee6c1e18. We need to update the reference output.
>
>      As for 061, Max sent a patch, but I think we need to fix qemu rather
>      than updating the reference output there
>      Because the error message has become considerably worse
>
>
> To test, I applied these two series to yesterday's QEMU git master:
>
>      [PATCH 00/11] block: incremental backup transactions
>      [PATCH v2 00/17] block: transactionless incremental backup
>
> So, I'm here (28 commits ahead of commit 3539bbb on master) after
> applying the patch series:
>
>      $ git describe
>      v2.2.0-1190-g41b7f5f
>
> I need to try w/ today's git though, yet.
>

OK, I'll check this again after I make my changes to the base series 
this is based on. I promise I wasn't seeing a failure then ;)

If I can't reproduce I will be in touch to help diagnose what's going wrong.

Thanks,
--js

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
  2015-03-11 14:25     ` John Snow
@ 2015-03-11 16:18       ` Kashyap Chamarthy
  0 siblings, 0 replies; 43+ messages in thread
From: Kashyap Chamarthy @ 2015-03-11 16:18 UTC (permalink / raw)
  To: John Snow
  Cc: kwolf, famz, qemu-block, qemu-devel, armbru, vsementsov,
	stefanha, mreitz

On Wed, Mar 11, 2015 at 10:25:13AM -0400, John Snow wrote:
> 
> 
> On 03/11/2015 08:11 AM, Kashyap Chamarthy wrote:

[. . .]

> >To test, I applied these two series to yesterday's QEMU git master:
> >
> >     [PATCH 00/11] block: incremental backup transactions
> >     [PATCH v2 00/17] block: transactionless incremental backup
> >
> >So, I'm here (28 commits ahead of commit 3539bbb on master) after
> >applying the patch series:
> >
> >     $ git describe
> >     v2.2.0-1190-g41b7f5f
> >
> >I need to try w/ today's git though, yet.
> >
> 
> OK, I'll check this again after I make my changes to the base series this is
> based on. I promise I wasn't seeing a failure then ;)
> 
> If I can't reproduce I will be in touch to help diagnose what's going wrong.

Following up from our IRC conversation (thanks!), testing from your 'git
remote' (from github) works. Below is what I did, for completeness'
sake:

     $ git remote add jsnow https://github.com/jnsnow/qemu.git
     $ git fetch jsnow transactionless-incremental
     $ git checkout -b 3-inc-backup-testing jsnow/transactionless-incremental
     $ git pull jsnow incremental-transactions
     $ git describe
     v2.2.0-1553-ged69392

Test:

     $ make clean
     $ ./configure --target-list=x86_64-softmmu --enable-debug
     $ make -j4
     $ cd tests/qemu-iotests
     $ ./check -v -qcow2 124
     QEMU          -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../x86_64-softmmu/qemu-system-x86_64
     QEMU_IMG      -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../qemu-img
     QEMU_IO       -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../qemu-io
     QEMU_NBD      -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../qemu-nbd
     IMGFMT        -- qcow2 (compat=1.1)
     IMGPROTO      -- file
     PLATFORM      -- Linux/x86_64 tesla 3.18.3-201.fc21.x86_64
     TEST_DIR      -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/scratch
     SOCKET_SCM_HELPER -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/socket_scm_helper
 
     124 2s ...
     Passed all 1 tests


-- 
/kashyap

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations John Snow
@ 2015-03-17 15:14   ` Max Reitz
  0 siblings, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-17 15:14 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> This adds two qmp commands to transactions.
>
> block-dirty-bitmap-add allows you to create a bitmap simultaneously
> alongside a new full backup to accomplish a clean synchronization
> point.
>
> block-dirty-bitmap-clear allows you to reset a bitmap back to as-if
> it were new, which can also be used alongside a full backup to
> accomplish a clean synchronization point.
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   blockdev.c       | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   qapi-schema.json |   6 +++-
>   2 files changed, 106 insertions(+), 1 deletion(-)
>
> diff --git a/blockdev.c b/blockdev.c
> index e0671be..5120af1 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1664,6 +1664,96 @@ static void blockdev_backup_clean(BlkTransactionState *common)
>       }
>   }
>   
> +typedef struct BlockDirtyBitmapState {
> +    BlkTransactionState common;
> +    BdrvDirtyBitmap *bitmap;
> +    BlockDriverState *bs;
> +    AioContext *aio_context;
> +    bool prepared;
> +} BlockDirtyBitmapState;
> +
> +static void block_dirty_bitmap_add_prepare(BlkTransactionState *common,
> +                                           Error **errp)
> +{
> +    Error *local_err = NULL;
> +    BlockDirtyBitmapAdd *action;
> +    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> +                                             common, common);
> +
> +    action = common->action->block_dirty_bitmap_add;
> +    /* AIO context taken within qmp_block_dirty_bitmap_add */
> +    qmp_block_dirty_bitmap_add(action->node, action->name,
> +                               action->has_granularity, action->granularity,
> +                               &local_err);
> +
> +    if (!local_err) {
> +        state->prepared = true;
> +    } else {
> +        error_propagate(errp, local_err);
> +    }
> +}
> +
> +static void block_dirty_bitmap_add_abort(BlkTransactionState *common)
> +{
> +    BlockDirtyBitmapAdd *action;
> +   BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,

Indentation is still off.

> +                                             common, common);
> +
> +    action = common->action->block_dirty_bitmap_add;
> +    /* Should not be able to fail: IF the bitmap was added via .prepare(),
> +     * then the node reference and bitmap name must have been valid.
> +     */
> +    if (state->prepared) {
> +        qmp_block_dirty_bitmap_remove(action->node, action->name, &error_abort);
> +    }
> +}
> +
> +static void block_dirty_bitmap_clear_prepare(BlkTransactionState *common,
> +                                             Error **errp)
> +{
> +    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> +                                             common, common);
> +    BlockDirtyBitmap *action;
> +
> +    action = common->action->block_dirty_bitmap_clear;
> +    state->bitmap = block_dirty_bitmap_lookup(action->node,
> +                                              action->name,
> +                                              &state->bs,
> +                                              errp);

This will need fix-up for v3 (and following, probably) of your 
transactionless series (&state->aio_context as the fourth parameter).

> +    if (!state->bitmap) {
> +        return;
> +    }
> +
> +    if (bdrv_dirty_bitmap_frozen(state->bitmap)) {
> +        error_setg(errp, "Cannot modify a frozen bitmap");
> +        return;
> +    } else if (!bdrv_dirty_bitmap_enabled(state->bitmap)) {
> +        error_setg(errp, "Cannot clear a disabled bitmap");
> +        return;
> +    }
> +
> +    /* AioContext is released in .clean() */
> +    state->aio_context = bdrv_get_aio_context(state->bs);
> +    aio_context_acquire(state->aio_context);

And this will probably need to be dropped for v4 of said series.

Other than that, looks good.

Max

> +}
> +
> +static void block_dirty_bitmap_clear_commit(BlkTransactionState *common)
> +{
> +    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> +                                             common, common);
> +    bdrv_clear_dirty_bitmap(state->bitmap);
> +}
> +
> +static void block_dirty_bitmap_clear_clean(BlkTransactionState *common)
> +{
> +    BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> +                                             common, common);
> +
> +    if (state->aio_context) {
> +        aio_context_release(state->aio_context);
> +    }
> +}
> +
>   static void abort_prepare(BlkTransactionState *common, Error **errp)
>   {
>       error_setg(errp, "Transaction aborted using Abort action");
> @@ -1704,6 +1794,17 @@ static const BdrvActionOps actions[] = {
>           .abort = internal_snapshot_abort,
>           .clean = internal_snapshot_clean,
>       },
> +    [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = {
> +        .instance_size = sizeof(BlockDirtyBitmapState),
> +        .prepare = block_dirty_bitmap_add_prepare,
> +        .abort = block_dirty_bitmap_add_abort,
> +    },
> +    [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = {
> +        .instance_size = sizeof(BlockDirtyBitmapState),
> +        .prepare = block_dirty_bitmap_clear_prepare,
> +        .commit = block_dirty_bitmap_clear_commit,
> +        .clean = block_dirty_bitmap_clear_clean,
> +    }
>   };
>   
>   /*
> diff --git a/qapi-schema.json b/qapi-schema.json
> index e16f8eb..12c61f3 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -1332,6 +1332,8 @@
>   # abort since 1.6
>   # blockdev-snapshot-internal-sync since 1.7
>   # blockdev-backup since 2.3
> +# block-dirty-bitmap-add since 2.3
> +# block-dirty-bitmap-clear since 2.3
>   ##
>   { 'union': 'TransactionAction',
>     'data': {
> @@ -1339,7 +1341,9 @@
>          'drive-backup': 'DriveBackup',
>          'blockdev-backup': 'BlockdevBackup',
>          'abort': 'Abort',
> -       'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal'
> +       'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
> +       'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
> +       'block-dirty-bitmap-clear': 'BlockDirtyBitmap'
>      } }
>   
>   ##

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature John Snow
@ 2015-03-17 17:47   ` Max Reitz
  2015-03-17 18:04     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-17 17:47 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, 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
> 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_transaction_wrapper, which
> creates a wrapper around a closure and callback that would originally have
> been passed to e.g. backup_start().
>
> The function will return a function pointer and a new closure to be passed
> instead. The transaction system will effectively intercept these callbacks
> and execute the desired actions upon reception of all intercepted callbacks.
>
> This means that the registered callbacks will be called after all other
> transaction actions that requested a callback have completed. The feature
> has no knowledge of jobs spawned without informing the BlkTransactionList.
>
> 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 in drive_backup (or any block job launched by transactions).
>
>
> Note 1: Defining a callback method alone is not sufficient to have the new
>          method invoked. You must call new_transaction_wrapper AND ensure the
>          callback it returns to you is used as the callback for the job.
>
> Note 2: You can use this feature for any system that registers completions of
>          an action via a callback of the form (void *opaque, int ret), not just
>          block job callbacks.
>
> Note 3: new_blk_transaction has no users in this patch, but will in
>          the next patch where it will become static and local to blockdev.c.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   blockdev.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>   1 file changed, 223 insertions(+), 2 deletions(-)
>
> diff --git a/blockdev.c b/blockdev.c
> index 5120af1..3153ee7 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
>   /* New and old BlockDriverState structs for atomic group operations */
>   
>   typedef struct BlkTransactionState BlkTransactionState;
> +typedef struct BlkTransactionList BlkTransactionList;
> +typedef struct BlkTransactionData BlkTransactionData;
>   
>   /* Only prepare() may fail. In a single transaction, only one of commit() or
>      abort() will be called, clean() will always be called if it present. */
> @@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {
>       void (*abort)(BlkTransactionState *common);
>       /* Clean up resource in the end, can be NULL. */
>       void (*clean)(BlkTransactionState *common);
> +    /* Execute this after +all+ jobs in the transaction finish */
> +    void (*cb)(BlkTransactionState *common);
>   } BdrvActionOps;
>   
>   /*
> @@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {
>   struct BlkTransactionState {
>       TransactionAction *action;
>       const BdrvActionOps *ops;
> +    BlkTransactionList *list;
> +    void *opaque;
> +    /* Allow external users (callbacks) to reference this obj past .clean() */
> +    int refcount;
> +    /* All transactions in the current group */
>       QSIMPLEQ_ENTRY(BlkTransactionState) entry;
> +    /* Transactions in the current group with callbacks */
> +    QSIMPLEQ_ENTRY(BlkTransactionState) list_entry;

"list_entry" seems very generic and it's hard for me to see a 
fundamental difference to just "entry". How about "cb_entry", or 
"cb_list_entry", or "cb_group_entry" or something like that?

>   };
>   
> +struct BlkTransactionList {
> +    int jobs;     /* Effectively: A refcount */

Good to know, but I'd like the reason why it's called like this to be 
here anyway ("Number of jobs remaining" or I don't know).

> +    int status;   /* Cumulative retcode */

The only places I currently find "retcode" in are linux-user/signal.c 
and tests/image-fuzzer/runner.py. How about the more common "return 
value" (or simply "cumulative status")?

("retcode" reads a bit like "retcon" to me, that's why I had to think 
about it for a second)

> +    QSIMPLEQ_HEAD(actions, BlkTransactionState) actions;
> +};
> +
> +typedef struct BlkTransactionData {
> +    void *opaque; /* Data given to encapsulated callback */
> +    int ret;      /* Return code given to encapsulated callback */
> +} BlkTransactionData;
> +
> +typedef struct BlkTransactionWrapper {
> +    void *opaque; /* Data to be given to encapsulated callback */
> +    void (*callback)(void *opaque, int ret); /* Encapsulated callback */
> +} BlkTransactionWrapper;

I find it pretty difficult to figure out what these objects are each 
for. Care to add comments for that, too?

> +
> +static BlkTransactionList *new_blk_transaction_list(void)
> +{
> +    BlkTransactionList *btl = g_malloc0(sizeof(*btl));

Maybe use g_new0(BlkTransactionList, 1); (It's typesafe! And who doesn't 
love typesafety?).

(Optional, though, the foo = malloc(sizeof(*foo)) pattern is pretty 
wide-spread, and as far as I remember, Markus purposely did not replace 
it by foo = g_new(Foo, 1))

> +
> +    /* Implicit 'job' for qmp_transaction itself */
> +    btl->jobs = 1;

Well, if it is a refcount, just call it that way...

(Update: Okay, I see why you didn't call it that way. Maybe the comment 
needs improvement, like "The transaction itself is a job, too, that 
needs to be completed before the callbacks are called")

> +    QSIMPLEQ_INIT(&btl->actions);
> +    return btl;
> +}
> +
> +static BlkTransactionState *blk_put_transaction_state(BlkTransactionState *bts)
> +{
> +    bts->refcount--;
> +    if (bts->refcount == 0) {
> +        g_free(bts);

How about removing it from the lists it's in? Doesn't appear to be 
necessary, but I'd find it cleaner.

> +        return NULL;
> +    }
> +    return bts;
> +}
> +
> +static void del_blk_transaction_list(BlkTransactionList *btl)
> +{
> +    BlkTransactionState *bts, *bts_next;
> +
> +    /* The list should in normal cases be empty,
> +     * but in case someone really just wants to kibosh the whole deal: */

Thank you for teaching me a new word.

> +    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
> +        g_free(bts->opaque);

Urp... Are you sure? :-/

I'd rather have some callback for destroying the object... Apparently 
this will (for now) be always useless overhead, because that callback is 
only calling g_free(), but it's an opaque pointer, so it's not really up 
to you to do anything with it other than passing it around.

> +        blk_put_transaction_state(bts);
> +    }
> +
> +    g_free(btl);
> +}
> +
> +static void blk_run_transaction_callbacks(BlkTransactionList *btl)
> +{
> +    BlkTransactionState *bts, *bts_next;
> +
> +    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
> +        if (bts->ops->cb) {
> +            bts->ops->cb(bts);
> +        }
> +
> +        /* Free the BlkTransactionData */
> +        g_free(bts->opaque);

Again... Urp. If you know it's a BlkTransactionData object, please make 
it a BlkTransactionData pointer and don't call it "opaque" if it's not 
so opaque in the end.

> +        bts->opaque = NULL;
> +        blk_put_transaction_state(bts);
> +    }
> +
> +    QSIMPLEQ_INIT(&btl->actions);
> +}
> +
> +static BlkTransactionList *put_blk_transaction_list(BlkTransactionList *btl)
> +{
> +    btl->jobs--;
> +    if (btl->jobs == 0) {

Okay, this is interesting, because on the one hand it makes it clear why 
you did not call it "refcount", while on the other hand this is clearly 
a pattern which looks very much like handling a refcount.

Maybe you could call the function differently, like 
"blk_transaction_list_job_completed()" or something. It's longer, but I 
think it reduces the change of people staring at this thinking "Why is 
'jobs' actually a refcount?".

> +        blk_run_transaction_callbacks(btl);
> +        del_blk_transaction_list(btl);
> +        return NULL;
> +    }
> +    return btl;
> +}
> +
> +static void blk_transaction_complete(BlkTransactionState *common)
> +{
> +    BlkTransactionList *btl = common->list;
> +
> +    /* Add this action into the pile to be completed */
> +    QSIMPLEQ_INSERT_TAIL(&btl->actions, common, list_entry);
> +
> +    /* Inform the list that we have a completion;
> +     * possibly run all the pending actions. */
> +    put_blk_transaction_list(btl);
> +}
> +
> +/**
> + * Intercept a callback that was issued due to a transactional action.
> + */
> +static void transaction_callback(void *opaque, int ret)
> +{
> +    BlkTransactionState *common = opaque;
> +    BlkTransactionWrapper *btw = common->opaque;
> +
> +    /* Prepare data for ops->cb() */
> +    BlkTransactionData *btd = g_malloc0(sizeof(*btd));

g_new0(BlkTransactionData, 1);

> +    btd->opaque = btw->opaque;
> +    btd->ret = ret;
> +
> +    /* Transaction state now tracks OUR data */
> +    common->opaque = btd;

Sorry, but I really have a hard time following this opaqueness... Again, 
if you can, please clarify what the objects are for, and it would be 
very nice to separate truly opaque objects from these internally used 
objects (which are managed by you and thus are to be managed by you 
(g_free()), because reusing void * pointers for different kinds of 
objects like this makes my brain go strawberry.

> +
> +    /* Keep track of the amalgamated return code */
> +    common->list->status |= ret;

Hm, I guess you're expecting ret to be -errno. In that case, you'd 
probably rather want "if (ret && (!common->list->status || 
common->list->status == -ENOSPC)) { common->list->status = ret; }" or 
something like that, because bit-OR-ing different -errno values will 
probably not turn out so great.

> +
> +    /* Deliver the intercepted callback FIRST */
> +    btw->callback(btw->opaque, ret);
> +    blk_transaction_complete(common);
> +    g_free(btw);
> +}
> +
> +typedef void (CallbackFn)(void *opaque, int ret);
> +
> +/* Temporary. Removed in the next patch. */

Actually, no. :-)

(remove in patch 7)

Why are you making them non-static in the first place? I see both 
functions mentioned in patch 3 and patch 7 only (except for context in 
patch 6).

> +CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
> +                                    void *opaque,
> +                                    void (*callback)(void *, int),
> +                                    void **new_opaque);
> +
> +void undo_transaction_wrapper(BlkTransactionState *common);
> +
> +/**
> + * 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 transactional state to set a callback for.
> + * @opaque A closure intended for the encapsulated callback.
> + * @callback The callback we are encapsulating.
> + * @new_opaque The closure to be used instead of @opaque.
> + *
> + * @return The callback to be used instead of @callback.
> + */
> +CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
> +                                           void *opaque,
> +                                           CallbackFn *callback,
> +                                           void **new_opaque)
> +{
> +    BlkTransactionWrapper *btw = g_malloc0(sizeof(*btw));

g_new0(BlkTransactionWrapper, 1);

> +    assert(new_opaque);
> +
> +    /* Stash the original callback information */
> +    btw->opaque = opaque;
> +    btw->callback = callback;
> +    common->opaque = btw;
> +
> +    /* The BTS will serve as our new closure */
> +    *new_opaque = common;
> +    common->refcount++;
> +
> +    /* Inform the transaction BTL to expect one more return */
> +    common->list->jobs++;
> +
> +    /* Lastly, the actual callback function to handle the interception. */
> +    return transaction_callback;
> +}
> +
> +/**
> + * Undo any actions performed by the above call.
> + */
> +void undo_transaction_wrapper(BlkTransactionState *common)
> +{
> +    BlkTransactionList *btl = common->list;
> +    BlkTransactionState *bts;
> +    BlkTransactionData *btd;
> +
> +    /* Stage 0: Wrapper was never created: */
> +    if (common->opaque == NULL && common->refcount == 1) {
> +        return;
> +    }
> +
> +    /* 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.
> +     */
> +    QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) {
> +        if (bts == common) {
> +            btd = common->opaque;
> +            /* Force error for callback */
> +            btd->ret = -1;

No -errno?

> +            common->ops->cb(common);
> +            QSIMPLEQ_REMOVE(&btl->actions, common,
> +                            BlkTransactionState, list_entry);
> +            goto cleanup;
> +        }
> +    }
> +    /* Stage 1: Callback created, but job never launched */
> +    put_blk_transaction_list(common->list);
> + cleanup:
> +    g_free(common->opaque);

urp

> +    blk_put_transaction_state(common);
> +}
> +
>   /* internal snapshot private data */
>   typedef struct InternalSnapshotState {
>       BlkTransactionState common;
> @@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = {
>           .instance_size = sizeof(DriveBackupState),
>           .prepare = drive_backup_prepare,
>           .abort = drive_backup_abort,
> -        .clean = drive_backup_clean,
> +        .clean = drive_backup_clean

Probably not intended, I guess.

>       },
>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>           .instance_size = sizeof(BlockdevBackupState),
> @@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)
>   {
>       TransactionActionList *dev_entry = dev_list;
>       BlkTransactionState *state, *next;
> +    BlkTransactionList *btl;
>       Error *local_err = NULL;
>   
>       QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states;
>       QSIMPLEQ_INIT(&snap_bdrv_states);
> +    btl = new_blk_transaction_list();
>   
>       /* drain all i/o before any operations */
>       bdrv_drain_all();
> @@ -1837,8 +2054,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->list = btl;
>           QSIMPLEQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
>   
>           state->ops->prepare(state, &local_err);
> @@ -1869,8 +2088,10 @@ exit:
>           if (state->ops->clean) {
>               state->ops->clean(state);
>           }
> -        g_free(state);
> +        blk_put_transaction_state(state);
>       }
> +
> +    put_blk_transaction_list(btl);
>   }
>   
>   

Sorry, as I said, I need more context on the objects and very much would 
like a separation between truly opaque pointers and not-so-opaque 
pointers, before I can really understand what's going on (or I put more 
effort into it, but since it's always more probable to belong to the 
majority, I guess I won't be the only one getting confused).

Maybe the next patches will clear it up, I don't know.

Max

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 04/11] block: add refcount to Job object
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 04/11] block: add refcount to Job object John Snow
@ 2015-03-17 17:54   ` Max Reitz
  0 siblings, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-17 17:54 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> If we want to get at the job after the life of the job,
> we'll need a refcount for this object.
>
> This may occur for example if we wish to inspect the actions
> taken by a particular job after a transactional group of jobs
> runs, and further actions are required.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   blockjob.c               | 18 ++++++++++++++++--
>   include/block/blockjob.h | 21 +++++++++++++++++++++
>   2 files changed, 37 insertions(+), 2 deletions(-)
>
> diff --git a/blockjob.c b/blockjob.c
> index ba2255d..d620082 100644
> --- a/blockjob.c
> +++ b/blockjob.c
> @@ -35,6 +35,19 @@
>   #include "qemu/timer.h"
>   #include "qapi-event.h"
>   
> +void block_job_incref(BlockJob *job)
> +{
> +    job->refcount++;
> +}
> +
> +void block_job_decref(BlockJob *job)
> +{
> +    job->refcount--;
> +    if (job->refcount == 0) {
> +        g_free(job);
> +    }
> +}
> +
>   void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
>                          int64_t speed, BlockCompletionFunc *cb,
>                          void *opaque, Error **errp)
> @@ -57,6 +70,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
>       job->cb            = cb;
>       job->opaque        = opaque;
>       job->busy          = true;
> +    job->refcount      = 1;
>       bs->job = job;
>   
>       /* Only set speed when necessary to avoid NotSupported error */
> @@ -68,7 +82,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
>               bs->job = NULL;
>               bdrv_op_unblock_all(bs, job->blocker);
>               error_free(job->blocker);
> -            g_free(job);
> +            block_job_decref(job);
>               error_propagate(errp, local_err);
>               return NULL;
>           }
> @@ -85,7 +99,7 @@ void block_job_completed(BlockJob *job, int ret)
>       bs->job = NULL;
>       bdrv_op_unblock_all(bs, job->blocker);
>       error_free(job->blocker);
> -    g_free(job);
> +    block_job_decref(job);
>   }
>   
>   void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
> diff --git a/include/block/blockjob.h b/include/block/blockjob.h
> index b6d4ebb..8fe1c95 100644
> --- a/include/block/blockjob.h
> +++ b/include/block/blockjob.h
> @@ -116,6 +116,9 @@ struct BlockJob {
>   
>       /** The opaque value that is passed to the completion function.  */
>       void *opaque;
> +
> +    /** A reference count, allowing for post-job actions in e.g. transactions */
> +    int refcount;
>   };
>   
>   /**
> @@ -141,6 +144,24 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,
>                          void *opaque, Error **errp);
>   
>   /**
> + * block_job_incref:
> + * @job: The job to pick up a handle to
> + *
> + * Increment the refcount on @job, to be able to use it asynchronously
> + * from the job it is being used for. Put down the reference when done
> + * with @block_job_unref

Missing a full stop?

With that added:

Reviewed-by: Max Reitz <mreitz@redhat.com>

> + */
> +void block_job_incref(BlockJob *job);
> +
> +/**
> + * block_job_decref:
> + * @job: The job to unreference and delete.
> + *
> + * Decrement the refcount on @job, and delete it if there are no users.

(and also with that changed to "if it becomes/reaches zero", if you'd 
like, because somehow the "users" sounds unrelated to a party holding a 
refcount, when in fact it's supposed to be exactly the same thing; up to 
you)

> + */
> +void block_job_decref(BlockJob *job);
> +
> +/**
>    * block_job_sleep_ns:
>    * @job: The job that calls the function.
>    * @clock: The clock to sleep on.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
  2015-03-17 17:47   ` Max Reitz
@ 2015-03-17 18:04     ` John Snow
  2015-03-17 18:18       ` Eric Blake
  2015-03-17 18:19       ` Max Reitz
  0 siblings, 2 replies; 43+ messages in thread
From: John Snow @ 2015-03-17 18:04 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 01:47 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, 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
>> 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_transaction_wrapper, which
>> creates a wrapper around a closure and callback that would originally
>> have
>> been passed to e.g. backup_start().
>>
>> The function will return a function pointer and a new closure to be
>> passed
>> instead. The transaction system will effectively intercept these
>> callbacks
>> and execute the desired actions upon reception of all intercepted
>> callbacks.
>>
>> This means that the registered callbacks will be called after all other
>> transaction actions that requested a callback have completed. The feature
>> has no knowledge of jobs spawned without informing the
>> BlkTransactionList.
>>
>> 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 in drive_backup (or any block job launched by
>> transactions).
>>
>>
>> Note 1: Defining a callback method alone is not sufficient to have the
>> new
>>          method invoked. You must call new_transaction_wrapper AND
>> ensure the
>>          callback it returns to you is used as the callback for the job.
>>
>> Note 2: You can use this feature for any system that registers
>> completions of
>>          an action via a callback of the form (void *opaque, int ret),
>> not just
>>          block job callbacks.
>>
>> Note 3: new_blk_transaction has no users in this patch, but will in
>>          the next patch where it will become static and local to
>> blockdev.c.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   blockdev.c | 225
>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>>   1 file changed, 223 insertions(+), 2 deletions(-)
>>
>> diff --git a/blockdev.c b/blockdev.c
>> index 5120af1..3153ee7 100644
>> --- a/blockdev.c
>> +++ b/blockdev.c
>> @@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap
>> *block_dirty_bitmap_lookup(const char *node,
>>   /* New and old BlockDriverState structs for atomic group operations */
>>   typedef struct BlkTransactionState BlkTransactionState;
>> +typedef struct BlkTransactionList BlkTransactionList;
>> +typedef struct BlkTransactionData BlkTransactionData;
>>   /* Only prepare() may fail. In a single transaction, only one of
>> commit() or
>>      abort() will be called, clean() will always be called if it
>> present. */
>> @@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {
>>       void (*abort)(BlkTransactionState *common);
>>       /* Clean up resource in the end, can be NULL. */
>>       void (*clean)(BlkTransactionState *common);
>> +    /* Execute this after +all+ jobs in the transaction finish */
>> +    void (*cb)(BlkTransactionState *common);
>>   } BdrvActionOps;
>>   /*
>> @@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {
>>   struct BlkTransactionState {
>>       TransactionAction *action;
>>       const BdrvActionOps *ops;
>> +    BlkTransactionList *list;
>> +    void *opaque;
>> +    /* Allow external users (callbacks) to reference this obj past
>> .clean() */
>> +    int refcount;
>> +    /* All transactions in the current group */
>>       QSIMPLEQ_ENTRY(BlkTransactionState) entry;
>> +    /* Transactions in the current group with callbacks */
>> +    QSIMPLEQ_ENTRY(BlkTransactionState) list_entry;
>
> "list_entry" seems very generic and it's hard for me to see a
> fundamental difference to just "entry". How about "cb_entry", or
> "cb_list_entry", or "cb_group_entry" or something like that?
>
>>   };
>> +struct BlkTransactionList {
>> +    int jobs;     /* Effectively: A refcount */
>
> Good to know, but I'd like the reason why it's called like this to be
> here anyway ("Number of jobs remaining" or I don't know).
>

Yes, it's basically a refcount where the references are held by jobs.

>> +    int status;   /* Cumulative retcode */
>
> The only places I currently find "retcode" in are linux-user/signal.c
> and tests/image-fuzzer/runner.py. How about the more common "return
> value" (or simply "cumulative status")?
>
> ("retcode" reads a bit like "retcon" to me, that's why I had to think
> about it for a second)
>

Sure.

>> +    QSIMPLEQ_HEAD(actions, BlkTransactionState) actions;
>> +};
>> +
>> +typedef struct BlkTransactionData {
>> +    void *opaque; /* Data given to encapsulated callback */
>> +    int ret;      /* Return code given to encapsulated callback */
>> +} BlkTransactionData;
>> +
>> +typedef struct BlkTransactionWrapper {
>> +    void *opaque; /* Data to be given to encapsulated callback */
>> +    void (*callback)(void *opaque, int ret); /* Encapsulated callback */
>> +} BlkTransactionWrapper;
>
> I find it pretty difficult to figure out what these objects are each
> for. Care to add comments for that, too?
>

Will do.

>> +
>> +static BlkTransactionList *new_blk_transaction_list(void)
>> +{
>> +    BlkTransactionList *btl = g_malloc0(sizeof(*btl));
>
> Maybe use g_new0(BlkTransactionList, 1); (It's typesafe! And who doesn't
> love typesafety?).
>
> (Optional, though, the foo = malloc(sizeof(*foo)) pattern is pretty
> wide-spread, and as far as I remember, Markus purposely did not replace
> it by foo = g_new(Foo, 1))
>
>> +
>> +    /* Implicit 'job' for qmp_transaction itself */
>> +    btl->jobs = 1;
>
> Well, if it is a refcount, just call it that way...
>
> (Update: Okay, I see why you didn't call it that way. Maybe the comment
> needs improvement, like "The transaction itself is a job, too, that
> needs to be completed before the callbacks are called")
>

That's exactly the trick in play, here.

>> +    QSIMPLEQ_INIT(&btl->actions);
>> +    return btl;
>> +}
>> +
>> +static BlkTransactionState
>> *blk_put_transaction_state(BlkTransactionState *bts)
>> +{
>> +    bts->refcount--;
>> +    if (bts->refcount == 0) {
>> +        g_free(bts);
>
> How about removing it from the lists it's in? Doesn't appear to be
> necessary, but I'd find it cleaner.
>
>> +        return NULL;
>> +    }
>> +    return bts;
>> +}
>> +
>> +static void del_blk_transaction_list(BlkTransactionList *btl)
>> +{
>> +    BlkTransactionState *bts, *bts_next;
>> +
>> +    /* The list should in normal cases be empty,
>> +     * but in case someone really just wants to kibosh the whole
>> deal: */
>
> Thank you for teaching me a new word.
>
>> +    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
>> +        g_free(bts->opaque);
>
> Urp... Are you sure? :-/
>
> I'd rather have some callback for destroying the object... Apparently
> this will (for now) be always useless overhead, because that callback is
> only calling g_free(), but it's an opaque pointer, so it's not really up
> to you to do anything with it other than passing it around.
>
>> +        blk_put_transaction_state(bts);
>> +    }
>> +
>> +    g_free(btl);
>> +}
>> +
>> +static void blk_run_transaction_callbacks(BlkTransactionList *btl)
>> +{
>> +    BlkTransactionState *bts, *bts_next;
>> +
>> +    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
>> +        if (bts->ops->cb) {
>> +            bts->ops->cb(bts);
>> +        }
>> +
>> +        /* Free the BlkTransactionData */
>> +        g_free(bts->opaque);
>
> Again... Urp. If you know it's a BlkTransactionData object, please make
> it a BlkTransactionData pointer and don't call it "opaque" if it's not
> so opaque in the end.
>

A good point. I use this "opaque" pointer to actually hold two very 
specific and knowable things; it's just that those things change during 
the lifetime of the object.

For whatever reason, I decided it was nicer to not have two pointers 
that are never used simultaneously.

I can either make two dedicated fields, or just introduce it as an 
union. The state of the object otherwise dictates which union field to 
access.

Because I am a complicated individual, I am thinking fondly of the union 
right now.

>> +        bts->opaque = NULL;
>> +        blk_put_transaction_state(bts);
>> +    }
>> +
>> +    QSIMPLEQ_INIT(&btl->actions);
>> +}
>> +
>> +static BlkTransactionList
>> *put_blk_transaction_list(BlkTransactionList *btl)
>> +{
>> +    btl->jobs--;
>> +    if (btl->jobs == 0) {
>
> Okay, this is interesting, because on the one hand it makes it clear why
> you did not call it "refcount", while on the other hand this is clearly
> a pattern which looks very much like handling a refcount.
>
> Maybe you could call the function differently, like
> "blk_transaction_list_job_completed()" or something. It's longer, but I
> think it reduces the change of people staring at this thinking "Why is
> 'jobs' actually a refcount?".
>

OK, I'm up for suggestions on naming. I didn't really (deep in my heart) 
expect v1 to be perfect.

I think your main problem here is "jobs" as an integral refcount. Maybe 
"num_jobs" or "jobs_remaining" etc would make it clearer.

And then, sure, "blk_transaction_list_job_completed" would be a fine 
alternative to "put."

>> +        blk_run_transaction_callbacks(btl);
>> +        del_blk_transaction_list(btl);
>> +        return NULL;
>> +    }
>> +    return btl;
>> +}
>> +
>> +static void blk_transaction_complete(BlkTransactionState *common)
>> +{
>> +    BlkTransactionList *btl = common->list;
>> +
>> +    /* Add this action into the pile to be completed */
>> +    QSIMPLEQ_INSERT_TAIL(&btl->actions, common, list_entry);
>> +
>> +    /* Inform the list that we have a completion;
>> +     * possibly run all the pending actions. */
>> +    put_blk_transaction_list(btl);
>> +}
>> +
>> +/**
>> + * Intercept a callback that was issued due to a transactional action.
>> + */
>> +static void transaction_callback(void *opaque, int ret)
>> +{
>> +    BlkTransactionState *common = opaque;
>> +    BlkTransactionWrapper *btw = common->opaque;
>> +
>> +    /* Prepare data for ops->cb() */
>> +    BlkTransactionData *btd = g_malloc0(sizeof(*btd));
>
> g_new0(BlkTransactionData, 1);
>
>> +    btd->opaque = btw->opaque;
>> +    btd->ret = ret;
>> +
>> +    /* Transaction state now tracks OUR data */
>> +    common->opaque = btd;
>
> Sorry, but I really have a hard time following this opaqueness... Again,
> if you can, please clarify what the objects are for, and it would be
> very nice to separate truly opaque objects from these internally used
> objects (which are managed by you and thus are to be managed by you
> (g_free()), because reusing void * pointers for different kinds of
> objects like this makes my brain go strawberry.
>

I'll shoot for Raspberry in v2.

>> +
>> +    /* Keep track of the amalgamated return code */
>> +    common->list->status |= ret;
>
> Hm, I guess you're expecting ret to be -errno. In that case, you'd
> probably rather want "if (ret && (!common->list->status ||
> common->list->status == -ENOSPC)) { common->list->status = ret; }" or
> something like that, because bit-OR-ing different -errno values will
> probably not turn out so great.
>

I am only super interested in keeping a zero-or-nonzero cumulative 
status -- the actual error code winds up not being so important. The 
mechanisms here allow the delivery of both the "cumulative" and the 
individual return code to the transactional callback, so error messages 
etc. can fill in the blanks if they need to.

>> +
>> +    /* Deliver the intercepted callback FIRST */
>> +    btw->callback(btw->opaque, ret);
>> +    blk_transaction_complete(common);
>> +    g_free(btw);
>> +}
>> +
>> +typedef void (CallbackFn)(void *opaque, int ret);
>> +
>> +/* Temporary. Removed in the next patch. */
>
> Actually, no. :-)
>
> (remove in patch 7)
>
> Why are you making them non-static in the first place? I see both
> functions mentioned in patch 3 and patch 7 only (except for context in
> patch 6).
>

Won't compile otherwise, because they have no users as of this patch. I 
am trying to keep the size of these patches down and in a sane order.

Once a user is added, we can re-add the static attribute.

>> +CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>> +                                    void *opaque,
>> +                                    void (*callback)(void *, int),
>> +                                    void **new_opaque);
>> +
>> +void undo_transaction_wrapper(BlkTransactionState *common);
>> +
>> +/**
>> + * 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 transactional state to set a callback for.
>> + * @opaque A closure intended for the encapsulated callback.
>> + * @callback The callback we are encapsulating.
>> + * @new_opaque The closure to be used instead of @opaque.
>> + *
>> + * @return The callback to be used instead of @callback.
>> + */
>> +CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>> +                                           void *opaque,
>> +                                           CallbackFn *callback,
>> +                                           void **new_opaque)
>> +{
>> +    BlkTransactionWrapper *btw = g_malloc0(sizeof(*btw));
>
> g_new0(BlkTransactionWrapper, 1);
>
>> +    assert(new_opaque);
>> +
>> +    /* Stash the original callback information */
>> +    btw->opaque = opaque;
>> +    btw->callback = callback;
>> +    common->opaque = btw;
>> +
>> +    /* The BTS will serve as our new closure */
>> +    *new_opaque = common;
>> +    common->refcount++;
>> +
>> +    /* Inform the transaction BTL to expect one more return */
>> +    common->list->jobs++;
>> +
>> +    /* Lastly, the actual callback function to handle the
>> interception. */
>> +    return transaction_callback;
>> +}
>> +
>> +/**
>> + * Undo any actions performed by the above call.
>> + */
>> +void undo_transaction_wrapper(BlkTransactionState *common)
>> +{
>> +    BlkTransactionList *btl = common->list;
>> +    BlkTransactionState *bts;
>> +    BlkTransactionData *btd;
>> +
>> +    /* Stage 0: Wrapper was never created: */
>> +    if (common->opaque == NULL && common->refcount == 1) {
>> +        return;
>> +    }
>> +
>> +    /* 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.
>> +     */
>> +    QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) {
>> +        if (bts == common) {
>> +            btd = common->opaque;
>> +            /* Force error for callback */
>> +            btd->ret = -1;
>
> No -errno?
>

Hmm. I suppose I should emulate one. What errno should I emulate for 
"One of your siblings died, and now you also need to take a dirtnap?"

>> +            common->ops->cb(common);
>> +            QSIMPLEQ_REMOVE(&btl->actions, common,
>> +                            BlkTransactionState, list_entry);
>> +            goto cleanup;
>> +        }
>> +    }
>> +    /* Stage 1: Callback created, but job never launched */
>> +    put_blk_transaction_list(common->list);
>> + cleanup:
>> +    g_free(common->opaque);
>
> urp
>

ok, ok! I'm sorry! ... kind of ...

>> +    blk_put_transaction_state(common);
>> +}
>> +
>>   /* internal snapshot private data */
>>   typedef struct InternalSnapshotState {
>>       BlkTransactionState common;
>> @@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = {
>>           .instance_size = sizeof(DriveBackupState),
>>           .prepare = drive_backup_prepare,
>>           .abort = drive_backup_abort,
>> -        .clean = drive_backup_clean,
>> +        .clean = drive_backup_clean
>
> Probably not intended, I guess.
>

Did an earlier patch of mine goof this up? The way this patch leaves it 
looks correct.

>>       },
>>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>>           .instance_size = sizeof(BlockdevBackupState),
>> @@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList
>> *dev_list, Error **errp)
>>   {
>>       TransactionActionList *dev_entry = dev_list;
>>       BlkTransactionState *state, *next;
>> +    BlkTransactionList *btl;
>>       Error *local_err = NULL;
>>       QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState)
>> snap_bdrv_states;
>>       QSIMPLEQ_INIT(&snap_bdrv_states);
>> +    btl = new_blk_transaction_list();
>>       /* drain all i/o before any operations */
>>       bdrv_drain_all();
>> @@ -1837,8 +2054,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->list = btl;
>>           QSIMPLEQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
>>           state->ops->prepare(state, &local_err);
>> @@ -1869,8 +2088,10 @@ exit:
>>           if (state->ops->clean) {
>>               state->ops->clean(state);
>>           }
>> -        g_free(state);
>> +        blk_put_transaction_state(state);
>>       }
>> +
>> +    put_blk_transaction_list(btl);
>>   }
>
> Sorry, as I said, I need more context on the objects and very much would
> like a separation between truly opaque pointers and not-so-opaque
> pointers, before I can really understand what's going on (or I put more
> effort into it, but since it's always more probable to belong to the
> majority, I guess I won't be the only one getting confused).
>
> Maybe the next patches will clear it up, I don't know.
>
> Max

I'll put some more elbow grease into explaining the flow.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
  2015-03-17 18:04     ` John Snow
@ 2015-03-17 18:18       ` Eric Blake
  2015-03-17 18:23         ` John Snow
  2015-03-17 18:19       ` Max Reitz
  1 sibling, 1 reply; 43+ messages in thread
From: Eric Blake @ 2015-03-17 18:18 UTC (permalink / raw)
  To: John Snow, Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

[-- Attachment #1: Type: text/plain, Size: 957 bytes --]

On 03/17/2015 12:04 PM, John Snow wrote:

>>> +typedef void (CallbackFn)(void *opaque, int ret);
>>> +
>>> +/* Temporary. Removed in the next patch. */
>>
>> Actually, no. :-)
>>
>> (remove in patch 7)
>>
>> Why are you making them non-static in the first place? I see both
>> functions mentioned in patch 3 and patch 7 only (except for context in
>> patch 6).
>>
> 
> Won't compile otherwise, because they have no users as of this patch. I
> am trying to keep the size of these patches down and in a sane order.

Another solution is to mark it static, but also add
__attribute__((unused)) to shut the compiler up, so that you don't cause
build failures, but also don't cause reviewers to wonder who is using
it.  Then when it finally gets used, a patch to remove the attribute is
easier to see than a patch adding static.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
  2015-03-17 18:04     ` John Snow
  2015-03-17 18:18       ` Eric Blake
@ 2015-03-17 18:19       ` Max Reitz
  1 sibling, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-17 18:19 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-17 at 14:04, John Snow wrote:
>
>
> On 03/17/2015 01:47 PM, Max Reitz wrote:
>> On 2015-03-04 at 23:15, 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
>>> 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_transaction_wrapper, 
>>> which
>>> creates a wrapper around a closure and callback that would originally
>>> have
>>> been passed to e.g. backup_start().
>>>
>>> The function will return a function pointer and a new closure to be
>>> passed
>>> instead. The transaction system will effectively intercept these
>>> callbacks
>>> and execute the desired actions upon reception of all intercepted
>>> callbacks.
>>>
>>> This means that the registered callbacks will be called after all other
>>> transaction actions that requested a callback have completed. The 
>>> feature
>>> has no knowledge of jobs spawned without informing the
>>> BlkTransactionList.
>>>
>>> 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 in drive_backup (or any block job launched by
>>> transactions).
>>>
>>>
>>> Note 1: Defining a callback method alone is not sufficient to have the
>>> new
>>>          method invoked. You must call new_transaction_wrapper AND
>>> ensure the
>>>          callback it returns to you is used as the callback for the 
>>> job.
>>>
>>> Note 2: You can use this feature for any system that registers
>>> completions of
>>>          an action via a callback of the form (void *opaque, int ret),
>>> not just
>>>          block job callbacks.
>>>
>>> Note 3: new_blk_transaction has no users in this patch, but will in
>>>          the next patch where it will become static and local to
>>> blockdev.c.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>>   blockdev.c | 225
>>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>>>   1 file changed, 223 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/blockdev.c b/blockdev.c
>>> index 5120af1..3153ee7 100644
>>> --- a/blockdev.c
>>> +++ b/blockdev.c
>>> @@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap
>>> *block_dirty_bitmap_lookup(const char *node,
>>>   /* New and old BlockDriverState structs for atomic group 
>>> operations */
>>>   typedef struct BlkTransactionState BlkTransactionState;
>>> +typedef struct BlkTransactionList BlkTransactionList;
>>> +typedef struct BlkTransactionData BlkTransactionData;
>>>   /* Only prepare() may fail. In a single transaction, only one of
>>> commit() or
>>>      abort() will be called, clean() will always be called if it
>>> present. */
>>> @@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {
>>>       void (*abort)(BlkTransactionState *common);
>>>       /* Clean up resource in the end, can be NULL. */
>>>       void (*clean)(BlkTransactionState *common);
>>> +    /* Execute this after +all+ jobs in the transaction finish */
>>> +    void (*cb)(BlkTransactionState *common);
>>>   } BdrvActionOps;
>>>   /*
>>> @@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {
>>>   struct BlkTransactionState {
>>>       TransactionAction *action;
>>>       const BdrvActionOps *ops;
>>> +    BlkTransactionList *list;
>>> +    void *opaque;
>>> +    /* Allow external users (callbacks) to reference this obj past
>>> .clean() */
>>> +    int refcount;
>>> +    /* All transactions in the current group */
>>>       QSIMPLEQ_ENTRY(BlkTransactionState) entry;
>>> +    /* Transactions in the current group with callbacks */
>>> +    QSIMPLEQ_ENTRY(BlkTransactionState) list_entry;
>>
>> "list_entry" seems very generic and it's hard for me to see a
>> fundamental difference to just "entry". How about "cb_entry", or
>> "cb_list_entry", or "cb_group_entry" or something like that?
>>
>>>   };
>>> +struct BlkTransactionList {
>>> +    int jobs;     /* Effectively: A refcount */
>>
>> Good to know, but I'd like the reason why it's called like this to be
>> here anyway ("Number of jobs remaining" or I don't know).
>>
>
> Yes, it's basically a refcount where the references are held by jobs.
>
>>> +    int status;   /* Cumulative retcode */
>>
>> The only places I currently find "retcode" in are linux-user/signal.c
>> and tests/image-fuzzer/runner.py. How about the more common "return
>> value" (or simply "cumulative status")?
>>
>> ("retcode" reads a bit like "retcon" to me, that's why I had to think
>> about it for a second)
>>
>
> Sure.
>
>>> +    QSIMPLEQ_HEAD(actions, BlkTransactionState) actions;
>>> +};
>>> +
>>> +typedef struct BlkTransactionData {
>>> +    void *opaque; /* Data given to encapsulated callback */
>>> +    int ret;      /* Return code given to encapsulated callback */
>>> +} BlkTransactionData;
>>> +
>>> +typedef struct BlkTransactionWrapper {
>>> +    void *opaque; /* Data to be given to encapsulated callback */
>>> +    void (*callback)(void *opaque, int ret); /* Encapsulated 
>>> callback */
>>> +} BlkTransactionWrapper;
>>
>> I find it pretty difficult to figure out what these objects are each
>> for. Care to add comments for that, too?
>>
>
> Will do.
>
>>> +
>>> +static BlkTransactionList *new_blk_transaction_list(void)
>>> +{
>>> +    BlkTransactionList *btl = g_malloc0(sizeof(*btl));
>>
>> Maybe use g_new0(BlkTransactionList, 1); (It's typesafe! And who doesn't
>> love typesafety?).
>>
>> (Optional, though, the foo = malloc(sizeof(*foo)) pattern is pretty
>> wide-spread, and as far as I remember, Markus purposely did not replace
>> it by foo = g_new(Foo, 1))
>>
>>> +
>>> +    /* Implicit 'job' for qmp_transaction itself */
>>> +    btl->jobs = 1;
>>
>> Well, if it is a refcount, just call it that way...
>>
>> (Update: Okay, I see why you didn't call it that way. Maybe the comment
>> needs improvement, like "The transaction itself is a job, too, that
>> needs to be completed before the callbacks are called")
>>
>
> That's exactly the trick in play, here.
>
>>> + QSIMPLEQ_INIT(&btl->actions);
>>> +    return btl;
>>> +}
>>> +
>>> +static BlkTransactionState
>>> *blk_put_transaction_state(BlkTransactionState *bts)
>>> +{
>>> +    bts->refcount--;
>>> +    if (bts->refcount == 0) {
>>> +        g_free(bts);
>>
>> How about removing it from the lists it's in? Doesn't appear to be
>> necessary, but I'd find it cleaner.
>>
>>> +        return NULL;
>>> +    }
>>> +    return bts;
>>> +}
>>> +
>>> +static void del_blk_transaction_list(BlkTransactionList *btl)
>>> +{
>>> +    BlkTransactionState *bts, *bts_next;
>>> +
>>> +    /* The list should in normal cases be empty,
>>> +     * but in case someone really just wants to kibosh the whole
>>> deal: */
>>
>> Thank you for teaching me a new word.
>>
>>> +    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
>>> +        g_free(bts->opaque);
>>
>> Urp... Are you sure? :-/
>>
>> I'd rather have some callback for destroying the object... Apparently
>> this will (for now) be always useless overhead, because that callback is
>> only calling g_free(), but it's an opaque pointer, so it's not really up
>> to you to do anything with it other than passing it around.
>>
>>> +        blk_put_transaction_state(bts);
>>> +    }
>>> +
>>> +    g_free(btl);
>>> +}
>>> +
>>> +static void blk_run_transaction_callbacks(BlkTransactionList *btl)
>>> +{
>>> +    BlkTransactionState *bts, *bts_next;
>>> +
>>> +    QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) {
>>> +        if (bts->ops->cb) {
>>> +            bts->ops->cb(bts);
>>> +        }
>>> +
>>> +        /* Free the BlkTransactionData */
>>> +        g_free(bts->opaque);
>>
>> Again... Urp. If you know it's a BlkTransactionData object, please make
>> it a BlkTransactionData pointer and don't call it "opaque" if it's not
>> so opaque in the end.
>>
>
> A good point. I use this "opaque" pointer to actually hold two very 
> specific and knowable things; it's just that those things change 
> during the lifetime of the object.
>
> For whatever reason, I decided it was nicer to not have two pointers 
> that are never used simultaneously.
>
> I can either make two dedicated fields, or just introduce it as an 
> union. The state of the object otherwise dictates which union field to 
> access.
>
> Because I am a complicated individual, I am thinking fondly of the 
> union right now.

Good. I like unions. Other people don't. But I do. Especially anonymous 
unions, but I guess we don't have the luxury of C11 in qemu...

>
>>> +        bts->opaque = NULL;
>>> +        blk_put_transaction_state(bts);
>>> +    }
>>> +
>>> +    QSIMPLEQ_INIT(&btl->actions);
>>> +}
>>> +
>>> +static BlkTransactionList
>>> *put_blk_transaction_list(BlkTransactionList *btl)
>>> +{
>>> +    btl->jobs--;
>>> +    if (btl->jobs == 0) {
>>
>> Okay, this is interesting, because on the one hand it makes it clear why
>> you did not call it "refcount", while on the other hand this is clearly
>> a pattern which looks very much like handling a refcount.
>>
>> Maybe you could call the function differently, like
>> "blk_transaction_list_job_completed()" or something. It's longer, but I
>> think it reduces the change of people staring at this thinking "Why is
>> 'jobs' actually a refcount?".
>>
>
> OK, I'm up for suggestions on naming. I didn't really (deep in my 
> heart) expect v1 to be perfect.

I know that feeling... And it's completely fine. That's what the 
reviewing process is for.

> I think your main problem here is "jobs" as an integral refcount. 
> Maybe "num_jobs" or "jobs_remaining" etc would make it clearer.

My main problem is that the function is named exactly like a function 
that decreases a refcount and the content of the function is exactly 
like a function that decreases a refcount, but the refcount isn't called 
'refcount', but 'jobs'.

Therefore, if you're trying to tie the refcount to a non-abstract 
concept (the number of jobs left to complete) by representing it as 
such, I'd like the function to represent that concept to (a job has been 
completed).

> And then, sure, "blk_transaction_list_job_completed" would be a fine 
> alternative to "put."
>
>>> + blk_run_transaction_callbacks(btl);
>>> +        del_blk_transaction_list(btl);
>>> +        return NULL;
>>> +    }
>>> +    return btl;
>>> +}
>>> +
>>> +static void blk_transaction_complete(BlkTransactionState *common)
>>> +{
>>> +    BlkTransactionList *btl = common->list;
>>> +
>>> +    /* Add this action into the pile to be completed */
>>> +    QSIMPLEQ_INSERT_TAIL(&btl->actions, common, list_entry);
>>> +
>>> +    /* Inform the list that we have a completion;
>>> +     * possibly run all the pending actions. */
>>> +    put_blk_transaction_list(btl);
>>> +}
>>> +
>>> +/**
>>> + * Intercept a callback that was issued due to a transactional action.
>>> + */
>>> +static void transaction_callback(void *opaque, int ret)
>>> +{
>>> +    BlkTransactionState *common = opaque;
>>> +    BlkTransactionWrapper *btw = common->opaque;
>>> +
>>> +    /* Prepare data for ops->cb() */
>>> +    BlkTransactionData *btd = g_malloc0(sizeof(*btd));
>>
>> g_new0(BlkTransactionData, 1);
>>
>>> +    btd->opaque = btw->opaque;
>>> +    btd->ret = ret;
>>> +
>>> +    /* Transaction state now tracks OUR data */
>>> +    common->opaque = btd;
>>
>> Sorry, but I really have a hard time following this opaqueness... Again,
>> if you can, please clarify what the objects are for, and it would be
>> very nice to separate truly opaque objects from these internally used
>> objects (which are managed by you and thus are to be managed by you
>> (g_free()), because reusing void * pointers for different kinds of
>> objects like this makes my brain go strawberry.
>>
>
> I'll shoot for Raspberry in v2.

Please don't try to add whipped cream on top.

>>> +
>>> +    /* Keep track of the amalgamated return code */
>>> +    common->list->status |= ret;
>>
>> Hm, I guess you're expecting ret to be -errno. In that case, you'd
>> probably rather want "if (ret && (!common->list->status ||
>> common->list->status == -ENOSPC)) { common->list->status = ret; }" or
>> something like that, because bit-OR-ing different -errno values will
>> probably not turn out so great.
>>
>
> I am only super interested in keeping a zero-or-nonzero cumulative 
> status -- the actual error code winds up not being so important.

Maybe, but most places in qemu try to follow the -errno convention. I'd 
only deviate from it if there is a compelling reason to. If you want to 
keep it short, just make it "if (ret) { common->list->status = ret; }".

As long as you don't intend to use bitmasks for the return values, this 
will give you at least the same amount of information: Some error 
occurred. Also, it'll even tell you the kind of at least on error that 
occurred (which may even be meaningful, if it's ENOSPC or something like 
that), so I don't think it's worse (other than requiring three lines 
instead of one).

> The mechanisms here allow the delivery of both the "cumulative" and 
> the individual return code to the transactional callback, so error 
> messages etc. can fill in the blanks if they need to.
>
>>> +
>>> +    /* Deliver the intercepted callback FIRST */
>>> +    btw->callback(btw->opaque, ret);
>>> +    blk_transaction_complete(common);
>>> +    g_free(btw);
>>> +}
>>> +
>>> +typedef void (CallbackFn)(void *opaque, int ret);
>>> +
>>> +/* Temporary. Removed in the next patch. */
>>
>> Actually, no. :-)
>>
>> (remove in patch 7)
>>
>> Why are you making them non-static in the first place? I see both
>> functions mentioned in patch 3 and patch 7 only (except for context in
>> patch 6).
>>
>
> Won't compile otherwise, because they have no users as of this patch. 
> I am trying to keep the size of these patches down and in a sane order.

Ah, hm, right...

> Once a user is added, we can re-add the static attribute.

Well, there's __attribute__((used)), but I see, yep.

(__attribute__((used)) would be cleaner because it doesn't litter the 
global namespace; on the other hand, if there are no conflict, in 
practice just not making it static is probably the better choice (I 
don't think any compiler that doesn't understand __attribute__((used)) 
will build qemu at all, but you never know what people try))

>>> +CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>> +                                    void *opaque,
>>> +                                    void (*callback)(void *, int),
>>> +                                    void **new_opaque);
>>> +
>>> +void undo_transaction_wrapper(BlkTransactionState *common);
>>> +
>>> +/**
>>> + * 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 transactional state to set a callback for.
>>> + * @opaque A closure intended for the encapsulated callback.
>>> + * @callback The callback we are encapsulating.
>>> + * @new_opaque The closure to be used instead of @opaque.
>>> + *
>>> + * @return The callback to be used instead of @callback.
>>> + */
>>> +CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>> +                                           void *opaque,
>>> +                                           CallbackFn *callback,
>>> +                                           void **new_opaque)
>>> +{
>>> +    BlkTransactionWrapper *btw = g_malloc0(sizeof(*btw));
>>
>> g_new0(BlkTransactionWrapper, 1);
>>
>>> +    assert(new_opaque);
>>> +
>>> +    /* Stash the original callback information */
>>> +    btw->opaque = opaque;
>>> +    btw->callback = callback;
>>> +    common->opaque = btw;
>>> +
>>> +    /* The BTS will serve as our new closure */
>>> +    *new_opaque = common;
>>> +    common->refcount++;
>>> +
>>> +    /* Inform the transaction BTL to expect one more return */
>>> +    common->list->jobs++;
>>> +
>>> +    /* Lastly, the actual callback function to handle the
>>> interception. */
>>> +    return transaction_callback;
>>> +}
>>> +
>>> +/**
>>> + * Undo any actions performed by the above call.
>>> + */
>>> +void undo_transaction_wrapper(BlkTransactionState *common)
>>> +{
>>> +    BlkTransactionList *btl = common->list;
>>> +    BlkTransactionState *bts;
>>> +    BlkTransactionData *btd;
>>> +
>>> +    /* Stage 0: Wrapper was never created: */
>>> +    if (common->opaque == NULL && common->refcount == 1) {
>>> +        return;
>>> +    }
>>> +
>>> +    /* 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.
>>> +     */
>>> +    QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) {
>>> +        if (bts == common) {
>>> +            btd = common->opaque;
>>> +            /* Force error for callback */
>>> +            btd->ret = -1;
>>
>> No -errno?
>>
>
> Hmm. I suppose I should emulate one. What errno should I emulate for 
> "One of your siblings died, and now you also need to take a dirtnap?"

That's always a hard question, isn't it? :-)

I'd really like an error code like "EEVERYTHINGWENTWRONG" or 
"EGOSHIDONTKNOW". I'm always consulting errno(3) at this point... ... 
Well, there's ECANCELED, maybe that's okay.

>
>>> + common->ops->cb(common);
>>> +            QSIMPLEQ_REMOVE(&btl->actions, common,
>>> +                            BlkTransactionState, list_entry);
>>> +            goto cleanup;
>>> +        }
>>> +    }
>>> +    /* Stage 1: Callback created, but job never launched */
>>> +    put_blk_transaction_list(common->list);
>>> + cleanup:
>>> +    g_free(common->opaque);
>>
>> urp
>>
>
> ok, ok! I'm sorry! ... kind of ...
>
>>> +    blk_put_transaction_state(common);
>>> +}
>>> +
>>>   /* internal snapshot private data */
>>>   typedef struct InternalSnapshotState {
>>>       BlkTransactionState common;
>>> @@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = {
>>>           .instance_size = sizeof(DriveBackupState),
>>>           .prepare = drive_backup_prepare,
>>>           .abort = drive_backup_abort,
>>> -        .clean = drive_backup_clean,
>>> +        .clean = drive_backup_clean
>>
>> Probably not intended, I guess.
>>
>
> Did an earlier patch of mine goof this up? The way this patch leaves 
> it looks correct.

Both are correct. However, generally qemu likes to keep commas at the 
end of initializers because it makes it easier to add new fields later on.

>
>>>       },
>>>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>>>           .instance_size = sizeof(BlockdevBackupState),
>>> @@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList
>>> *dev_list, Error **errp)
>>>   {
>>>       TransactionActionList *dev_entry = dev_list;
>>>       BlkTransactionState *state, *next;
>>> +    BlkTransactionList *btl;
>>>       Error *local_err = NULL;
>>>       QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState)
>>> snap_bdrv_states;
>>>       QSIMPLEQ_INIT(&snap_bdrv_states);
>>> +    btl = new_blk_transaction_list();
>>>       /* drain all i/o before any operations */
>>>       bdrv_drain_all();
>>> @@ -1837,8 +2054,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->list = btl;
>>>           QSIMPLEQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
>>>           state->ops->prepare(state, &local_err);
>>> @@ -1869,8 +2088,10 @@ exit:
>>>           if (state->ops->clean) {
>>>               state->ops->clean(state);
>>>           }
>>> -        g_free(state);
>>> +        blk_put_transaction_state(state);
>>>       }
>>> +
>>> +    put_blk_transaction_list(btl);
>>>   }
>>
>> Sorry, as I said, I need more context on the objects and very much would
>> like a separation between truly opaque pointers and not-so-opaque
>> pointers, before I can really understand what's going on (or I put more
>> effort into it, but since it's always more probable to belong to the
>> majority, I guess I won't be the only one getting confused).
>>
>> Maybe the next patches will clear it up, I don't know.
>>
>> Max
>
> I'll put some more elbow grease into explaining the flow.

Thanks!

Max

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
  2015-03-17 18:18       ` Eric Blake
@ 2015-03-17 18:23         ` John Snow
  0 siblings, 0 replies; 43+ messages in thread
From: John Snow @ 2015-03-17 18:23 UTC (permalink / raw)
  To: Eric Blake, Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 02:18 PM, Eric Blake wrote:
> On 03/17/2015 12:04 PM, John Snow wrote:
>
>>>> +typedef void (CallbackFn)(void *opaque, int ret);
>>>> +
>>>> +/* Temporary. Removed in the next patch. */
>>>
>>> Actually, no. :-)
>>>
>>> (remove in patch 7)
>>>
>>> Why are you making them non-static in the first place? I see both
>>> functions mentioned in patch 3 and patch 7 only (except for context in
>>> patch 6).
>>>
>>
>> Won't compile otherwise, because they have no users as of this patch. I
>> am trying to keep the size of these patches down and in a sane order.
>
> Another solution is to mark it static, but also add
> __attribute__((unused)) to shut the compiler up, so that you don't cause
> build failures, but also don't cause reviewers to wonder who is using
> it.  Then when it finally gets used, a patch to remove the attribute is
> easier to see than a patch adding static.
>

Good trick, thanks.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup John Snow
@ 2015-03-17 18:44   ` Max Reitz
  2015-03-17 19:12     ` John Snow
  2015-03-17 22:46     ` John Snow
  0 siblings, 2 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-17 18:44 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> Allow bitmap successors to carry reference counts.
>
> We can in a later patch use this ability to clean up the dirty bitmap
> according to both the individual job's success and the success of all
> jobs in the transaction group.
>
> The code for cleaning up a bitmap is also moved from backup_run to
> backup_complete.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   block.c               | 79 +++++++++++++++++++++++++++++++++++++++++++++++----
>   block/backup.c        | 23 ++++++---------
>   include/block/block.h | 11 ++++---
>   3 files changed, 87 insertions(+), 26 deletions(-)
>
> diff --git a/block.c b/block.c
> index 5eaa874..a0036af 100644
> --- a/block.c
> +++ b/block.c
> @@ -51,6 +51,12 @@
>   #include <windows.h>
>   #endif
>   
> +typedef enum BitmapSuccessorAction {
> +    SUCCESSOR_ACTION_UNDEFINED = 0,
> +    SUCCESSOR_ACTION_ABDICATE,
> +    SUCCESSOR_ACTION_RECLAIM
> +} BitmapSuccessorAction;

Maybe making this a QAPI enum can come in handy later. I don't know in 
which QMP commands one would use it, but if I could predict the future, 
I'd make trillions (or even billions) at the stock market.

(It's just that it already looks so much alike to a QAPI enum that I 
can't help but to think of making it one)

> +
>   /**
>    * A BdrvDirtyBitmap can be in three possible states:
>    * (1) successor is false and disabled is false: full r/w mode
> @@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {
>       char *name;                 /* Optional non-empty unique ID */
>       int64_t size;               /* Size of the bitmap (Number of sectors) */
>       bool disabled;              /* Bitmap is read-only */
> +    int successor_refcount;     /* Number of active handles to the successor */
> +    BitmapSuccessorAction act;  /* Action to take on successor upon release */
>       QLIST_ENTRY(BdrvDirtyBitmap) list;
>   };
>   
> @@ -5508,6 +5516,7 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>   
>       /* Install the successor and freeze the parent */
>       bitmap->successor = child;
> +    bitmap->successor_refcount = 1;
>       return 0;
>   }
>   
> @@ -5515,9 +5524,9 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>    * For a bitmap with a successor, yield our name to the successor,
>    * Delete the old bitmap, and return a handle to the new bitmap.
>    */
> -BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
> -                                            BdrvDirtyBitmap *bitmap,
> -                                            Error **errp)
> +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
> +                                                   BdrvDirtyBitmap *bitmap,
> +                                                   Error **errp)
>   {
>       char *name;
>       BdrvDirtyBitmap *successor = bitmap->successor;
> @@ -5542,9 +5551,9 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>    * We may wish to re-join the parent and child/successor.
>    * The merged parent will be un-frozen, but not explicitly re-enabled.
>    */
> -BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
> -                                           BdrvDirtyBitmap *parent,
> -                                           Error **errp)
> +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
> +                                                  BdrvDirtyBitmap *parent,
> +                                                  Error **errp)
>   {
>       BdrvDirtyBitmap *successor = parent->successor;
>   
> @@ -5563,6 +5572,64 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>       return parent;
>   }
>   
> +static BdrvDirtyBitmap *bdrv_free_bitmap_successor(BlockDriverState *bs,
> +                                                   BdrvDirtyBitmap *parent,
> +                                                   Error **errp)
> +{
> +    if (parent->successor_refcount) {

I don't know whether you're intending to call this function from 
anywhere outside of bdrv_dirty_bitmap_decref(), but if you don't, please 
just make this an assert(!parent->successor_refcount).

> +        error_setg(errp, "Cannot free the successor for bitmap '%s', "
> +                   "because the refcount is non-zero.", parent->name);

Hm, again with the full stops? *g*

> +        return NULL;
> +    }
> +
> +    switch (parent->act) {
> +    case SUCCESSOR_ACTION_UNDEFINED:
> +        error_setg(errp, "Cannot free the successor for bitmap '%s', "
> +                   "because the successor action has not yet been set.",

Indeed, again with the full stops. ;-)

> +                   parent->name);
> +        return NULL;

So you're (for now) only calling this function from 
bdrv_dirty_bitmap_decref(), and that function always makes sure that 
parent->act is set to SUCCESSOR_ACTION_{RECLAIM,ABDICATE}. Why not add 
an assert(parent->act != SUCCESSOR_ACTION_UNDEFINED) before the switch? 
(If that causes problems in regards to compiler warnings or something 
(not having all enum values covered in the switch), just add an 
assert(0); under "case SUCCESSOR_ACTION_UNDEFINED:".)

> +    case SUCCESSOR_ACTION_RECLAIM:
> +        return bdrv_reclaim_dirty_bitmap(bs, parent, errp);
> +    case SUCCESSOR_ACTION_ABDICATE:
> +        return bdrv_dirty_bitmap_abdicate(bs, parent, errp);
> +    default:
> +        error_setg(errp,
> +                   "Unrecognized successor action (%d), "
> +                   "cannot free successor for bitmap '%s'",
> +                   parent->act, parent->name);
> +        return NULL;

This can be made an assert(0), a g_assert_not_reached(), or simply an 
abort() (with the latter probably being the preferred way).

So I think that all the error_setg() calls are actually cases that 
should never happen. Better make them abort() and drop the Error 
parameter (because *_decref() and *_free() functions normally (for good 
reason) don't have Error parameters...).

> +    }
> +}
> +
> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,

I don't know whether I'm that content with the name chosen, because 
you're actually decrementing the refcount of the successor; but since 
the successor is basically a clone of the original bitmap (and I mean in 
the Star Trek sense, that it's a teleported clone and the original is 
intended to be destroyed so the successor can replace it), decrementing 
the refcount of the successor basically is equal to decrementing the 
refcount of the bitmap itself (as long as there is a successor, which 
you are asserting; maybe you want to add a comment about that to 
include/block/block.h, that one can only use this on frozen bitmaps?).

> +                                          BdrvDirtyBitmap *parent,
> +                                          int ret,
> +                                          Error **errp)
> +{
> +    assert(bdrv_dirty_bitmap_frozen(parent));
> +    assert(parent->successor);
> +
> +    if (ret) {
> +        parent->act = SUCCESSOR_ACTION_RECLAIM;
> +    } else if (parent->act != SUCCESSOR_ACTION_RECLAIM) {
> +        parent->act = SUCCESSOR_ACTION_ABDICATE;
> +    }
> +
> +    parent->successor_refcount--;
> +    if (parent->successor_refcount == 0) {
> +        return bdrv_free_bitmap_successor(bs, parent, errp);

If you drop the Error parameter from bdrv_free_bitmap_successor(), you 
can drop it from this function, too.

> +    }
> +    return parent;
> +}
> +
> +void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent)
> +{
> +    assert(bdrv_dirty_bitmap_frozen(parent));
> +    assert(parent->successor);
> +
> +    parent->successor_refcount++;
> +}
> +
>   static void dirty_bitmap_truncate(BdrvDirtyBitmap *bitmap, uint64_t size)
>   {
>       /* Should only be frozen during a block backup job, which should have
> diff --git a/block/backup.c b/block/backup.c
> index 41bd9af..4332df4 100644
> --- a/block/backup.c
> +++ b/block/backup.c
> @@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)
>   
>       bdrv_unref(s->target);
>   
> +    if (s->sync_bitmap) {
> +        BdrvDirtyBitmap *bm;
> +        bm = bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, data->ret, NULL);
> +        assert(bm);

You can use &error_abort as the Error object and drop the assert(); or, 
if you are dropping the Error parameter, there is no need to check the 
return value at all, because it will always be non-NULL (there won't be 
any code path in the function returning NULL at all). Maybe you can even 
drop the return value, too.

I just looked through the series: Actually, you're never using the Error 
parameter for bdrv_dirty_bitmap_decref() at all. Seems to me like you 
really should drop it (and maybe the return value along with it).

> +    }
> +
>       block_job_completed(job, data->ret);
>       g_free(data);
>   }
> @@ -419,19 +425,6 @@ leave:
>       qemu_co_rwlock_wrlock(&job->flush_rwlock);
>       qemu_co_rwlock_unlock(&job->flush_rwlock);
>   
> -    if (job->sync_bitmap) {
> -        BdrvDirtyBitmap *bm;
> -        if (ret < 0) {
> -            /* Merge the successor back into the parent, delete nothing. */
> -            bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
> -            assert(bm);
> -            bdrv_enable_dirty_bitmap(job->sync_bitmap);

Hm, what is that function call doing here? It feels like it shouldn't 
have been part of your transactionless series (because other than this, 
there is no caller of bdrv_{en,dis}able_dirty_bitmap() at all).

(You're silently removing it here; removing it is fine, but I guess it 
shouldn't have been there in the first place)

> -        } else {
> -            /* Everything is fine, delete this bitmap and install the backup. */
> -            bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
> -            assert(bm);
> -        }
> -    }
>       hbitmap_free(job->bitmap);
>   
>       bdrv_iostatus_disable(target);
> @@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
>   
>    error:
>       if (sync_bitmap) {
> -        bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
> +        BdrvDirtyBitmap *ret;
> +        ret = bdrv_dirty_bitmap_decref(bs, sync_bitmap, -1, NULL);
> +        assert(ret);
>       }
>   }
> diff --git a/include/block/block.h b/include/block/block.h
> index 3a85690..d7859a7 100644
> --- a/include/block/block.h
> +++ b/include/block/block.h
> @@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);
>   int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>                                          BdrvDirtyBitmap *bitmap,
>                                          Error **errp);
> -BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
> -                                            BdrvDirtyBitmap *bitmap,
> -                                            Error **errp);
> -BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
> -                                           BdrvDirtyBitmap *bitmap,
> -                                           Error **errp);
> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
> +                                          BdrvDirtyBitmap *parent,
> +                                          int ret,
> +                                          Error **errp);
> +void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent);
>   BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
>                                           const char *name);
>   void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);

I'm happy with this patch if you drop the Error parameter from 
bdrv_dirty_bitmap_decref() (possibly dropping the return value, too) and 
turn all the error cases into assertions.

Max

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup John Snow
@ 2015-03-17 18:51   ` Max Reitz
  2015-03-17 19:16     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-17 18:51 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> We'd like to be able to specify the callback given to backup_start
> manually in the case of transactions, so split apart qmp_drive_backup
> into an implementation and a wrapper.
>
> Switch drive_backup_prepare to use the new wrapper, but don't overload
> the callback and closure yet.
>
> This is kind of gross, but we need to forward-declare the wrapper for
> the drive_backup transaction to be able to use. I could try moving
> the transaction blocks lower instead, but I wanted (for v1, at least)
> to minimize code movement so that the core changes were easiest to see.
>
> If anyone has suggestions on organization, please suggest them.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   blockdev.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++----------------
>   1 file changed, 58 insertions(+), 19 deletions(-)
>
> diff --git a/blockdev.c b/blockdev.c
> index 3153ee7..9e3b9e9 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1449,6 +1449,19 @@ void undo_transaction_wrapper(BlkTransactionState *common)
>       blk_put_transaction_state(common);
>   }
>   
> +static void _drive_backup(const char *device, const char *target,

Please don't use leading underscores. (Please.)

Just call it do_drive_backup() or something.

Other than that, I'm okay with a forward-declaration; if you want to 
move it, but I'd be fine with moving the code, too.

With _drive_backup() being called do_drive_backup():

Reviewed-by: Max Reitz <mreitz@redhat.com>

> +                          bool has_format, const char *format,
> +                          enum MirrorSyncMode sync,
> +                          bool has_mode, enum NewImageMode mode,
> +                          bool has_speed, int64_t speed,
> +                          bool has_bitmap, const char *bitmap,
> +                          bool has_on_source_error,
> +                          BlockdevOnError on_source_error,
> +                          bool has_on_target_error,
> +                          BlockdevOnError on_target_error,
> +                          BlockCompletionFunc *cb, void *opaque,
> +                          Error **errp);
> +
>   /* internal snapshot private data */
>   typedef struct InternalSnapshotState {
>       BlkTransactionState common;
> @@ -1768,15 +1781,16 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
>       state->aio_context = bdrv_get_aio_context(bs);
>       aio_context_acquire(state->aio_context);
>   
> -    qmp_drive_backup(backup->device, backup->target,
> -                     backup->has_format, backup->format,
> -                     backup->sync,
> -                     backup->has_mode, backup->mode,
> -                     backup->has_speed, backup->speed,
> -                     backup->has_bitmap, backup->bitmap,
> -                     backup->has_on_source_error, backup->on_source_error,
> -                     backup->has_on_target_error, backup->on_target_error,
> -                     &local_err);
> +    _drive_backup(backup->device, backup->target,
> +                  backup->has_format, backup->format,
> +                  backup->sync,
> +                  backup->has_mode, backup->mode,
> +                  backup->has_speed, backup->speed,
> +                  backup->has_bitmap, backup->bitmap,
> +                  backup->has_on_source_error, backup->on_source_error,
> +                  backup->has_on_target_error, backup->on_target_error,
> +                  NULL, NULL,
> +                  &local_err);
>       if (local_err) {
>           error_propagate(errp, local_err);
>           return;
> @@ -2717,15 +2731,18 @@ out:
>       aio_context_release(aio_context);
>   }
>   
> -void qmp_drive_backup(const char *device, const char *target,
> -                      bool has_format, const char *format,
> -                      enum MirrorSyncMode sync,
> -                      bool has_mode, enum NewImageMode mode,
> -                      bool has_speed, int64_t speed,
> -                      bool has_bitmap, const char *bitmap,
> -                      bool has_on_source_error, BlockdevOnError on_source_error,
> -                      bool has_on_target_error, BlockdevOnError on_target_error,
> -                      Error **errp)
> +static void _drive_backup(const char *device, const char *target,
> +                          bool has_format, const char *format,
> +                          enum MirrorSyncMode sync,
> +                          bool has_mode, enum NewImageMode mode,
> +                          bool has_speed, int64_t speed,
> +                          bool has_bitmap, const char *bitmap,
> +                          bool has_on_source_error,
> +                          BlockdevOnError on_source_error,
> +                          bool has_on_target_error,
> +                          BlockdevOnError on_target_error,
> +                          BlockCompletionFunc *cb, void *opaque,
> +                          Error **errp)
>   {
>       BlockDriverState *bs;
>       BlockDriverState *target_bs;
> @@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const char *target,
>           }
>       }
>   
> +    if (cb == NULL) {
> +        cb = block_job_cb;
> +    }
> +    if (opaque == NULL) {
> +        opaque = bs;
> +    }
>       backup_start(bs, target_bs, speed, sync, bmap,
>                    on_source_error, on_target_error,
> -                 block_job_cb, bs, &local_err);
> +                 cb, opaque, &local_err);
>       if (local_err != NULL) {
>           bdrv_unref(target_bs);
>           error_propagate(errp, local_err);
> @@ -2850,6 +2873,22 @@ out:
>       aio_context_release(aio_context);
>   }
>   
> +void qmp_drive_backup(const char *device, const char *target,
> +                      bool has_format, const char *format,
> +                      enum MirrorSyncMode sync,
> +                      bool has_mode, enum NewImageMode mode,
> +                      bool has_speed, int64_t speed,
> +                      bool has_bitmap, const char *bitmap,
> +                      bool has_on_source_error, BlockdevOnError on_source_error,
> +                      bool has_on_target_error, BlockdevOnError on_target_error,
> +                      Error **errp)
> +{
> +    _drive_backup(device, target, has_format, format, sync, has_mode, mode,
> +                  has_speed, speed, has_bitmap, bitmap, has_on_source_error,
> +                  on_source_error, has_on_target_error, on_target_error,
> +                  NULL, NULL, errp);
> +}
> +
>   BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
>   {
>       return bdrv_named_nodes_list();

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
  2015-03-17 18:44   ` Max Reitz
@ 2015-03-17 19:12     ` John Snow
  2015-03-17 22:46     ` John Snow
  1 sibling, 0 replies; 43+ messages in thread
From: John Snow @ 2015-03-17 19:12 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 02:44 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> Allow bitmap successors to carry reference counts.
>>
>> We can in a later patch use this ability to clean up the dirty bitmap
>> according to both the individual job's success and the success of all
>> jobs in the transaction group.
>>
>> The code for cleaning up a bitmap is also moved from backup_run to
>> backup_complete.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   block.c               | 79
>> +++++++++++++++++++++++++++++++++++++++++++++++----
>>   block/backup.c        | 23 ++++++---------
>>   include/block/block.h | 11 ++++---
>>   3 files changed, 87 insertions(+), 26 deletions(-)
>>
>> diff --git a/block.c b/block.c
>> index 5eaa874..a0036af 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -51,6 +51,12 @@
>>   #include <windows.h>
>>   #endif
>> +typedef enum BitmapSuccessorAction {
>> +    SUCCESSOR_ACTION_UNDEFINED = 0,
>> +    SUCCESSOR_ACTION_ABDICATE,
>> +    SUCCESSOR_ACTION_RECLAIM
>> +} BitmapSuccessorAction;
>
> Maybe making this a QAPI enum can come in handy later. I don't know in
> which QMP commands one would use it, but if I could predict the future,
> I'd make trillions (or even billions) at the stock market.
>
> (It's just that it already looks so much alike to a QAPI enum that I
> can't help but to think of making it one)
>

I don't see a reason to expose it, so I won't. We can always add it in 
later.

>> +
>>   /**
>>    * A BdrvDirtyBitmap can be in three possible states:
>>    * (1) successor is false and disabled is false: full r/w mode
>> @@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {
>>       char *name;                 /* Optional non-empty unique ID */
>>       int64_t size;               /* Size of the bitmap (Number of
>> sectors) */
>>       bool disabled;              /* Bitmap is read-only */
>> +    int successor_refcount;     /* Number of active handles to the
>> successor */
>> +    BitmapSuccessorAction act;  /* Action to take on successor upon
>> release */
>>       QLIST_ENTRY(BdrvDirtyBitmap) list;
>>   };
>> @@ -5508,6 +5516,7 @@ int
>> bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>>       /* Install the successor and freeze the parent */
>>       bitmap->successor = child;
>> +    bitmap->successor_refcount = 1;
>>       return 0;
>>   }
>> @@ -5515,9 +5524,9 @@ int
>> bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>>    * For a bitmap with a successor, yield our name to the successor,
>>    * Delete the old bitmap, and return a handle to the new bitmap.
>>    */
>> -BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>> -                                            BdrvDirtyBitmap *bitmap,
>> -                                            Error **errp)
>> +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>> +                                                   BdrvDirtyBitmap
>> *bitmap,
>> +                                                   Error **errp)
>>   {
>>       char *name;
>>       BdrvDirtyBitmap *successor = bitmap->successor;
>> @@ -5542,9 +5551,9 @@ BdrvDirtyBitmap
>> *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>>    * We may wish to re-join the parent and child/successor.
>>    * The merged parent will be un-frozen, but not explicitly re-enabled.
>>    */
>> -BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> -                                           BdrvDirtyBitmap *parent,
>> -                                           Error **errp)
>> +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> +                                                  BdrvDirtyBitmap
>> *parent,
>> +                                                  Error **errp)
>>   {
>>       BdrvDirtyBitmap *successor = parent->successor;
>> @@ -5563,6 +5572,64 @@ BdrvDirtyBitmap
>> *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>>       return parent;
>>   }
>> +static BdrvDirtyBitmap *bdrv_free_bitmap_successor(BlockDriverState *bs,
>> +                                                   BdrvDirtyBitmap
>> *parent,
>> +                                                   Error **errp)
>> +{
>> +    if (parent->successor_refcount) {
>
> I don't know whether you're intending to call this function from
> anywhere outside of bdrv_dirty_bitmap_decref(), but if you don't, please
> just make this an assert(!parent->successor_refcount).
>

OK.

>> +        error_setg(errp, "Cannot free the successor for bitmap '%s', "
>> +                   "because the refcount is non-zero.", parent->name);
>
> Hm, again with the full stops? *g*
>

Can we make checkpatch check for this? I am serious. I can't stop myself 
from doing this.\n

>> +        return NULL;
>> +    }
>> +
>> +    switch (parent->act) {
>> +    case SUCCESSOR_ACTION_UNDEFINED:
>> +        error_setg(errp, "Cannot free the successor for bitmap '%s', "
>> +                   "because the successor action has not yet been set.",
>
> Indeed, again with the full stops. ;-)
>
>> +                   parent->name);
>> +        return NULL;
>
> So you're (for now) only calling this function from
> bdrv_dirty_bitmap_decref(), and that function always makes sure that
> parent->act is set to SUCCESSOR_ACTION_{RECLAIM,ABDICATE}. Why not add
> an assert(parent->act != SUCCESSOR_ACTION_UNDEFINED) before the switch?
> (If that causes problems in regards to compiler warnings or something
> (not having all enum values covered in the switch), just add an
> assert(0); under "case SUCCESSOR_ACTION_UNDEFINED:".)
>

OK, sure.

>> +    case SUCCESSOR_ACTION_RECLAIM:
>> +        return bdrv_reclaim_dirty_bitmap(bs, parent, errp);
>> +    case SUCCESSOR_ACTION_ABDICATE:
>> +        return bdrv_dirty_bitmap_abdicate(bs, parent, errp);
>> +    default:
>> +        error_setg(errp,
>> +                   "Unrecognized successor action (%d), "
>> +                   "cannot free successor for bitmap '%s'",
>> +                   parent->act, parent->name);
>> +        return NULL;
>
> This can be made an assert(0), a g_assert_not_reached(), or simply an
> abort() (with the latter probably being the preferred way).
>
> So I think that all the error_setg() calls are actually cases that
> should never happen. Better make them abort() and drop the Error
> parameter (because *_decref() and *_free() functions normally (for good
> reason) don't have Error parameters...).
>

Uhh, yeah. I'll harden these functions to be meaner about failing.

>> +    }
>> +}
>> +
>> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
>
> I don't know whether I'm that content with the name chosen, because
> you're actually decrementing the refcount of the successor; but since
> the successor is basically a clone of the original bitmap (and I mean in
> the Star Trek sense, that it's a teleported clone and the original is
> intended to be destroyed so the successor can replace it), decrementing
> the refcount of the successor basically is equal to decrementing the
> refcount of the bitmap itself (as long as there is a successor, which
> you are asserting; maybe you want to add a comment about that to
> include/block/block.h, that one can only use this on frozen bitmaps?).
>

Actual struggle I had when naming it myself. Went with the simpler 
answer that was less accurate. I might just augment with a comment 
explaining what exactly is being decremented.

>> +                                          BdrvDirtyBitmap *parent,
>> +                                          int ret,
>> +                                          Error **errp)
>> +{
>> +    assert(bdrv_dirty_bitmap_frozen(parent));
>> +    assert(parent->successor);
>> +
>> +    if (ret) {
>> +        parent->act = SUCCESSOR_ACTION_RECLAIM;
>> +    } else if (parent->act != SUCCESSOR_ACTION_RECLAIM) {
>> +        parent->act = SUCCESSOR_ACTION_ABDICATE;
>> +    }
>> +
>> +    parent->successor_refcount--;
>> +    if (parent->successor_refcount == 0) {
>> +        return bdrv_free_bitmap_successor(bs, parent, errp);
>
> If you drop the Error parameter from bdrv_free_bitmap_successor(), you
> can drop it from this function, too.
>

Noted.

>> +    }
>> +    return parent;
>> +}
>> +
>> +void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent)
>> +{
>> +    assert(bdrv_dirty_bitmap_frozen(parent));
>> +    assert(parent->successor);
>> +
>> +    parent->successor_refcount++;
>> +}
>> +
>>   static void dirty_bitmap_truncate(BdrvDirtyBitmap *bitmap, uint64_t
>> size)
>>   {
>>       /* Should only be frozen during a block backup job, which should
>> have
>> diff --git a/block/backup.c b/block/backup.c
>> index 41bd9af..4332df4 100644
>> --- a/block/backup.c
>> +++ b/block/backup.c
>> @@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void
>> *opaque)
>>       bdrv_unref(s->target);
>> +    if (s->sync_bitmap) {
>> +        BdrvDirtyBitmap *bm;
>> +        bm = bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap,
>> data->ret, NULL);
>> +        assert(bm);
>
> You can use &error_abort as the Error object and drop the assert(); or,
> if you are dropping the Error parameter, there is no need to check the
> return value at all, because it will always be non-NULL (there won't be
> any code path in the function returning NULL at all). Maybe you can even
> drop the return value, too.
>
> I just looked through the series: Actually, you're never using the Error
> parameter for bdrv_dirty_bitmap_decref() at all. Seems to me like you
> really should drop it (and maybe the return value along with it).
>

Return code is there for future usages, I suppose. I wanted to carry it 
around as a "generic" thing. In this one usage, we don't wind up needing 
it, but I wanted to make an allowance for it in this API.

I'll trim down the soft errors.

>> +    }
>> +
>>       block_job_completed(job, data->ret);
>>       g_free(data);
>>   }
>> @@ -419,19 +425,6 @@ leave:
>>       qemu_co_rwlock_wrlock(&job->flush_rwlock);
>>       qemu_co_rwlock_unlock(&job->flush_rwlock);
>> -    if (job->sync_bitmap) {
>> -        BdrvDirtyBitmap *bm;
>> -        if (ret < 0) {
>> -            /* Merge the successor back into the parent, delete
>> nothing. */
>> -            bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
>> -            assert(bm);
>> -            bdrv_enable_dirty_bitmap(job->sync_bitmap);
>
> Hm, what is that function call doing here? It feels like it shouldn't
> have been part of your transactionless series (because other than this,
> there is no caller of bdrv_{en,dis}able_dirty_bitmap() at all).
>

Originally it was to re-enable a bitmap after it was frozen for the 
operation, but that concept has long since been ditched, so it's just a 
NOP right now, basically.

I'll try to pull it out of the original series.

> (You're silently removing it here; removing it is fine, but I guess it
> shouldn't have been there in the first place)
>
>> -        } else {
>> -            /* Everything is fine, delete this bitmap and install the
>> backup. */
>> -            bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
>> -            assert(bm);
>> -        }
>> -    }
>>       hbitmap_free(job->bitmap);
>>       bdrv_iostatus_disable(target);
>> @@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>>    error:
>>       if (sync_bitmap) {
>> -        bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
>> +        BdrvDirtyBitmap *ret;
>> +        ret = bdrv_dirty_bitmap_decref(bs, sync_bitmap, -1, NULL);
>> +        assert(ret);
>>       }
>>   }
>> diff --git a/include/block/block.h b/include/block/block.h
>> index 3a85690..d7859a7 100644
>> --- a/include/block/block.h
>> +++ b/include/block/block.h
>> @@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState
>> *bs);
>>   int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>>                                          BdrvDirtyBitmap *bitmap,
>>                                          Error **errp);
>> -BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>> -                                            BdrvDirtyBitmap *bitmap,
>> -                                            Error **errp);
>> -BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> -                                           BdrvDirtyBitmap *bitmap,
>> -                                           Error **errp);
>> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
>> +                                          BdrvDirtyBitmap *parent,
>> +                                          int ret,
>> +                                          Error **errp);
>> +void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent);
>>   BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
>>                                           const char *name);
>>   void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);
>
> I'm happy with this patch if you drop the Error parameter from
> bdrv_dirty_bitmap_decref() (possibly dropping the return value, too) and
> turn all the error cases into assertions.
>
> Max
>

Okie-dokey.

--js

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
  2015-03-17 18:51   ` Max Reitz
@ 2015-03-17 19:16     ` John Snow
  2015-03-17 19:33       ` Max Reitz
  2015-03-17 20:15       ` Eric Blake
  0 siblings, 2 replies; 43+ messages in thread
From: John Snow @ 2015-03-17 19:16 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 02:51 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> We'd like to be able to specify the callback given to backup_start
>> manually in the case of transactions, so split apart qmp_drive_backup
>> into an implementation and a wrapper.
>>
>> Switch drive_backup_prepare to use the new wrapper, but don't overload
>> the callback and closure yet.
>>
>> This is kind of gross, but we need to forward-declare the wrapper for
>> the drive_backup transaction to be able to use. I could try moving
>> the transaction blocks lower instead, but I wanted (for v1, at least)
>> to minimize code movement so that the core changes were easiest to see.
>>
>> If anyone has suggestions on organization, please suggest them.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   blockdev.c | 77
>> ++++++++++++++++++++++++++++++++++++++++++++++----------------
>>   1 file changed, 58 insertions(+), 19 deletions(-)
>>
>> diff --git a/blockdev.c b/blockdev.c
>> index 3153ee7..9e3b9e9 100644
>> --- a/blockdev.c
>> +++ b/blockdev.c
>> @@ -1449,6 +1449,19 @@ void
>> undo_transaction_wrapper(BlkTransactionState *common)
>>       blk_put_transaction_state(common);
>>   }
>> +static void _drive_backup(const char *device, const char *target,
>
> Please don't use leading underscores. (Please.)
>
> Just call it do_drive_backup() or something.
>
> Other than that, I'm okay with a forward-declaration; if you want to
> move it, but I'd be fine with moving the code, too.
>
> With _drive_backup() being called do_drive_backup():
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
>

Moving the code around might be better, it just makes for a much noisier 
patch. I made my decisions for v1 to keep code disturbed the least.

Once more critiques roll in I will decide what to do.

I'll replace with "do_drive_backup" unless you can think of a nice 
three-letter word to replace "do" so that I don't even have to perturb 
the alignment context ;)

>> +                          bool has_format, const char *format,
>> +                          enum MirrorSyncMode sync,
>> +                          bool has_mode, enum NewImageMode mode,
>> +                          bool has_speed, int64_t speed,
>> +                          bool has_bitmap, const char *bitmap,
>> +                          bool has_on_source_error,
>> +                          BlockdevOnError on_source_error,
>> +                          bool has_on_target_error,
>> +                          BlockdevOnError on_target_error,
>> +                          BlockCompletionFunc *cb, void *opaque,
>> +                          Error **errp);
>> +
>>   /* internal snapshot private data */
>>   typedef struct InternalSnapshotState {
>>       BlkTransactionState common;
>> @@ -1768,15 +1781,16 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>       state->aio_context = bdrv_get_aio_context(bs);
>>       aio_context_acquire(state->aio_context);
>> -    qmp_drive_backup(backup->device, backup->target,
>> -                     backup->has_format, backup->format,
>> -                     backup->sync,
>> -                     backup->has_mode, backup->mode,
>> -                     backup->has_speed, backup->speed,
>> -                     backup->has_bitmap, backup->bitmap,
>> -                     backup->has_on_source_error,
>> backup->on_source_error,
>> -                     backup->has_on_target_error,
>> backup->on_target_error,
>> -                     &local_err);
>> +    _drive_backup(backup->device, backup->target,
>> +                  backup->has_format, backup->format,
>> +                  backup->sync,
>> +                  backup->has_mode, backup->mode,
>> +                  backup->has_speed, backup->speed,
>> +                  backup->has_bitmap, backup->bitmap,
>> +                  backup->has_on_source_error, backup->on_source_error,
>> +                  backup->has_on_target_error, backup->on_target_error,
>> +                  NULL, NULL,
>> +                  &local_err);
>>       if (local_err) {
>>           error_propagate(errp, local_err);
>>           return;
>> @@ -2717,15 +2731,18 @@ out:
>>       aio_context_release(aio_context);
>>   }
>> -void qmp_drive_backup(const char *device, const char *target,
>> -                      bool has_format, const char *format,
>> -                      enum MirrorSyncMode sync,
>> -                      bool has_mode, enum NewImageMode mode,
>> -                      bool has_speed, int64_t speed,
>> -                      bool has_bitmap, const char *bitmap,
>> -                      bool has_on_source_error, BlockdevOnError
>> on_source_error,
>> -                      bool has_on_target_error, BlockdevOnError
>> on_target_error,
>> -                      Error **errp)
>> +static void _drive_backup(const char *device, const char *target,
>> +                          bool has_format, const char *format,
>> +                          enum MirrorSyncMode sync,
>> +                          bool has_mode, enum NewImageMode mode,
>> +                          bool has_speed, int64_t speed,
>> +                          bool has_bitmap, const char *bitmap,
>> +                          bool has_on_source_error,
>> +                          BlockdevOnError on_source_error,
>> +                          bool has_on_target_error,
>> +                          BlockdevOnError on_target_error,
>> +                          BlockCompletionFunc *cb, void *opaque,
>> +                          Error **errp)
>>   {
>>       BlockDriverState *bs;
>>       BlockDriverState *target_bs;
>> @@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>>           }
>>       }
>> +    if (cb == NULL) {
>> +        cb = block_job_cb;
>> +    }
>> +    if (opaque == NULL) {
>> +        opaque = bs;
>> +    }
>>       backup_start(bs, target_bs, speed, sync, bmap,
>>                    on_source_error, on_target_error,
>> -                 block_job_cb, bs, &local_err);
>> +                 cb, opaque, &local_err);
>>       if (local_err != NULL) {
>>           bdrv_unref(target_bs);
>>           error_propagate(errp, local_err);
>> @@ -2850,6 +2873,22 @@ out:
>>       aio_context_release(aio_context);
>>   }
>> +void qmp_drive_backup(const char *device, const char *target,
>> +                      bool has_format, const char *format,
>> +                      enum MirrorSyncMode sync,
>> +                      bool has_mode, enum NewImageMode mode,
>> +                      bool has_speed, int64_t speed,
>> +                      bool has_bitmap, const char *bitmap,
>> +                      bool has_on_source_error, BlockdevOnError
>> on_source_error,
>> +                      bool has_on_target_error, BlockdevOnError
>> on_target_error,
>> +                      Error **errp)
>> +{
>> +    _drive_backup(device, target, has_format, format, sync, has_mode,
>> mode,
>> +                  has_speed, speed, has_bitmap, bitmap,
>> has_on_source_error,
>> +                  on_source_error, has_on_target_error, on_target_error,
>> +                  NULL, NULL, errp);
>> +}
>> +
>>   BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
>>   {
>>       return bdrv_named_nodes_list();
>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
  2015-03-17 19:16     ` John Snow
@ 2015-03-17 19:33       ` Max Reitz
  2015-03-17 20:15       ` Eric Blake
  1 sibling, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-17 19:33 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-17 at 15:16, John Snow wrote:
>
>
> On 03/17/2015 02:51 PM, Max Reitz wrote:
>> On 2015-03-04 at 23:15, John Snow wrote:
>>> We'd like to be able to specify the callback given to backup_start
>>> manually in the case of transactions, so split apart qmp_drive_backup
>>> into an implementation and a wrapper.
>>>
>>> Switch drive_backup_prepare to use the new wrapper, but don't overload
>>> the callback and closure yet.
>>>
>>> This is kind of gross, but we need to forward-declare the wrapper for
>>> the drive_backup transaction to be able to use. I could try moving
>>> the transaction blocks lower instead, but I wanted (for v1, at least)
>>> to minimize code movement so that the core changes were easiest to see.
>>>
>>> If anyone has suggestions on organization, please suggest them.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>>   blockdev.c | 77
>>> ++++++++++++++++++++++++++++++++++++++++++++++----------------
>>>   1 file changed, 58 insertions(+), 19 deletions(-)
>>>
>>> diff --git a/blockdev.c b/blockdev.c
>>> index 3153ee7..9e3b9e9 100644
>>> --- a/blockdev.c
>>> +++ b/blockdev.c
>>> @@ -1449,6 +1449,19 @@ void
>>> undo_transaction_wrapper(BlkTransactionState *common)
>>>       blk_put_transaction_state(common);
>>>   }
>>> +static void _drive_backup(const char *device, const char *target,
>>
>> Please don't use leading underscores. (Please.)
>>
>> Just call it do_drive_backup() or something.
>>
>> Other than that, I'm okay with a forward-declaration; if you want to
>> move it, but I'd be fine with moving the code, too.
>>
>> With _drive_backup() being called do_drive_backup():
>>
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>>
>
> Moving the code around might be better, it just makes for a much 
> noisier patch. I made my decisions for v1 to keep code disturbed the 
> least.
>
> Once more critiques roll in I will decide what to do.
>
> I'll replace with "do_drive_backup" unless you can think of a nice 
> three-letter word to replace "do" so that I don't even have to perturb 
> the alignment context ;)

Well, you're still prepending "static", so I think you won't get around 
perturbing the context :-)

Max

>>> +                          bool has_format, const char *format,
>>> +                          enum MirrorSyncMode sync,
>>> +                          bool has_mode, enum NewImageMode mode,
>>> +                          bool has_speed, int64_t speed,
>>> +                          bool has_bitmap, const char *bitmap,
>>> +                          bool has_on_source_error,
>>> +                          BlockdevOnError on_source_error,
>>> +                          bool has_on_target_error,
>>> +                          BlockdevOnError on_target_error,
>>> +                          BlockCompletionFunc *cb, void *opaque,
>>> +                          Error **errp);
>>> +
>>>   /* internal snapshot private data */
>>>   typedef struct InternalSnapshotState {
>>>       BlkTransactionState common;
>>> @@ -1768,15 +1781,16 @@ static void
>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>       state->aio_context = bdrv_get_aio_context(bs);
>>>       aio_context_acquire(state->aio_context);
>>> -    qmp_drive_backup(backup->device, backup->target,
>>> -                     backup->has_format, backup->format,
>>> -                     backup->sync,
>>> -                     backup->has_mode, backup->mode,
>>> -                     backup->has_speed, backup->speed,
>>> -                     backup->has_bitmap, backup->bitmap,
>>> -                     backup->has_on_source_error,
>>> backup->on_source_error,
>>> -                     backup->has_on_target_error,
>>> backup->on_target_error,
>>> -                     &local_err);
>>> +    _drive_backup(backup->device, backup->target,
>>> +                  backup->has_format, backup->format,
>>> +                  backup->sync,
>>> +                  backup->has_mode, backup->mode,
>>> +                  backup->has_speed, backup->speed,
>>> +                  backup->has_bitmap, backup->bitmap,
>>> +                  backup->has_on_source_error, 
>>> backup->on_source_error,
>>> +                  backup->has_on_target_error, 
>>> backup->on_target_error,
>>> +                  NULL, NULL,
>>> +                  &local_err);
>>>       if (local_err) {
>>>           error_propagate(errp, local_err);
>>>           return;
>>> @@ -2717,15 +2731,18 @@ out:
>>>       aio_context_release(aio_context);
>>>   }
>>> -void qmp_drive_backup(const char *device, const char *target,
>>> -                      bool has_format, const char *format,
>>> -                      enum MirrorSyncMode sync,
>>> -                      bool has_mode, enum NewImageMode mode,
>>> -                      bool has_speed, int64_t speed,
>>> -                      bool has_bitmap, const char *bitmap,
>>> -                      bool has_on_source_error, BlockdevOnError
>>> on_source_error,
>>> -                      bool has_on_target_error, BlockdevOnError
>>> on_target_error,
>>> -                      Error **errp)
>>> +static void _drive_backup(const char *device, const char *target,
>>> +                          bool has_format, const char *format,
>>> +                          enum MirrorSyncMode sync,
>>> +                          bool has_mode, enum NewImageMode mode,
>>> +                          bool has_speed, int64_t speed,
>>> +                          bool has_bitmap, const char *bitmap,
>>> +                          bool has_on_source_error,
>>> +                          BlockdevOnError on_source_error,
>>> +                          bool has_on_target_error,
>>> +                          BlockdevOnError on_target_error,
>>> +                          BlockCompletionFunc *cb, void *opaque,
>>> +                          Error **errp)
>>>   {
>>>       BlockDriverState *bs;
>>>       BlockDriverState *target_bs;
>>> @@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const
>>> char *target,
>>>           }
>>>       }
>>> +    if (cb == NULL) {
>>> +        cb = block_job_cb;
>>> +    }
>>> +    if (opaque == NULL) {
>>> +        opaque = bs;
>>> +    }
>>>       backup_start(bs, target_bs, speed, sync, bmap,
>>>                    on_source_error, on_target_error,
>>> -                 block_job_cb, bs, &local_err);
>>> +                 cb, opaque, &local_err);
>>>       if (local_err != NULL) {
>>>           bdrv_unref(target_bs);
>>>           error_propagate(errp, local_err);
>>> @@ -2850,6 +2873,22 @@ out:
>>>       aio_context_release(aio_context);
>>>   }
>>> +void qmp_drive_backup(const char *device, const char *target,
>>> +                      bool has_format, const char *format,
>>> +                      enum MirrorSyncMode sync,
>>> +                      bool has_mode, enum NewImageMode mode,
>>> +                      bool has_speed, int64_t speed,
>>> +                      bool has_bitmap, const char *bitmap,
>>> +                      bool has_on_source_error, BlockdevOnError
>>> on_source_error,
>>> +                      bool has_on_target_error, BlockdevOnError
>>> on_target_error,
>>> +                      Error **errp)
>>> +{
>>> +    _drive_backup(device, target, has_format, format, sync, has_mode,
>>> mode,
>>> +                  has_speed, speed, has_bitmap, bitmap,
>>> has_on_source_error,
>>> +                  on_source_error, has_on_target_error, 
>>> on_target_error,
>>> +                  NULL, NULL, errp);
>>> +}
>>> +
>>>   BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
>>>   {
>>>       return bdrv_named_nodes_list();
>>
>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support John Snow
@ 2015-03-17 19:49   ` Max Reitz
  2015-03-17 23:27     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-17 19:49 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> This patch actually implements the transactional callback system
> for the drive_backup transaction.
>
> (1) We manually pick up a reference to the bitmap if present to allow
>      its cleanup to be delayed until after all drive_backup jobs launched
>      by the transaction have fully completed.
>
> (2) We create a functional closure that envelops the original drive_backup
>      callback, to be able to intercept the completion status and return code
>      for the job.
>
> (3) We add the drive_backup_cb method for the drive_backup action, which
>      unpacks the completion information and invokes the final cleanup.
>
> (4) backup_transaction_complete will perform the final cleanup on the
>      backup job.
>
> (5) In the case of transaction cancellation, drive_backup_cb is still
>      responsible for cleaning up the mess we may have already made.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   block/backup.c            |  9 +++++++
>   blockdev.c                | 64 ++++++++++++++++++++++++++++++++++++++---------
>   include/block/block_int.h |  8 ++++++
>   3 files changed, 69 insertions(+), 12 deletions(-)
>
> diff --git a/block/backup.c b/block/backup.c
> index 4332df4..3673fb0 100644
> --- a/block/backup.c
> +++ b/block/backup.c
> @@ -233,6 +233,15 @@ typedef struct {
>       int ret;
>   } BackupCompleteData;
>   
> +void backup_transaction_complete(BlockJob *job, int ret)
> +{
> +    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
> +
> +    if (s->sync_bitmap) {
> +        bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, ret, NULL);
> +    }
> +}
> +
>   static void backup_complete(BlockJob *job, void *opaque)
>   {
>       BackupBlockJob *s = container_of(job, BackupBlockJob, common);
> diff --git a/blockdev.c b/blockdev.c
> index 9e3b9e9..3ff14a7 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)
>   
>   typedef void (CallbackFn)(void *opaque, int ret);
>   
> -/* Temporary. Removed in the next patch. */
> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
> -                                    void *opaque,
> -                                    void (*callback)(void *, int),
> -                                    void **new_opaque);
> -
> -void undo_transaction_wrapper(BlkTransactionState *common);
> -
>   /**
>    * Create a new transactional callback wrapper.
>    *
> @@ -1389,7 +1381,7 @@ void undo_transaction_wrapper(BlkTransactionState *common);
>    *
>    * @return The callback to be used instead of @callback.
>    */
> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
> +static CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>                                              void *opaque,
>                                              CallbackFn *callback,
>                                              void **new_opaque)
> @@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>   /**
>    * Undo any actions performed by the above call.
>    */
> -void undo_transaction_wrapper(BlkTransactionState *common)
> +static void undo_transaction_wrapper(BlkTransactionState *common)
>   {
>       BlkTransactionList *btl = common->list;
>       BlkTransactionState *bts;
> @@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)
>       blk_put_transaction_state(common);
>   }
>   
> +static void block_job_cb(void *opaque, int ret);
>   static void _drive_backup(const char *device, const char *target,
>                             bool has_format, const char *format,
>                             enum MirrorSyncMode sync,
> @@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
>       BlockDriverState *bs;
>       DriveBackup *backup;
>       Error *local_err = NULL;
> +    CallbackFn *cb;
> +    void *opaque;
> +    BdrvDirtyBitmap *bmap = NULL;
>   
>       assert(common->action->kind == TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
>       backup = common->action->drive_backup;
> @@ -1777,6 +1773,19 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
>           return;
>       }
>   
> +    /* BackupBlockJob is opaque to us, so look up the bitmap ourselves */
> +    if (backup->has_bitmap) {
> +        bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap);
> +        if (!bmap) {
> +            error_setg(errp, "Bitmap '%s' could not be found", backup->bitmap);
> +            return;
> +        }
> +    }
> +
> +    /* Create our transactional callback wrapper,
> +       and register that we'd like to call .cb() later. */
> +    cb = new_transaction_wrapper(common, bs, block_job_cb, &opaque);
> +
>       /* AioContext is released in .clean() */
>       state->aio_context = bdrv_get_aio_context(bs);
>       aio_context_acquire(state->aio_context);
> @@ -1789,7 +1798,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
>                     backup->has_bitmap, backup->bitmap,
>                     backup->has_on_source_error, backup->on_source_error,
>                     backup->has_on_target_error, backup->on_target_error,
> -                  NULL, NULL,
> +                  cb, opaque,
>                     &local_err);
>       if (local_err) {
>           error_propagate(errp, local_err);
> @@ -1798,6 +1807,11 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
>   
>       state->bs = bs;
>       state->job = state->bs->job;
> +    /* Keep the job alive until .cb(), too. */
> +    block_job_incref(state->job);
> +    if (bmap) {
> +        bdrv_dirty_bitmap_incref(bmap);
> +    }
>   }
>   
>   static void drive_backup_abort(BlkTransactionState *common)
> @@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)
>       if (bs && bs->job && bs->job == state->job) {
>           block_job_cancel_sync(bs->job);
>       }
> +
> +    /* Undo any callback actions we may have done. Putting down references
> +     * obtained during prepare() is handled by cb(). */
> +    undo_transaction_wrapper(common);
>   }
>   
>   static void drive_backup_clean(BlkTransactionState *common)
> @@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common)
>       }
>   }
>   
> +static void drive_backup_cb(BlkTransactionState *common)
> +{
> +    BlkTransactionData *btd = common->opaque;
> +    BlockDriverState *bs = btd->opaque;
> +    DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common);
> +
> +    assert(state->bs == bs);
> +    if (bs->job) {
> +        assert(state->job == bs->job);
> +    }
> +
> +    state->aio_context = bdrv_get_aio_context(bs);
> +    aio_context_acquire(state->aio_context);
> +
> +    /* Note: We also have the individual job's return code in btd->ret */
> +    backup_transaction_complete(state->job, common->list->status);
> +    block_job_decref(state->job);
> +
> +    aio_context_release(state->aio_context);
> +}
> +
>   typedef struct BlockdevBackupState {
>       BlkTransactionState common;
>       BlockDriverState *bs;
> @@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = {
>           .instance_size = sizeof(DriveBackupState),
>           .prepare = drive_backup_prepare,
>           .abort = drive_backup_abort,
> -        .clean = drive_backup_clean
> +        .clean = drive_backup_clean,
> +        .cb = drive_backup_cb
>       },
>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>           .instance_size = sizeof(BlockdevBackupState),
> diff --git a/include/block/block_int.h b/include/block/block_int.h
> index e0d5561..731684d 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
>                     BlockCompletionFunc *cb, void *opaque,
>                     Error **errp);
>   
> +/*
> + * backup_transaction_complete
> + * @job The BackupJob that was associated with a transaction

s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no 
structure named "BackupJob", but this looks like there might be one)

> + * @ret Amalgamated return code for the entire transaction

Hm. The call to this function you're introducing in this patch will 
probably stay the only one so there won't be anyone who'll have to worry 
about what this means, but if there was, they probably won't reach a 
conclusive result.

I know what it means because I've seen patch 3 (right now it means 
"everything OR-ed together so it's 0 for success or some non-zero (maybe 
positive, maybe negative, depending on whether you have an even or an 
odd number of errors, and depending on whether the jobs return negative 
values for errors or not) for error"), but I wouldn't be able to infer 
it from this. At the least you should add that 0 means success and 
everything else means error (if you take my suggestion for patch 3, it 
would be 0 for success and -errno for error, where that error number is 
one of the errors encountered).

Other than that, looks good (as far as I can tell with my still limited 
insights into patch 3).

Max

> + */
> +void backup_transaction_complete(BlockJob *job, int ret);
> +
> +
>   void blk_dev_change_media_cb(BlockBackend *blk, bool load);
>   bool blk_dev_has_removable_media(BlockBackend *blk);
>   void blk_dev_eject_request(BlockBackend *blk, bool force);

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue John Snow
@ 2015-03-17 20:04   ` Max Reitz
  0 siblings, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-17 20:04 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> A filter is added to allow callers to request very specific
> events to be pulled from the event queue, while leaving undesired
> events still in the stream.
>
> This allows to poll for completion data for multiple asynchronous
> events in any arbitrary order.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++
>   1 file changed, 38 insertions(+)

Reviewed-by: Max Reitz <mreitz@redhat.com>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
  2015-03-17 19:16     ` John Snow
  2015-03-17 19:33       ` Max Reitz
@ 2015-03-17 20:15       ` Eric Blake
  1 sibling, 0 replies; 43+ messages in thread
From: Eric Blake @ 2015-03-17 20:15 UTC (permalink / raw)
  To: John Snow, Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

[-- Attachment #1: Type: text/plain, Size: 793 bytes --]

On 03/17/2015 01:16 PM, John Snow wrote:

>> Other than that, I'm okay with a forward-declaration; if you want to
>> move it, but I'd be fine with moving the code, too.
>>
>> With _drive_backup() being called do_drive_backup():
>>
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>>
> 
> Moving the code around might be better, it just makes for a much noisier
> patch. I made my decisions for v1 to keep code disturbed the least.
> 
> Once more critiques roll in I will decide what to do.

I like avoiding forward declarations for non-recursive static functions
(aka topological sorting); but if you do code motion, do it in a
separate patch from the other changes.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring John Snow
@ 2015-03-17 20:44   ` Max Reitz
  2015-03-17 23:40     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-17 20:44 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> The original test was not particularly good about keeping the
> relationships between bitmaps, drives, and images very explicit,
> so this patch adds an explicit 'drive' dict that is used to
> keep these relationships explicit.
>
> This is necessary in order to test two full backup chains
> simultaneously for two drives, which will happen in a forthcoming
> test that examines failure scenarios for incremental backups created
> for multiple drives in a single transaction.
>
> Highlights:
>
> - Each test case carries around a list of drives
> - Each bitmap now acknowledges the full backup belonging to the drive
>    as its "last target" if it hasn't made an incremental backup yet.
> - Most functions generally try to accept a drive argument instead of
>    target or format arguments.
> - Filenames are now based on their formatting and id name.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   tests/qemu-iotests/124 | 212 +++++++++++++++++++++++++++++--------------------
>   1 file changed, 126 insertions(+), 86 deletions(-)

How about putting the previous patch in your transactionless series and 
squashing this one into the respective patches there?

Reviewed-by: Max Reitz <mreitz@redhat.com>

Some remarks below, whether you follow them or not is up to you.

> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
> index da0bf6d..2eccc3e 100644
> --- a/tests/qemu-iotests/124
> +++ b/tests/qemu-iotests/124
> @@ -29,14 +29,18 @@ def io_write_patterns(img, patterns):
>           iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
>   
>   class Bitmap:
> -    def __init__(self, name, node):
> +    def __init__(self, name, drive):
>           self.name = name
> -        self.node = node
> +        self.drive = drive
>           self.pattern = os.path.join(iotests.test_dir.replace('%', '%%'),
> -                                    '%s.backup.%%i.img' % name)
> +                                    '%s.%s.backup.%%i.img' % (drive['id'],
> +                                                              name))
>           self.num = 0
>           self.backups = list()
>   
> +    def base_target(self):
> +        return self.drive['backup']
> +
>       def new_target(self, num=None):
>           if num is None:
>               num = self.num
> @@ -46,7 +50,9 @@ class Bitmap:
>           return target
>   
>       def last_target(self):
> -        return self.backups[-1]
> +        if self.backups:
> +            return self.backups[-1]
> +        return self.base_target()
>   
>       def del_target(self):
>           os.remove(self.backups.pop())
> @@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>       def setUp(self):
>           self.bitmaps = list()
>           self.files = list()
> +        self.drives = list()
>           self.vm = iotests.VM()
> -        self.test_img = os.path.join(iotests.test_dir, 'base.img')
> -        self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
> -        self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
> -        self.img_create(self.test_img, iotests.imgfmt)
> -        self.vm.add_drive(self.test_img)
> +        self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)

Nice. *g*

(using the format name as the file extension)

((even though you're not doing it in __init__))

> +
>           # Create a base image with a distinctive patterning
> -        io_write_patterns(self.test_img, (('0x41', 0, 512),
> -                                          ('0xd5', '1M', '32k'),
> -                                          ('0xdc', '32M', '124k')))
> +        drive0 = self.add_node('drive0')
> +        self.img_create(drive0['file'], drive0['fmt'])
> +        self.vm.add_drive(drive0['file'])
> +        io_write_patterns(drive0['file'], (('0x41', 0, 512),
> +                                           ('0xd5', '1M', '32k'),
> +                                           ('0xdc', '32M', '124k')))
>           self.vm.launch()
>   
>   
> +    def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
> +        if path is None:
> +            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
> +        if backup is None:
> +            backup = os.path.join(iotests.test_dir,
> +                                  '%s.full.backup.%s' % (node_id, fmt))
> +
> +        self.drives.append({
> +            'id': node_id,
> +            'file': path,
> +            'backup': backup,
> +            'fmt': fmt })
> +        return self.drives[-1]
> +
> +
>       def img_create(self, img, fmt=iotests.imgfmt, size='64M',
>                      parent=None, parentFormat=None):
>           plist = list()
> @@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           self.files.append(img)
>   
>   
> -    def create_full_backup(self, drive='drive0'):
> -        res = self.vm.qmp('drive-backup', device=drive,
> -                          sync='full', format=iotests.imgfmt,
> -                          target=self.full_bak)
> +    def create_full_backup(self, drive=None):
> +        if drive is None:
> +            drive = self.drives[-1]
> +
> +        res = self.vm.qmp('drive-backup', device=drive['id'],
> +                          sync='full', format=drive['fmt'],
> +                          target=drive['backup'])
>           self.assert_qmp(res, 'return', {})
> -        self.wait_until_completed(drive)
> -        self.check_full_backup()
> -        self.files.append(self.full_bak)
> +        self.wait_until_completed(drive['id'])
> +        self.check_full_backup(drive)
> +        self.files.append(drive['backup'])
> +        return drive['backup']
>   
>   
> -    def check_full_backup(self):
> -        self.assertTrue(iotests.compare_images(self.test_img, self.full_bak))
> +    def check_full_backup(self, drive=None):
> +        if drive is None:
> +            drive = self.drives[-1]
> +        self.assertTrue(iotests.compare_images(drive['file'], drive['backup']))
>   
>   
> -    def add_bitmap(self, name, node='drive0'):
> -        bitmap = Bitmap(name, node)
> +    def add_bitmap(self, name, drive):
> +        bitmap = Bitmap(name, drive)
>           self.bitmaps.append(bitmap)
> -        result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node,
> -                             name=bitmap.name)
> +        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
> +                             name=name)

No real need to replace bitmap.name by name here, but no harm in doing it.

>           self.assert_qmp(result, 'return', {})
>           return bitmap
>   
> @@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           if bitmap is None:
>               bitmap = self.bitmaps[-1]
>   
> -        # If this is the first incremental backup for a bitmap,
> -        # use the full backup as a backing image. Otherwise, use
> -        # the last incremental backup.
>           if parent is None:
> -            if bitmap.num == 0:
> -                parent = self.full_bak
> -            else:
> -                parent = bitmap.last_target()
> +            parent = bitmap.last_target()
>   
>           target = bitmap.new_target(num)
> -        self.img_create(target, iotests.imgfmt, parent=parent)
> +        self.img_create(target, bitmap.drive['fmt'], parent=parent)
>   
> -        result = self.vm.qmp('drive-backup', device=bitmap.node,
> +        result = self.vm.qmp('drive-backup', device=bitmap.drive['id'],
>                                sync='dirty-bitmap', bitmap=bitmap.name,
> -                             format=iotests.imgfmt, target=target,
> +                             format=bitmap.drive['fmt'], target=target,
>                                mode='existing')
>           self.assert_qmp(result, 'return', {})
>   
> -        event = self.wait_until_completed(bitmap.node, check_offset=validate,
> -                                          allow_failures=(not validate))
> -        if 'error' in event['data']:
> +        return self.wait_incremental(bitmap, validate)
> +
> +
> +    def wait_incremental(self, bitmap=None,
> +                         validate=True, error='Input/output error'):
> +        if bitmap is None:
> +            bitmap = self.bitmaps[-1]
> +
> +        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
> +                                   match={'data': {'device':
> +                                                   bitmap.drive['id']}})
> +        if validate:
> +            self.assert_qmp_absent(event, 'data/error')
> +            return self.check_incremental(bitmap)
> +        else:
> +            self.assert_qmp(event, 'data/error', error)
>               bitmap.del_target()
>               return False
> -        if validate:
> -            return self.check_incremental(target)
>   
>   
> -    def check_incremental(self, target=None):
> -        if target is None:
> -            target = self.bitmaps[-1].last_target()
> -        self.assertTrue(iotests.compare_images(self.test_img, target))
> +    def check_incremental(self, bitmap=None):
> +        if bitmap is None:
> +            bitmap = self.bitmaps[-1]
> +        self.assertTrue(iotests.compare_images(bitmap.drive['file'],
> +                                               bitmap.last_target()))
>           return True
>   
>   
> @@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           i.e.; after IO requests begin modifying the drive.
>           '''
>           self.create_full_backup()
> -        self.add_bitmap('bitmap0', 'drive0')
> +        self.add_bitmap('bitmap0', self.drives[0])
>   
>           # Sanity: Create a "hollow" incremental backup
>           self.create_incremental()
>           # Three writes: One complete overwrite, one new segment,
>           # and one partial overlap.
> -        self.hmp_io_writes('drive0', (('0xab', 0, 512),
> -                                      ('0xfe', '16M', '256k'),
> -                                      ('0x64', '32736k', '64k')))
> +        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
> +                                                  ('0xfe', '16M', '256k'),
> +                                                  ('0x64', '32736k', '64k')))
>           self.create_incremental()
>           # Three more writes, one of each kind, like above
> -        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
> -                                      ('0x55', '8M', '352k'),
> -                                      ('0x78', '15872k', '1M')))
> +        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
> +                                                  ('0x55', '8M', '352k'),
> +                                                  ('0x78', '15872k', '1M')))
>           self.create_incremental()
>           return True
>   
> @@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           bitmap AFTER writes have already occurred. Use transactions to create
>           a full backup and synchronize both bitmaps to this backup.
>           Create an incremental backup through both bitmaps and verify that
> -        both backups match the full backup.
> +        both backups match the current drive0 image.
>           '''
> -        bitmap0 = self.add_bitmap('bitmap0', 'drive0')
> -        self.hmp_io_writes('drive0', (('0xab', 0, 512),
> -                                      ('0xfe', '16M', '256k'),
> -                                      ('0x64', '32736k', '64k')))
> -        bitmap1 = self.add_bitmap('bitmap1', 'drive0')
> +
> +        drive0 = self.drives[0]
> +        bitmap0 = self.add_bitmap('bitmap0', self.drives[0])
> +        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
> +                                          ('0xfe', '16M', '256k'),
> +                                          ('0x64', '32736k', '64k')))
> +        bitmap1 = self.add_bitmap('bitmap1', drive0)
>   
>           result = self.vm.qmp('transaction', actions=[
>               {
>                   'type': 'block-dirty-bitmap-clear',
> -                'data': { 'node': 'drive0',
> -                          'name': 'bitmap0' },
> +                'data': { 'node': bitmap0.drive['id'],
> +                          'name': bitmap0.name },
>               },
>               {
>                   'type': 'block-dirty-bitmap-clear',
> -                'data': { 'node': 'drive0',
> -                          'name': 'bitmap1' },
> +                'data': { 'node': bitmap1.drive['id'],
> +                          'name': bitmap1.name },
>               },
>               {
>                   'type': 'drive-backup',
> -                'data': { 'device': 'drive0',
> +                'data': { 'device': drive0['id'],
>                             'sync': 'full',
> -                          'format': iotests.imgfmt,
> -                          'target': self.full_bak },
> +                          'format': drive0['fmt'],
> +                          'target': drive0['backup'] },
>               }
>           ])
>           self.assert_qmp(result, 'return', {})
>           self.wait_until_completed()
> -        self.files.append(self.full_bak)
> +        self.files.append(drive0['backup'])
>           self.check_full_backup()
>   
> -        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
> -                                      ('0x55', '8M', '352k'),
> -                                      ('0x78', '15872k', '1M')))
> +        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
> +                                          ('0x55', '8M', '352k'),
> +                                          ('0x78', '15872k', '1M')))
>           # Both bitmaps should be in sync and create fully valid
>           # incremental backups
>           res1 = self.create_incremental(bitmap0)
> @@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           afterwards and verify that the backup created is correct.
>           '''
>   
> -        # Create a blkdebug interface to this img as 'drive1'
> +        # Create a blkdebug interface to this img as 'drive1',
> +        # but don't actually create a new image.
> +        drive1 = self.add_node('drive1', self.drives[0]['fmt'],
> +                               path=self.drives[0]['file'],
> +                               backup=self.drives[0]['backup'])
>           result = self.vm.qmp('blockdev-add', options={
>               'id': 'drive1',

"'id': drive1['id']" would work, too.

Max

> -            'driver': iotests.imgfmt,
> +            'driver': drive1['fmt'],
>               'file': {
>                   'driver': 'blkdebug',
>                   'image': {
>                       'driver': 'file',
> -                    'filename': self.test_img
> +                    'filename': drive1['file']
>                   },
>                   'set-state': [{
>                       'event': 'flush_to_disk',
> @@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           })
>           self.assert_qmp(result, 'return', {})
>   
> -        self.create_full_backup()
> -        self.add_bitmap('bitmap0', 'drive1')
> +        self.create_full_backup(self.drives[0])
> +        self.add_bitmap('bitmap0', drive1)
>           # Note: at this point, during a normal execution,
>           # Assume that the VM resumes and begins issuing IO requests here.
>   
> -        self.hmp_io_writes('drive1', (('0xab', 0, 512),
> -                                      ('0xfe', '16M', '256k'),
> -                                      ('0x64', '32736k', '64k')))
> +        self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
> +                                          ('0xfe', '16M', '256k'),
> +                                          ('0x64', '32736k', '64k')))
>   
>           result = self.create_incremental(validate=False)
>           self.assertFalse(result)
> -        self.hmp_io_writes('drive1', (('0x9a', 0, 512),
> -                                      ('0x55', '8M', '352k'),
> -                                      ('0x78', '15872k', '1M')))
> +        self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
> +                                          ('0x55', '8M', '352k'),
> +                                          ('0x78', '15872k', '1M')))
>           self.create_incremental()
>   
>   
>       def test_sync_dirty_bitmap_missing(self):
>           self.assert_no_active_block_jobs()
> -        self.files.append(self.foo_img)
> -        result = self.vm.qmp('drive-backup', device='drive0',
> -                             sync='dirty-bitmap', format=iotests.imgfmt,
> -                             target=self.foo_img)
> +        self.files.append(self.err_img)
> +        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
> +                             sync='dirty-bitmap', format=self.drives[0]['fmt'],
> +                             target=self.err_img)
>           self.assert_qmp(result, 'error/class', 'GenericError')
>   
>   
>       def test_sync_dirty_bitmap_not_found(self):
>           self.assert_no_active_block_jobs()
> -        self.files.append(self.foo_img)
> -        result = self.vm.qmp('drive-backup', device='drive0',
> +        self.files.append(self.err_img)
> +        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
>                                sync='dirty-bitmap', bitmap='unknown',
> -                             format=iotests.imgfmt, target=self.foo_img)
> +                             format=self.drives[0]['fmt'], target=self.err_img)
>           self.assert_qmp(result, 'error/class', 'GenericError')
>   
>   

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring John Snow
@ 2015-03-17 20:50   ` Max Reitz
  2015-03-17 23:44     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-17 20:50 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> Allow tests to call just the backup preparation routine
> without invoking a backup. Useful for transactions where
> we want to prepare, but don't wish to issue the QMP command.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   tests/qemu-iotests/124 | 17 ++++++++++++-----
>   1 file changed, 12 insertions(+), 5 deletions(-)
>
> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
> index 2eccc3e..4afdca1 100644
> --- a/tests/qemu-iotests/124
> +++ b/tests/qemu-iotests/124
> @@ -100,7 +100,6 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>   
>       def img_create(self, img, fmt=iotests.imgfmt, size='64M',
>                      parent=None, parentFormat=None):
> -        plist = list()

Hm. This appears to never have been used. Would it make sense to drop it 
from "iotests: add invalid input incremental backup tests" in your 
transactionless series?

>           if parent:
>               if parentFormat is None:
>                   parentFormat = fmt
> @@ -140,17 +139,25 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           return bitmap
>   
>   
> -    def create_incremental(self, bitmap=None, num=None,
> -                           parent=None, parentFormat=None, validate=True):
> +    def prepare_backup(self, bitmap=None, parent=None):
>           if bitmap is None:
>               bitmap = self.bitmaps[-1]
> -

Removing this empty line looks like it should never have been added, too.

>           if parent is None:
>               parent = bitmap.last_target()
>   
> -        target = bitmap.new_target(num)
> +        target = bitmap.new_target()
>           self.img_create(target, bitmap.drive['fmt'], parent=parent)
> +        return target
>   
> +
> +    def create_incremental(self, bitmap=None, parent=None,
> +                           parentFormat=None, validate=True):
> +        if bitmap is None:
> +            bitmap = self.bitmaps[-1]
> +        if parent is None:
> +            parent = bitmap.last_target()
> +
> +        target = self.prepare_backup(bitmap, parent)
>           result = self.vm.qmp('drive-backup', device=bitmap.drive['id'],
>                                sync='dirty-bitmap', bitmap=bitmap.name,
>                                format=bitmap.drive['fmt'], target=target,

Both "issues" are not caused by this patch, however, so whether you 
squash the hunks somewhere else or not:

Reviewed-by: Max Reitz <mreitz@redhat.com>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test
  2015-03-05  4:15 ` [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test John Snow
@ 2015-03-17 20:59   ` Max Reitz
  2015-03-17 21:04     ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-17 20:59 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-04 at 23:15, John Snow wrote:
> Use a transaction to request an incremental backup across two drives.
> Coerce one of the jobs to fail, and then re-run the transaction.
>
> Verify that no bitmap data was lost due to the partial transaction
> failure.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>   tests/qemu-iotests/124     | 119 +++++++++++++++++++++++++++++++++++++++++++++
>   tests/qemu-iotests/124.out |   4 +-
>   2 files changed, 121 insertions(+), 2 deletions(-)
>
> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
> index 4afdca1..48571a5 100644
> --- a/tests/qemu-iotests/124
> +++ b/tests/qemu-iotests/124
> @@ -331,6 +331,125 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>           self.create_incremental()
>   
>   
> +    def test_transaction_failure(self):
> +        '''Test: Verify backups made from a transaction that partially fails.
> +
> +        Add a second drive with its own unique pattern, and add a bitmap to each
> +        drive. Use blkdebug to interfere with the backup on just one drive and
> +        attempt to create a coherent incremental backup across both drives.
> +
> +        verify a failure in one but not both, then delete the failed stubs and
> +        re-run the same transaction.
> +
> +        verify that both incrementals are created successfully.
> +        '''
> +
> +        # Create a second drive, with pattern:
> +        drive1 = self.add_node('drive1')
> +        self.img_create(drive1['file'], drive1['fmt'])
> +        io_write_patterns(drive1['file'], (('0x14', 0, 512),
> +                                           ('0x5d', '1M', '32k'),
> +                                           ('0xcd', '32M', '124k')))
> +
> +        # Create a blkdebug interface to this img as 'drive1'
> +        result = self.vm.qmp('blockdev-add', options={
> +            'id': drive1['id'],
> +            'driver': drive1['fmt'],
> +            'file': {
> +                'driver': 'blkdebug',
> +                'image': {
> +                    'driver': 'file',
> +                    'filename': drive1['file']
> +                },
> +                'set-state': [{
> +                    'event': 'flush_to_disk',
> +                    'state': 1,
> +                    'new_state': 2
> +                }],
> +                'inject-error': [{
> +                    'event': 'read_aio',
> +                    'errno': 5,
> +                    'state': 2,
> +                    'immediately': False,
> +                    'once': True
> +                }],
> +            }
> +        })
> +        self.assert_qmp(result, 'return', {})
> +
> +        # Create bitmaps and full backups for both drives
> +        drive0 = self.drives[0]
> +        dr0bm0 = self.add_bitmap('bitmap0', drive0)
> +        dr1bm0 = self.add_bitmap('bitmap0', drive1)
> +        self.create_full_backup(drive0)
> +        self.create_full_backup(drive1)
> +        self.assert_no_active_block_jobs()
> +        self.assertFalse(self.vm.get_qmp_events(wait=False))
> +
> +        # Emulate some writes
> +        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
> +                                          ('0xfe', '16M', '256k'),
> +                                          ('0x64', '32736k', '64k')))
> +        self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
> +                                          ('0xef', '16M', '256k'),
> +                                          ('0x46', '32736k', '64k')))
> +
> +        # Create incremental backup targets
> +        target0 = self.prepare_backup(dr0bm0)
> +        target1 = self.prepare_backup(dr1bm0)
> +
> +        # Ask for a new incremental backup per-each drive,
> +        # expecting drive1's backup to fail:
> +        transaction = [
> +            {
> +                'type': 'drive-backup',
> +                'data': { 'device': drive0['id'],
> +                          'sync': 'dirty-bitmap',
> +                          'format': drive0['fmt'],
> +                          'target': target0,
> +                          'mode': 'existing',
> +                          'bitmap': dr0bm0.name },
> +            },
> +            {
> +                'type': 'drive-backup',
> +                'data': { 'device': drive1['id'],
> +                          'sync': 'dirty-bitmap',
> +                          'format': drive1['fmt'],
> +                          'target': target1,
> +                          'mode': 'existing',
> +                          'bitmap': dr1bm0.name }
> +            }
> +        ]
> +        result = self.vm.qmp('transaction', actions=transaction)
> +        self.assert_qmp(result, 'return', {})
> +
> +        # Observe that drive0's backup completes, but drive1's does not.
> +        # Consume drive1's error and ensure all pending actions are completed.
> +        self.wait_incremental(dr0bm0, validate=True)
> +        self.wait_incremental(dr1bm0, validate=False)
> +        error = self.vm.event_wait('BLOCK_JOB_ERROR')
> +        self.assert_qmp(error, 'data', {'device': drive1['id'],
> +                                        'action': 'report',
> +                                        'operation': 'read'})
> +        self.assertFalse(self.vm.get_qmp_events(wait=False))
> +        self.assert_no_active_block_jobs()
> +
> +        # Delete drive0's (successful) backup and create two new empty
> +        # targets to re-run the transaction.
> +        dr0bm0.del_target()
> +        target0 = self.prepare_backup(dr0bm0)
> +        target1 = self.prepare_backup(dr1bm0)
> +
> +        # Re-run the exact same transaction.
> +        result = self.vm.qmp('transaction', actions=transaction)
> +        self.assert_qmp(result, 'return', {})
> +        # Both should complete successfully this time.
> +        self.wait_incremental(dr0bm0, 'drive0')
> +        self.wait_incremental(dr1bm0, 'drive1')

s/'drive.'/validate=True/, I think (will lead to the same result, 
though, yay for dynamically typed languages).

With that fixed:

Reviewed-by: Max Reitz <mreitz@redhat.com>

Nice!

> +        self.assertFalse(self.vm.get_qmp_events(wait=False))
> +        self.assert_no_active_block_jobs()
> +
> +
>       def test_sync_dirty_bitmap_missing(self):
>           self.assert_no_active_block_jobs()
>           self.files.append(self.err_img)
> diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
> index 914e373..3f8a935 100644
> --- a/tests/qemu-iotests/124.out
> +++ b/tests/qemu-iotests/124.out
> @@ -1,5 +1,5 @@
> -.....
> +......
>   ----------------------------------------------------------------------
> -Ran 5 tests
> +Ran 6 tests
>   
>   OK

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test
  2015-03-17 20:59   ` Max Reitz
@ 2015-03-17 21:04     ` John Snow
  0 siblings, 0 replies; 43+ messages in thread
From: John Snow @ 2015-03-17 21:04 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 04:59 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> Use a transaction to request an incremental backup across two drives.
>> Coerce one of the jobs to fail, and then re-run the transaction.
>>
>> Verify that no bitmap data was lost due to the partial transaction
>> failure.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   tests/qemu-iotests/124     | 119
>> +++++++++++++++++++++++++++++++++++++++++++++
>>   tests/qemu-iotests/124.out |   4 +-
>>   2 files changed, 121 insertions(+), 2 deletions(-)
>>
>> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
>> index 4afdca1..48571a5 100644
>> --- a/tests/qemu-iotests/124
>> +++ b/tests/qemu-iotests/124
>> @@ -331,6 +331,125 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           self.create_incremental()
>> +    def test_transaction_failure(self):
>> +        '''Test: Verify backups made from a transaction that
>> partially fails.
>> +
>> +        Add a second drive with its own unique pattern, and add a
>> bitmap to each
>> +        drive. Use blkdebug to interfere with the backup on just one
>> drive and
>> +        attempt to create a coherent incremental backup across both
>> drives.
>> +
>> +        verify a failure in one but not both, then delete the failed
>> stubs and
>> +        re-run the same transaction.
>> +
>> +        verify that both incrementals are created successfully.
>> +        '''
>> +
>> +        # Create a second drive, with pattern:
>> +        drive1 = self.add_node('drive1')
>> +        self.img_create(drive1['file'], drive1['fmt'])
>> +        io_write_patterns(drive1['file'], (('0x14', 0, 512),
>> +                                           ('0x5d', '1M', '32k'),
>> +                                           ('0xcd', '32M', '124k')))
>> +
>> +        # Create a blkdebug interface to this img as 'drive1'
>> +        result = self.vm.qmp('blockdev-add', options={
>> +            'id': drive1['id'],
>> +            'driver': drive1['fmt'],
>> +            'file': {
>> +                'driver': 'blkdebug',
>> +                'image': {
>> +                    'driver': 'file',
>> +                    'filename': drive1['file']
>> +                },
>> +                'set-state': [{
>> +                    'event': 'flush_to_disk',
>> +                    'state': 1,
>> +                    'new_state': 2
>> +                }],
>> +                'inject-error': [{
>> +                    'event': 'read_aio',
>> +                    'errno': 5,
>> +                    'state': 2,
>> +                    'immediately': False,
>> +                    'once': True
>> +                }],
>> +            }
>> +        })
>> +        self.assert_qmp(result, 'return', {})
>> +
>> +        # Create bitmaps and full backups for both drives
>> +        drive0 = self.drives[0]
>> +        dr0bm0 = self.add_bitmap('bitmap0', drive0)
>> +        dr1bm0 = self.add_bitmap('bitmap0', drive1)
>> +        self.create_full_backup(drive0)
>> +        self.create_full_backup(drive1)
>> +        self.assert_no_active_block_jobs()
>> +        self.assertFalse(self.vm.get_qmp_events(wait=False))
>> +
>> +        # Emulate some writes
>> +        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
>> +                                          ('0xfe', '16M', '256k'),
>> +                                          ('0x64', '32736k', '64k')))
>> +        self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
>> +                                          ('0xef', '16M', '256k'),
>> +                                          ('0x46', '32736k', '64k')))
>> +
>> +        # Create incremental backup targets
>> +        target0 = self.prepare_backup(dr0bm0)
>> +        target1 = self.prepare_backup(dr1bm0)
>> +
>> +        # Ask for a new incremental backup per-each drive,
>> +        # expecting drive1's backup to fail:
>> +        transaction = [
>> +            {
>> +                'type': 'drive-backup',
>> +                'data': { 'device': drive0['id'],
>> +                          'sync': 'dirty-bitmap',
>> +                          'format': drive0['fmt'],
>> +                          'target': target0,
>> +                          'mode': 'existing',
>> +                          'bitmap': dr0bm0.name },
>> +            },
>> +            {
>> +                'type': 'drive-backup',
>> +                'data': { 'device': drive1['id'],
>> +                          'sync': 'dirty-bitmap',
>> +                          'format': drive1['fmt'],
>> +                          'target': target1,
>> +                          'mode': 'existing',
>> +                          'bitmap': dr1bm0.name }
>> +            }
>> +        ]
>> +        result = self.vm.qmp('transaction', actions=transaction)
>> +        self.assert_qmp(result, 'return', {})
>> +
>> +        # Observe that drive0's backup completes, but drive1's does not.
>> +        # Consume drive1's error and ensure all pending actions are
>> completed.
>> +        self.wait_incremental(dr0bm0, validate=True)
>> +        self.wait_incremental(dr1bm0, validate=False)
>> +        error = self.vm.event_wait('BLOCK_JOB_ERROR')
>> +        self.assert_qmp(error, 'data', {'device': drive1['id'],
>> +                                        'action': 'report',
>> +                                        'operation': 'read'})
>> +        self.assertFalse(self.vm.get_qmp_events(wait=False))
>> +        self.assert_no_active_block_jobs()
>> +
>> +        # Delete drive0's (successful) backup and create two new empty
>> +        # targets to re-run the transaction.
>> +        dr0bm0.del_target()
>> +        target0 = self.prepare_backup(dr0bm0)
>> +        target1 = self.prepare_backup(dr1bm0)
>> +
>> +        # Re-run the exact same transaction.
>> +        result = self.vm.qmp('transaction', actions=transaction)
>> +        self.assert_qmp(result, 'return', {})
>> +        # Both should complete successfully this time.
>> +        self.wait_incremental(dr0bm0, 'drive0')
>> +        self.wait_incremental(dr1bm0, 'drive1')
>
> s/'drive.'/validate=True/, I think (will lead to the same result,
> though, yay for dynamically typed languages).
>

They really let you get away with murder sometimes. Thanks for the catch.

> With that fixed:
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
>
> Nice!
>
>> +        self.assertFalse(self.vm.get_qmp_events(wait=False))
>> +        self.assert_no_active_block_jobs()
>> +
>> +
>>       def test_sync_dirty_bitmap_missing(self):
>>           self.assert_no_active_block_jobs()
>>           self.files.append(self.err_img)
>> diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
>> index 914e373..3f8a935 100644
>> --- a/tests/qemu-iotests/124.out
>> +++ b/tests/qemu-iotests/124.out
>> @@ -1,5 +1,5 @@
>> -.....
>> +......
>>   ----------------------------------------------------------------------
>> -Ran 5 tests
>> +Ran 6 tests
>>   OK
>
>

-- 
—js

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
  2015-03-17 18:44   ` Max Reitz
  2015-03-17 19:12     ` John Snow
@ 2015-03-17 22:46     ` John Snow
  2015-03-18 13:03       ` Max Reitz
  1 sibling, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-17 22:46 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 02:44 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> Allow bitmap successors to carry reference counts.
>>
>> We can in a later patch use this ability to clean up the dirty bitmap
>> according to both the individual job's success and the success of all
>> jobs in the transaction group.
>>
>> The code for cleaning up a bitmap is also moved from backup_run to
>> backup_complete.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   block.c               | 79
>> +++++++++++++++++++++++++++++++++++++++++++++++----
>>   block/backup.c        | 23 ++++++---------
>>   include/block/block.h | 11 ++++---
>>   3 files changed, 87 insertions(+), 26 deletions(-)
>>
>> diff --git a/block.c b/block.c
>> index 5eaa874..a0036af 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -51,6 +51,12 @@
>>   #include <windows.h>
>>   #endif
>> +typedef enum BitmapSuccessorAction {
>> +    SUCCESSOR_ACTION_UNDEFINED = 0,
>> +    SUCCESSOR_ACTION_ABDICATE,
>> +    SUCCESSOR_ACTION_RECLAIM
>> +} BitmapSuccessorAction;
>
> Maybe making this a QAPI enum can come in handy later. I don't know in
> which QMP commands one would use it, but if I could predict the future,
> I'd make trillions (or even billions) at the stock market.
>
> (It's just that it already looks so much alike to a QAPI enum that I
> can't help but to think of making it one)
>
>> +
>>   /**
>>    * A BdrvDirtyBitmap can be in three possible states:
>>    * (1) successor is false and disabled is false: full r/w mode
>> @@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {
>>       char *name;                 /* Optional non-empty unique ID */
>>       int64_t size;               /* Size of the bitmap (Number of
>> sectors) */
>>       bool disabled;              /* Bitmap is read-only */
>> +    int successor_refcount;     /* Number of active handles to the
>> successor */
>> +    BitmapSuccessorAction act;  /* Action to take on successor upon
>> release */
>>       QLIST_ENTRY(BdrvDirtyBitmap) list;
>>   };
>> @@ -5508,6 +5516,7 @@ int
>> bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>>       /* Install the successor and freeze the parent */
>>       bitmap->successor = child;
>> +    bitmap->successor_refcount = 1;
>>       return 0;
>>   }
>> @@ -5515,9 +5524,9 @@ int
>> bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>>    * For a bitmap with a successor, yield our name to the successor,
>>    * Delete the old bitmap, and return a handle to the new bitmap.
>>    */
>> -BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>> -                                            BdrvDirtyBitmap *bitmap,
>> -                                            Error **errp)
>> +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>> +                                                   BdrvDirtyBitmap
>> *bitmap,
>> +                                                   Error **errp)
>>   {
>>       char *name;
>>       BdrvDirtyBitmap *successor = bitmap->successor;
>> @@ -5542,9 +5551,9 @@ BdrvDirtyBitmap
>> *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>>    * We may wish to re-join the parent and child/successor.
>>    * The merged parent will be un-frozen, but not explicitly re-enabled.
>>    */
>> -BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> -                                           BdrvDirtyBitmap *parent,
>> -                                           Error **errp)
>> +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> +                                                  BdrvDirtyBitmap
>> *parent,
>> +                                                  Error **errp)
>>   {
>>       BdrvDirtyBitmap *successor = parent->successor;
>> @@ -5563,6 +5572,64 @@ BdrvDirtyBitmap
>> *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>>       return parent;
>>   }
>> +static BdrvDirtyBitmap *bdrv_free_bitmap_successor(BlockDriverState *bs,
>> +                                                   BdrvDirtyBitmap
>> *parent,
>> +                                                   Error **errp)
>> +{
>> +    if (parent->successor_refcount) {
>
> I don't know whether you're intending to call this function from
> anywhere outside of bdrv_dirty_bitmap_decref(), but if you don't, please
> just make this an assert(!parent->successor_refcount).
>

No, deleted.

>> +        error_setg(errp, "Cannot free the successor for bitmap '%s', "
>> +                   "because the refcount is non-zero.", parent->name);
>
> Hm, again with the full stops? *g*
>

Erased error message altogether.

>> +        return NULL;
>> +    }
>> +
>> +    switch (parent->act) {
>> +    case SUCCESSOR_ACTION_UNDEFINED:
>> +        error_setg(errp, "Cannot free the successor for bitmap '%s', "
>> +                   "because the successor action has not yet been set.",
>
> Indeed, again with the full stops. ;-)
>

And this one.

>> +                   parent->name);
>> +        return NULL;
>
> So you're (for now) only calling this function from
> bdrv_dirty_bitmap_decref(), and that function always makes sure that
> parent->act is set to SUCCESSOR_ACTION_{RECLAIM,ABDICATE}. Why not add
> an assert(parent->act != SUCCESSOR_ACTION_UNDEFINED) before the switch?
> (If that causes problems in regards to compiler warnings or something
> (not having all enum values covered in the switch), just add an
> assert(0); under "case SUCCESSOR_ACTION_UNDEFINED:".)
>

Will use g_assert_not_reached() because I like the way it reads.

>> +    case SUCCESSOR_ACTION_RECLAIM:
>> +        return bdrv_reclaim_dirty_bitmap(bs, parent, errp);
>> +    case SUCCESSOR_ACTION_ABDICATE:
>> +        return bdrv_dirty_bitmap_abdicate(bs, parent, errp);
>> +    default:
>> +        error_setg(errp,
>> +                   "Unrecognized successor action (%d), "
>> +                   "cannot free successor for bitmap '%s'",
>> +                   parent->act, parent->name);
>> +        return NULL;
>
> This can be made an assert(0), a g_assert_not_reached(), or simply an
> abort() (with the latter probably being the preferred way).
>
> So I think that all the error_setg() calls are actually cases that
> should never happen. Better make them abort() and drop the Error
> parameter (because *_decref() and *_free() functions normally (for good
> reason) don't have Error parameters...).
>

The problem all stems from when I made abdicate and reclaim callable 
interfaces in the original patch series, so they carried errp pointers 
and I let them flow outwards from there.

So, the transactionless set is still on-list, so I could clean those 
interfaces too, but I suppose I wanted to see how this series was 
received before I squashed too many things back into the v1 ... I'd 
rather leave that series be for obvious reasons.

>> +    }
>> +}
>> +
>> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
>
> I don't know whether I'm that content with the name chosen, because
> you're actually decrementing the refcount of the successor; but since
> the successor is basically a clone of the original bitmap (and I mean in
> the Star Trek sense, that it's a teleported clone and the original is
> intended to be destroyed so the successor can replace it), decrementing
> the refcount of the successor basically is equal to decrementing the
> refcount of the bitmap itself (as long as there is a successor, which
> you are asserting; maybe you want to add a comment about that to
> include/block/block.h, that one can only use this on frozen bitmaps?).
>

I could get clever with the name and call it bdrv_frozen_bitmap_decref, 
which hopefully still shows membership to the bdrv_dirty_bitmap class of 
functions but clarifies its usage sufficiently.

>> +                                          BdrvDirtyBitmap *parent,
>> +                                          int ret,
>> +                                          Error **errp)
>> +{
>> +    assert(bdrv_dirty_bitmap_frozen(parent));
>> +    assert(parent->successor);
>> +
>> +    if (ret) {
>> +        parent->act = SUCCESSOR_ACTION_RECLAIM;
>> +    } else if (parent->act != SUCCESSOR_ACTION_RECLAIM) {
>> +        parent->act = SUCCESSOR_ACTION_ABDICATE;
>> +    }
>> +
>> +    parent->successor_refcount--;
>> +    if (parent->successor_refcount == 0) {
>> +        return bdrv_free_bitmap_successor(bs, parent, errp);
>
> If you drop the Error parameter from bdrv_free_bitmap_successor(), you
> can drop it from this function, too.
>

Done.

>> +    }
>> +    return parent;
>> +}
>> +
>> +void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent)
>> +{
>> +    assert(bdrv_dirty_bitmap_frozen(parent));
>> +    assert(parent->successor);
>> +
>> +    parent->successor_refcount++;
>> +}
>> +
>>   static void dirty_bitmap_truncate(BdrvDirtyBitmap *bitmap, uint64_t
>> size)
>>   {
>>       /* Should only be frozen during a block backup job, which should
>> have
>> diff --git a/block/backup.c b/block/backup.c
>> index 41bd9af..4332df4 100644
>> --- a/block/backup.c
>> +++ b/block/backup.c
>> @@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void
>> *opaque)
>>       bdrv_unref(s->target);
>> +    if (s->sync_bitmap) {
>> +        BdrvDirtyBitmap *bm;
>> +        bm = bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap,
>> data->ret, NULL);
>> +        assert(bm);
>
> You can use &error_abort as the Error object and drop the assert(); or,
> if you are dropping the Error parameter, there is no need to check the
> return value at all, because it will always be non-NULL (there won't be
> any code path in the function returning NULL at all). Maybe you can even
> drop the return value, too.
>
> I just looked through the series: Actually, you're never using the Error
> parameter for bdrv_dirty_bitmap_decref() at all. Seems to me like you
> really should drop it (and maybe the return value along with it).
>

I actually use this parameter to return the "new bitmap" (if any) after 
the decrement operation. I wanted to leave the window open for merge 
optimizations, so I tell the caller which bitmap remains after the 
operation.

I will cull the errp, but will likely leave the return code.

>> +    }
>> +
>>       block_job_completed(job, data->ret);
>>       g_free(data);
>>   }
>> @@ -419,19 +425,6 @@ leave:
>>       qemu_co_rwlock_wrlock(&job->flush_rwlock);
>>       qemu_co_rwlock_unlock(&job->flush_rwlock);
>> -    if (job->sync_bitmap) {
>> -        BdrvDirtyBitmap *bm;
>> -        if (ret < 0) {
>> -            /* Merge the successor back into the parent, delete
>> nothing. */
>> -            bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
>> -            assert(bm);
>> -            bdrv_enable_dirty_bitmap(job->sync_bitmap);
>
> Hm, what is that function call doing here? It feels like it shouldn't
> have been part of your transactionless series (because other than this,
> there is no caller of bdrv_{en,dis}able_dirty_bitmap() at all).
>
> (You're silently removing it here; removing it is fine, but I guess it
> shouldn't have been there in the first place)
>

Fixed in v4 for the transactionless series.

>> -        } else {
>> -            /* Everything is fine, delete this bitmap and install the
>> backup. */
>> -            bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
>> -            assert(bm);
>> -        }
>> -    }
>>       hbitmap_free(job->bitmap);
>>       bdrv_iostatus_disable(target);
>> @@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>>    error:
>>       if (sync_bitmap) {
>> -        bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
>> +        BdrvDirtyBitmap *ret;
>> +        ret = bdrv_dirty_bitmap_decref(bs, sync_bitmap, -1, NULL);
>> +        assert(ret);
>>       }
>>   }
>> diff --git a/include/block/block.h b/include/block/block.h
>> index 3a85690..d7859a7 100644
>> --- a/include/block/block.h
>> +++ b/include/block/block.h
>> @@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState
>> *bs);
>>   int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
>>                                          BdrvDirtyBitmap *bitmap,
>>                                          Error **errp);
>> -BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
>> -                                            BdrvDirtyBitmap *bitmap,
>> -                                            Error **errp);
>> -BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> -                                           BdrvDirtyBitmap *bitmap,
>> -                                           Error **errp);
>> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
>> +                                          BdrvDirtyBitmap *parent,
>> +                                          int ret,
>> +                                          Error **errp);
>> +void bdrv_dirty_bitmap_incref(BdrvDirtyBitmap *parent);
>>   BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
>>                                           const char *name);
>>   void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);
>
> I'm happy with this patch if you drop the Error parameter from
> bdrv_dirty_bitmap_decref() (possibly dropping the return value, too) and
> turn all the error cases into assertions.
>
> Max
>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
  2015-03-17 19:49   ` Max Reitz
@ 2015-03-17 23:27     ` John Snow
  2015-03-18 13:41       ` Max Reitz
  0 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-17 23:27 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 03:49 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> This patch actually implements the transactional callback system
>> for the drive_backup transaction.
>>
>> (1) We manually pick up a reference to the bitmap if present to allow
>>      its cleanup to be delayed until after all drive_backup jobs launched
>>      by the transaction have fully completed.
>>
>> (2) We create a functional closure that envelops the original
>> drive_backup
>>      callback, to be able to intercept the completion status and
>> return code
>>      for the job.
>>
>> (3) We add the drive_backup_cb method for the drive_backup action, which
>>      unpacks the completion information and invokes the final cleanup.
>>
>> (4) backup_transaction_complete will perform the final cleanup on the
>>      backup job.
>>
>> (5) In the case of transaction cancellation, drive_backup_cb is still
>>      responsible for cleaning up the mess we may have already made.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   block/backup.c            |  9 +++++++
>>   blockdev.c                | 64
>> ++++++++++++++++++++++++++++++++++++++---------
>>   include/block/block_int.h |  8 ++++++
>>   3 files changed, 69 insertions(+), 12 deletions(-)
>>
>> diff --git a/block/backup.c b/block/backup.c
>> index 4332df4..3673fb0 100644
>> --- a/block/backup.c
>> +++ b/block/backup.c
>> @@ -233,6 +233,15 @@ typedef struct {
>>       int ret;
>>   } BackupCompleteData;
>> +void backup_transaction_complete(BlockJob *job, int ret)
>> +{
>> +    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>> +
>> +    if (s->sync_bitmap) {
>> +        bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, ret, NULL);
>> +    }
>> +}
>> +
>>   static void backup_complete(BlockJob *job, void *opaque)
>>   {
>>       BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>> diff --git a/blockdev.c b/blockdev.c
>> index 9e3b9e9..3ff14a7 100644
>> --- a/blockdev.c
>> +++ b/blockdev.c
>> @@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque,
>> int ret)
>>   typedef void (CallbackFn)(void *opaque, int ret);
>> -/* Temporary. Removed in the next patch. */
>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>> -                                    void *opaque,
>> -                                    void (*callback)(void *, int),
>> -                                    void **new_opaque);
>> -
>> -void undo_transaction_wrapper(BlkTransactionState *common);
>> -
>>   /**
>>    * Create a new transactional callback wrapper.
>>    *
>> @@ -1389,7 +1381,7 @@ void
>> undo_transaction_wrapper(BlkTransactionState *common);
>>    *
>>    * @return The callback to be used instead of @callback.
>>    */
>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>> +static CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>                                              void *opaque,
>>                                              CallbackFn *callback,
>>                                              void **new_opaque)
>> @@ -1416,7 +1408,7 @@ CallbackFn
>> *new_transaction_wrapper(BlkTransactionState *common,
>>   /**
>>    * Undo any actions performed by the above call.
>>    */
>> -void undo_transaction_wrapper(BlkTransactionState *common)
>> +static void undo_transaction_wrapper(BlkTransactionState *common)
>>   {
>>       BlkTransactionList *btl = common->list;
>>       BlkTransactionState *bts;
>> @@ -1449,6 +1441,7 @@ void
>> undo_transaction_wrapper(BlkTransactionState *common)
>>       blk_put_transaction_state(common);
>>   }
>> +static void block_job_cb(void *opaque, int ret);
>>   static void _drive_backup(const char *device, const char *target,
>>                             bool has_format, const char *format,
>>                             enum MirrorSyncMode sync,
>> @@ -1767,6 +1760,9 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>       BlockDriverState *bs;
>>       DriveBackup *backup;
>>       Error *local_err = NULL;
>> +    CallbackFn *cb;
>> +    void *opaque;
>> +    BdrvDirtyBitmap *bmap = NULL;
>>       assert(common->action->kind ==
>> TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
>>       backup = common->action->drive_backup;
>> @@ -1777,6 +1773,19 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>           return;
>>       }
>> +    /* BackupBlockJob is opaque to us, so look up the bitmap
>> ourselves */
>> +    if (backup->has_bitmap) {
>> +        bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap);
>> +        if (!bmap) {
>> +            error_setg(errp, "Bitmap '%s' could not be found",
>> backup->bitmap);
>> +            return;
>> +        }
>> +    }
>> +
>> +    /* Create our transactional callback wrapper,
>> +       and register that we'd like to call .cb() later. */
>> +    cb = new_transaction_wrapper(common, bs, block_job_cb, &opaque);
>> +
>>       /* AioContext is released in .clean() */
>>       state->aio_context = bdrv_get_aio_context(bs);
>>       aio_context_acquire(state->aio_context);
>> @@ -1789,7 +1798,7 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>                     backup->has_bitmap, backup->bitmap,
>>                     backup->has_on_source_error, backup->on_source_error,
>>                     backup->has_on_target_error, backup->on_target_error,
>> -                  NULL, NULL,
>> +                  cb, opaque,
>>                     &local_err);
>>       if (local_err) {
>>           error_propagate(errp, local_err);
>> @@ -1798,6 +1807,11 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>       state->bs = bs;
>>       state->job = state->bs->job;
>> +    /* Keep the job alive until .cb(), too. */
>> +    block_job_incref(state->job);
>> +    if (bmap) {
>> +        bdrv_dirty_bitmap_incref(bmap);
>> +    }
>>   }
>>   static void drive_backup_abort(BlkTransactionState *common)
>> @@ -1809,6 +1823,10 @@ static void
>> drive_backup_abort(BlkTransactionState *common)
>>       if (bs && bs->job && bs->job == state->job) {
>>           block_job_cancel_sync(bs->job);
>>       }
>> +
>> +    /* Undo any callback actions we may have done. Putting down
>> references
>> +     * obtained during prepare() is handled by cb(). */
>> +    undo_transaction_wrapper(common);
>>   }
>>   static void drive_backup_clean(BlkTransactionState *common)
>> @@ -1820,6 +1838,27 @@ static void
>> drive_backup_clean(BlkTransactionState *common)
>>       }
>>   }
>> +static void drive_backup_cb(BlkTransactionState *common)
>> +{
>> +    BlkTransactionData *btd = common->opaque;
>> +    BlockDriverState *bs = btd->opaque;
>> +    DriveBackupState *state = DO_UPCAST(DriveBackupState, common,
>> common);
>> +
>> +    assert(state->bs == bs);
>> +    if (bs->job) {
>> +        assert(state->job == bs->job);
>> +    }
>> +
>> +    state->aio_context = bdrv_get_aio_context(bs);
>> +    aio_context_acquire(state->aio_context);
>> +
>> +    /* Note: We also have the individual job's return code in
>> btd->ret */
>> +    backup_transaction_complete(state->job, common->list->status);
>> +    block_job_decref(state->job);
>> +
>> +    aio_context_release(state->aio_context);
>> +}
>> +
>>   typedef struct BlockdevBackupState {
>>       BlkTransactionState common;
>>       BlockDriverState *bs;
>> @@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = {
>>           .instance_size = sizeof(DriveBackupState),
>>           .prepare = drive_backup_prepare,
>>           .abort = drive_backup_abort,
>> -        .clean = drive_backup_clean
>> +        .clean = drive_backup_clean,
>> +        .cb = drive_backup_cb
>>       },
>>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>>           .instance_size = sizeof(BlockdevBackupState),
>> diff --git a/include/block/block_int.h b/include/block/block_int.h
>> index e0d5561..731684d 100644
>> --- a/include/block/block_int.h
>> +++ b/include/block/block_int.h
>> @@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>>                     BlockCompletionFunc *cb, void *opaque,
>>                     Error **errp);
>> +/*
>> + * backup_transaction_complete
>> + * @job The BackupJob that was associated with a transaction
>
> s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no
> structure named "BackupJob", but this looks like there might be one)
>
>> + * @ret Amalgamated return code for the entire transaction
>
> Hm. The call to this function you're introducing in this patch will
> probably stay the only one so there won't be anyone who'll have to worry
> about what this means, but if there was, they probably won't reach a
> conclusive result.
>
> I know what it means because I've seen patch 3 (right now it means
> "everything OR-ed together so it's 0 for success or some non-zero (maybe
> positive, maybe negative, depending on whether you have an even or an
> odd number of errors, and depending on whether the jobs return negative
> values for errors or not) for error"), but I wouldn't be able to infer
> it from this. At the least you should add that 0 means success and
> everything else means error (if you take my suggestion for patch 3, it
> would be 0 for success and -errno for error, where that error number is
> one of the errors encountered).
>
> Other than that, looks good (as far as I can tell with my still limited
> insights into patch 3).
>
> Max
>

It's my opinion that this patch should give you insight into patch #3, 
instead of the other way around. This patch is useful for demonstrating 
the general flow, because I kept all drive_backup specific concerns 
strictly separated from patch #3.




I'll write a more comprehensive document for the docs/ folder soon, but 
the general shape of the idea is "The cleanup actions defined in the 
.cb() method will be executed after every job in the transactional group 
has completed."

But there's some fine print:

"'every job' refers only to jobs that have the capability to register a 
post-transaction callback, which currently means only drive_backup."

The general approach to this is, mechanically:

(1) Extend the life of objects of interest with reference counts, 
including Jobs, Bitmaps, and BlkTransactionStates.

(2) "steal" the callback from a BlockJob and, when invoked, update our 
management information for the transaction group in BlkTransactionList.

(3) Once all jobs we have sent out return, execute the .cb() methods as 
indicated in the BlkTransactionList.



So, if you were adding a callback to a different QMP transaction:
- Look at new_transaction_wrapper; you'll use this to bump up various 
references used internally for this whole system, and it'll keep 
qmp_transaction from being able to delete the list and state objects.

   This function will give to you (as a gimmick) a new callback and 
opaque data structure pointer for you to give to the job that you are 
starting. I obfuscate this just a bit to make it clear that you should 
be using this function every time to help manage the transactional state.

- Now, the job will run on its own happy way, When it finishes, it will 
call transaction_callback, which is the function that "intercepts" the 
callbacks, and dispatches the original enveloped callbacks. It ditches 
the original data we used to know how to call the original callback, and 
begins storing completion information for jobs instead.

- transaction_callback will call blk_transaction_complete to store 
completion info for one job. In turn, it calls put_blk_transaction_list 
to decrement the pending jobs count (the "jobs" refcount) and once that 
hits zero ...

- All of the callback mechanisms associated with each 
BlockTransactionState are run via blk_run_transaction_callbacks.

- This is where drive_backup_cb is going to get invoked again, and then 
we will splinter back out into backup-specific concerns, with Jobs and 
Bitmaps and what not.


 From the point of view of the "transaction action developer," the 
interface to the new feature is found via the "new_transaction_wrapper" 
function, But some care must be taken to make sure this callback 
actually gets used once it has been requested. The whole thing can be 
aborted with the "undo_transaction_wrapper" function.

All of the other functions that got added to blockdev.c in Patch #3 are 
there to assist these two "interface" functions in doing what they claim to.

Everything in patch 4 and 5 just adds more reference count flexibility 
to things that are only of interest to drive_backup.

Patch 6 allows us to inject our special callback wrapper into the 
qmp_drive_backup function.


Clear as mud?
--js

>> + */
>> +void backup_transaction_complete(BlockJob *job, int ret);
>> +
>> +
>>   void blk_dev_change_media_cb(BlockBackend *blk, bool load);
>>   bool blk_dev_has_removable_media(BlockBackend *blk);
>>   void blk_dev_eject_request(BlockBackend *blk, bool force);
>
>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
  2015-03-17 20:44   ` Max Reitz
@ 2015-03-17 23:40     ` John Snow
  2015-03-18 13:44       ` Max Reitz
  0 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-17 23:40 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 04:44 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> The original test was not particularly good about keeping the
>> relationships between bitmaps, drives, and images very explicit,
>> so this patch adds an explicit 'drive' dict that is used to
>> keep these relationships explicit.
>>
>> This is necessary in order to test two full backup chains
>> simultaneously for two drives, which will happen in a forthcoming
>> test that examines failure scenarios for incremental backups created
>> for multiple drives in a single transaction.
>>
>> Highlights:
>>
>> - Each test case carries around a list of drives
>> - Each bitmap now acknowledges the full backup belonging to the drive
>>    as its "last target" if it hasn't made an incremental backup yet.
>> - Most functions generally try to accept a drive argument instead of
>>    target or format arguments.
>> - Filenames are now based on their formatting and id name.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   tests/qemu-iotests/124 | 212
>> +++++++++++++++++++++++++++++--------------------
>>   1 file changed, 126 insertions(+), 86 deletions(-)
>
> How about putting the previous patch in your transactionless series and
> squashing this one into the respective patches there?
>

I don't want to startle the cat.


I'd rather get the 'transactionless' checked in, then worry about 
features that are important for transactions in the series that 
introduces those features.

If we keep thinking "Oh, but this function hasn't gone upstream in the 
first place, so let's fix it there" in this series, then the first 
series getting committed is going to wind up dependent on this series 
getting reviewed as we keep changing things.

Since this group of patches is more experimental, I'd really prefer to 
keep it separate that way.

Even if this patch is ugly. Sorry about that.

> Reviewed-by: Max Reitz <mreitz@redhat.com>
>
> Some remarks below, whether you follow them or not is up to you.
>
>> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
>> index da0bf6d..2eccc3e 100644
>> --- a/tests/qemu-iotests/124
>> +++ b/tests/qemu-iotests/124
>> @@ -29,14 +29,18 @@ def io_write_patterns(img, patterns):
>>           iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
>>   class Bitmap:
>> -    def __init__(self, name, node):
>> +    def __init__(self, name, drive):
>>           self.name = name
>> -        self.node = node
>> +        self.drive = drive
>>           self.pattern = os.path.join(iotests.test_dir.replace('%',
>> '%%'),
>> -                                    '%s.backup.%%i.img' % name)
>> +                                    '%s.%s.backup.%%i.img' %
>> (drive['id'],
>> +                                                              name))
>>           self.num = 0
>>           self.backups = list()
>> +    def base_target(self):
>> +        return self.drive['backup']
>> +
>>       def new_target(self, num=None):
>>           if num is None:
>>               num = self.num
>> @@ -46,7 +50,9 @@ class Bitmap:
>>           return target
>>       def last_target(self):
>> -        return self.backups[-1]
>> +        if self.backups:
>> +            return self.backups[-1]
>> +        return self.base_target()
>>       def del_target(self):
>>           os.remove(self.backups.pop())
>> @@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>       def setUp(self):
>>           self.bitmaps = list()
>>           self.files = list()
>> +        self.drives = list()
>>           self.vm = iotests.VM()
>> -        self.test_img = os.path.join(iotests.test_dir, 'base.img')
>> -        self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
>> -        self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
>> -        self.img_create(self.test_img, iotests.imgfmt)
>> -        self.vm.add_drive(self.test_img)
>> +        self.err_img = os.path.join(iotests.test_dir, 'err.%s' %
>> iotests.imgfmt)
>
> Nice. *g*
>
> (using the format name as the file extension)
>
> ((even though you're not doing it in __init__))
>

Can't please everyone. :)

>> +
>>           # Create a base image with a distinctive patterning
>> -        io_write_patterns(self.test_img, (('0x41', 0, 512),
>> -                                          ('0xd5', '1M', '32k'),
>> -                                          ('0xdc', '32M', '124k')))
>> +        drive0 = self.add_node('drive0')
>> +        self.img_create(drive0['file'], drive0['fmt'])
>> +        self.vm.add_drive(drive0['file'])
>> +        io_write_patterns(drive0['file'], (('0x41', 0, 512),
>> +                                           ('0xd5', '1M', '32k'),
>> +                                           ('0xdc', '32M', '124k')))
>>           self.vm.launch()
>> +    def add_node(self, node_id, fmt=iotests.imgfmt, path=None,
>> backup=None):
>> +        if path is None:
>> +            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id,
>> fmt))
>> +        if backup is None:
>> +            backup = os.path.join(iotests.test_dir,
>> +                                  '%s.full.backup.%s' % (node_id, fmt))
>> +
>> +        self.drives.append({
>> +            'id': node_id,
>> +            'file': path,
>> +            'backup': backup,
>> +            'fmt': fmt })
>> +        return self.drives[-1]
>> +
>> +
>>       def img_create(self, img, fmt=iotests.imgfmt, size='64M',
>>                      parent=None, parentFormat=None):
>>           plist = list()
>> @@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           self.files.append(img)
>> -    def create_full_backup(self, drive='drive0'):
>> -        res = self.vm.qmp('drive-backup', device=drive,
>> -                          sync='full', format=iotests.imgfmt,
>> -                          target=self.full_bak)
>> +    def create_full_backup(self, drive=None):
>> +        if drive is None:
>> +            drive = self.drives[-1]
>> +
>> +        res = self.vm.qmp('drive-backup', device=drive['id'],
>> +                          sync='full', format=drive['fmt'],
>> +                          target=drive['backup'])
>>           self.assert_qmp(res, 'return', {})
>> -        self.wait_until_completed(drive)
>> -        self.check_full_backup()
>> -        self.files.append(self.full_bak)
>> +        self.wait_until_completed(drive['id'])
>> +        self.check_full_backup(drive)
>> +        self.files.append(drive['backup'])
>> +        return drive['backup']
>> -    def check_full_backup(self):
>> -        self.assertTrue(iotests.compare_images(self.test_img,
>> self.full_bak))
>> +    def check_full_backup(self, drive=None):
>> +        if drive is None:
>> +            drive = self.drives[-1]
>> +        self.assertTrue(iotests.compare_images(drive['file'],
>> drive['backup']))
>> -    def add_bitmap(self, name, node='drive0'):
>> -        bitmap = Bitmap(name, node)
>> +    def add_bitmap(self, name, drive):
>> +        bitmap = Bitmap(name, drive)
>>           self.bitmaps.append(bitmap)
>> -        result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node,
>> -                             name=bitmap.name)
>> +        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
>> +                             name=name)
>
> No real need to replace bitmap.name by name here, but no harm in doing it.
>
>>           self.assert_qmp(result, 'return', {})
>>           return bitmap
>> @@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           if bitmap is None:
>>               bitmap = self.bitmaps[-1]
>> -        # If this is the first incremental backup for a bitmap,
>> -        # use the full backup as a backing image. Otherwise, use
>> -        # the last incremental backup.
>>           if parent is None:
>> -            if bitmap.num == 0:
>> -                parent = self.full_bak
>> -            else:
>> -                parent = bitmap.last_target()
>> +            parent = bitmap.last_target()
>>           target = bitmap.new_target(num)
>> -        self.img_create(target, iotests.imgfmt, parent=parent)
>> +        self.img_create(target, bitmap.drive['fmt'], parent=parent)
>> -        result = self.vm.qmp('drive-backup', device=bitmap.node,
>> +        result = self.vm.qmp('drive-backup', device=bitmap.drive['id'],
>>                                sync='dirty-bitmap', bitmap=bitmap.name,
>> -                             format=iotests.imgfmt, target=target,
>> +                             format=bitmap.drive['fmt'], target=target,
>>                                mode='existing')
>>           self.assert_qmp(result, 'return', {})
>> -        event = self.wait_until_completed(bitmap.node,
>> check_offset=validate,
>> -                                          allow_failures=(not validate))
>> -        if 'error' in event['data']:
>> +        return self.wait_incremental(bitmap, validate)
>> +
>> +
>> +    def wait_incremental(self, bitmap=None,
>> +                         validate=True, error='Input/output error'):
>> +        if bitmap is None:
>> +            bitmap = self.bitmaps[-1]
>> +
>> +        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
>> +                                   match={'data': {'device':
>> +                                                   bitmap.drive['id']}})
>> +        if validate:
>> +            self.assert_qmp_absent(event, 'data/error')
>> +            return self.check_incremental(bitmap)
>> +        else:
>> +            self.assert_qmp(event, 'data/error', error)
>>               bitmap.del_target()
>>               return False
>> -        if validate:
>> -            return self.check_incremental(target)
>> -    def check_incremental(self, target=None):
>> -        if target is None:
>> -            target = self.bitmaps[-1].last_target()
>> -        self.assertTrue(iotests.compare_images(self.test_img, target))
>> +    def check_incremental(self, bitmap=None):
>> +        if bitmap is None:
>> +            bitmap = self.bitmaps[-1]
>> +        self.assertTrue(iotests.compare_images(bitmap.drive['file'],
>> +                                               bitmap.last_target()))
>>           return True
>> @@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           i.e.; after IO requests begin modifying the drive.
>>           '''
>>           self.create_full_backup()
>> -        self.add_bitmap('bitmap0', 'drive0')
>> +        self.add_bitmap('bitmap0', self.drives[0])
>>           # Sanity: Create a "hollow" incremental backup
>>           self.create_incremental()
>>           # Three writes: One complete overwrite, one new segment,
>>           # and one partial overlap.
>> -        self.hmp_io_writes('drive0', (('0xab', 0, 512),
>> -                                      ('0xfe', '16M', '256k'),
>> -                                      ('0x64', '32736k', '64k')))
>> +        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
>> +                                                  ('0xfe', '16M',
>> '256k'),
>> +                                                  ('0x64', '32736k',
>> '64k')))
>>           self.create_incremental()
>>           # Three more writes, one of each kind, like above
>> -        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
>> -                                      ('0x55', '8M', '352k'),
>> -                                      ('0x78', '15872k', '1M')))
>> +        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
>> +                                                  ('0x55', '8M',
>> '352k'),
>> +                                                  ('0x78', '15872k',
>> '1M')))
>>           self.create_incremental()
>>           return True
>> @@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           bitmap AFTER writes have already occurred. Use transactions
>> to create
>>           a full backup and synchronize both bitmaps to this backup.
>>           Create an incremental backup through both bitmaps and verify
>> that
>> -        both backups match the full backup.
>> +        both backups match the current drive0 image.
>>           '''
>> -        bitmap0 = self.add_bitmap('bitmap0', 'drive0')
>> -        self.hmp_io_writes('drive0', (('0xab', 0, 512),
>> -                                      ('0xfe', '16M', '256k'),
>> -                                      ('0x64', '32736k', '64k')))
>> -        bitmap1 = self.add_bitmap('bitmap1', 'drive0')
>> +
>> +        drive0 = self.drives[0]
>> +        bitmap0 = self.add_bitmap('bitmap0', self.drives[0])
>> +        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
>> +                                          ('0xfe', '16M', '256k'),
>> +                                          ('0x64', '32736k', '64k')))
>> +        bitmap1 = self.add_bitmap('bitmap1', drive0)
>>           result = self.vm.qmp('transaction', actions=[
>>               {
>>                   'type': 'block-dirty-bitmap-clear',
>> -                'data': { 'node': 'drive0',
>> -                          'name': 'bitmap0' },
>> +                'data': { 'node': bitmap0.drive['id'],
>> +                          'name': bitmap0.name },
>>               },
>>               {
>>                   'type': 'block-dirty-bitmap-clear',
>> -                'data': { 'node': 'drive0',
>> -                          'name': 'bitmap1' },
>> +                'data': { 'node': bitmap1.drive['id'],
>> +                          'name': bitmap1.name },
>>               },
>>               {
>>                   'type': 'drive-backup',
>> -                'data': { 'device': 'drive0',
>> +                'data': { 'device': drive0['id'],
>>                             'sync': 'full',
>> -                          'format': iotests.imgfmt,
>> -                          'target': self.full_bak },
>> +                          'format': drive0['fmt'],
>> +                          'target': drive0['backup'] },
>>               }
>>           ])
>>           self.assert_qmp(result, 'return', {})
>>           self.wait_until_completed()
>> -        self.files.append(self.full_bak)
>> +        self.files.append(drive0['backup'])
>>           self.check_full_backup()
>> -        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
>> -                                      ('0x55', '8M', '352k'),
>> -                                      ('0x78', '15872k', '1M')))
>> +        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
>> +                                          ('0x55', '8M', '352k'),
>> +                                          ('0x78', '15872k', '1M')))
>>           # Both bitmaps should be in sync and create fully valid
>>           # incremental backups
>>           res1 = self.create_incremental(bitmap0)
>> @@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           afterwards and verify that the backup created is correct.
>>           '''
>> -        # Create a blkdebug interface to this img as 'drive1'
>> +        # Create a blkdebug interface to this img as 'drive1',
>> +        # but don't actually create a new image.
>> +        drive1 = self.add_node('drive1', self.drives[0]['fmt'],
>> +                               path=self.drives[0]['file'],
>> +                               backup=self.drives[0]['backup'])
>>           result = self.vm.qmp('blockdev-add', options={
>>               'id': 'drive1',
>
> "'id': drive1['id']" would work, too.
>
> Max
>

You're right! I missed one.

>> -            'driver': iotests.imgfmt,
>> +            'driver': drive1['fmt'],
>>               'file': {
>>                   'driver': 'blkdebug',
>>                   'image': {
>>                       'driver': 'file',
>> -                    'filename': self.test_img
>> +                    'filename': drive1['file']
>>                   },
>>                   'set-state': [{
>>                       'event': 'flush_to_disk',
>> @@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           })
>>           self.assert_qmp(result, 'return', {})
>> -        self.create_full_backup()
>> -        self.add_bitmap('bitmap0', 'drive1')
>> +        self.create_full_backup(self.drives[0])
>> +        self.add_bitmap('bitmap0', drive1)
>>           # Note: at this point, during a normal execution,
>>           # Assume that the VM resumes and begins issuing IO requests
>> here.
>> -        self.hmp_io_writes('drive1', (('0xab', 0, 512),
>> -                                      ('0xfe', '16M', '256k'),
>> -                                      ('0x64', '32736k', '64k')))
>> +        self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
>> +                                          ('0xfe', '16M', '256k'),
>> +                                          ('0x64', '32736k', '64k')))
>>           result = self.create_incremental(validate=False)
>>           self.assertFalse(result)
>> -        self.hmp_io_writes('drive1', (('0x9a', 0, 512),
>> -                                      ('0x55', '8M', '352k'),
>> -                                      ('0x78', '15872k', '1M')))
>> +        self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
>> +                                          ('0x55', '8M', '352k'),
>> +                                          ('0x78', '15872k', '1M')))
>>           self.create_incremental()
>>       def test_sync_dirty_bitmap_missing(self):
>>           self.assert_no_active_block_jobs()
>> -        self.files.append(self.foo_img)
>> -        result = self.vm.qmp('drive-backup', device='drive0',
>> -                             sync='dirty-bitmap', format=iotests.imgfmt,
>> -                             target=self.foo_img)
>> +        self.files.append(self.err_img)
>> +        result = self.vm.qmp('drive-backup',
>> device=self.drives[0]['id'],
>> +                             sync='dirty-bitmap',
>> format=self.drives[0]['fmt'],
>> +                             target=self.err_img)
>>           self.assert_qmp(result, 'error/class', 'GenericError')
>>       def test_sync_dirty_bitmap_not_found(self):
>>           self.assert_no_active_block_jobs()
>> -        self.files.append(self.foo_img)
>> -        result = self.vm.qmp('drive-backup', device='drive0',
>> +        self.files.append(self.err_img)
>> +        result = self.vm.qmp('drive-backup',
>> device=self.drives[0]['id'],
>>                                sync='dirty-bitmap', bitmap='unknown',
>> -                             format=iotests.imgfmt, target=self.foo_img)
>> +                             format=self.drives[0]['fmt'],
>> target=self.err_img)
>>           self.assert_qmp(result, 'error/class', 'GenericError')
>
>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring
  2015-03-17 20:50   ` Max Reitz
@ 2015-03-17 23:44     ` John Snow
  0 siblings, 0 replies; 43+ messages in thread
From: John Snow @ 2015-03-17 23:44 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/17/2015 04:50 PM, Max Reitz wrote:
> On 2015-03-04 at 23:15, John Snow wrote:
>> Allow tests to call just the backup preparation routine
>> without invoking a backup. Useful for transactions where
>> we want to prepare, but don't wish to issue the QMP command.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   tests/qemu-iotests/124 | 17 ++++++++++++-----
>>   1 file changed, 12 insertions(+), 5 deletions(-)
>>
>> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
>> index 2eccc3e..4afdca1 100644
>> --- a/tests/qemu-iotests/124
>> +++ b/tests/qemu-iotests/124
>> @@ -100,7 +100,6 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>       def img_create(self, img, fmt=iotests.imgfmt, size='64M',
>>                      parent=None, parentFormat=None):
>> -        plist = list()
>
> Hm. This appears to never have been used. Would it make sense to drop it
> from "iotests: add invalid input incremental backup tests" in your
> transactionless series?
>

Yes. (Poor kitty.)

>>           if parent:
>>               if parentFormat is None:
>>                   parentFormat = fmt
>> @@ -140,17 +139,25 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>           return bitmap
>> -    def create_incremental(self, bitmap=None, num=None,
>> -                           parent=None, parentFormat=None,
>> validate=True):
>> +    def prepare_backup(self, bitmap=None, parent=None):
>>           if bitmap is None:
>>               bitmap = self.bitmaps[-1]
>> -
>
> Removing this empty line looks like it should never have been added, too.
>

Also yes.

>>           if parent is None:
>>               parent = bitmap.last_target()
>> -        target = bitmap.new_target(num)
>> +        target = bitmap.new_target()
>>           self.img_create(target, bitmap.drive['fmt'], parent=parent)
>> +        return target
>> +
>> +    def create_incremental(self, bitmap=None, parent=None,
>> +                           parentFormat=None, validate=True):
>> +        if bitmap is None:
>> +            bitmap = self.bitmaps[-1]
>> +        if parent is None:
>> +            parent = bitmap.last_target()
>> +
>> +        target = self.prepare_backup(bitmap, parent)
>>           result = self.vm.qmp('drive-backup', device=bitmap.drive['id'],
>>                                sync='dirty-bitmap', bitmap=bitmap.name,
>>                                format=bitmap.drive['fmt'], target=target,
>
> Both "issues" are not caused by this patch, however, so whether you
> squash the hunks somewhere else or not:
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
>

Thanks!

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
  2015-03-17 22:46     ` John Snow
@ 2015-03-18 13:03       ` Max Reitz
  0 siblings, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-18 13:03 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-17 at 18:46, John Snow wrote:
>
>
> On 03/17/2015 02:44 PM, Max Reitz wrote:
>> On 2015-03-04 at 23:15, John Snow wrote:

[snip]

>>> +    }
>>> +}
>>> +
>>> +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
>>
>> I don't know whether I'm that content with the name chosen, because
>> you're actually decrementing the refcount of the successor; but since
>> the successor is basically a clone of the original bitmap (and I mean in
>> the Star Trek sense, that it's a teleported clone and the original is
>> intended to be destroyed so the successor can replace it), decrementing
>> the refcount of the successor basically is equal to decrementing the
>> refcount of the bitmap itself (as long as there is a successor, which
>> you are asserting; maybe you want to add a comment about that to
>> include/block/block.h, that one can only use this on frozen bitmaps?).
>>
>
> I could get clever with the name and call it 
> bdrv_frozen_bitmap_decref, which hopefully still shows membership to 
> the bdrv_dirty_bitmap class of functions but clarifies its usage 
> sufficiently.

Sounds good to me.

[snip]

>>> diff --git a/block/backup.c b/block/backup.c
>>> index 41bd9af..4332df4 100644
>>> --- a/block/backup.c
>>> +++ b/block/backup.c
>>> @@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void
>>> *opaque)
>>>       bdrv_unref(s->target);
>>> +    if (s->sync_bitmap) {
>>> +        BdrvDirtyBitmap *bm;
>>> +        bm = bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap,
>>> data->ret, NULL);
>>> +        assert(bm);
>>
>> You can use &error_abort as the Error object and drop the assert(); or,
>> if you are dropping the Error parameter, there is no need to check the
>> return value at all, because it will always be non-NULL (there won't be
>> any code path in the function returning NULL at all). Maybe you can even
>> drop the return value, too.
>>
>> I just looked through the series: Actually, you're never using the Error
>> parameter for bdrv_dirty_bitmap_decref() at all. Seems to me like you
>> really should drop it (and maybe the return value along with it).
>>
>
> I actually use this parameter to return the "new bitmap" (if any) 
> after the decrement operation. I wanted to leave the window open for 
> merge optimizations, so I tell the caller which bitmap remains after 
> the operation.
>
> I will cull the errp, but will likely leave the return code.

OK.

Max

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
  2015-03-17 23:27     ` John Snow
@ 2015-03-18 13:41       ` Max Reitz
  2015-03-18 19:51         ` John Snow
  0 siblings, 1 reply; 43+ messages in thread
From: Max Reitz @ 2015-03-18 13:41 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-17 at 19:27, John Snow wrote:
>
>
> On 03/17/2015 03:49 PM, Max Reitz wrote:
>> On 2015-03-04 at 23:15, John Snow wrote:
>>> This patch actually implements the transactional callback system
>>> for the drive_backup transaction.
>>>
>>> (1) We manually pick up a reference to the bitmap if present to allow
>>>      its cleanup to be delayed until after all drive_backup jobs 
>>> launched
>>>      by the transaction have fully completed.
>>>
>>> (2) We create a functional closure that envelops the original
>>> drive_backup
>>>      callback, to be able to intercept the completion status and
>>> return code
>>>      for the job.
>>>
>>> (3) We add the drive_backup_cb method for the drive_backup action, 
>>> which
>>>      unpacks the completion information and invokes the final cleanup.
>>>
>>> (4) backup_transaction_complete will perform the final cleanup on the
>>>      backup job.
>>>
>>> (5) In the case of transaction cancellation, drive_backup_cb is still
>>>      responsible for cleaning up the mess we may have already made.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>>   block/backup.c            |  9 +++++++
>>>   blockdev.c                | 64
>>> ++++++++++++++++++++++++++++++++++++++---------
>>>   include/block/block_int.h |  8 ++++++
>>>   3 files changed, 69 insertions(+), 12 deletions(-)
>>>
>>> diff --git a/block/backup.c b/block/backup.c
>>> index 4332df4..3673fb0 100644
>>> --- a/block/backup.c
>>> +++ b/block/backup.c
>>> @@ -233,6 +233,15 @@ typedef struct {
>>>       int ret;
>>>   } BackupCompleteData;
>>> +void backup_transaction_complete(BlockJob *job, int ret)
>>> +{
>>> +    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>>> +
>>> +    if (s->sync_bitmap) {
>>> +        bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, ret, NULL);
>>> +    }
>>> +}
>>> +
>>>   static void backup_complete(BlockJob *job, void *opaque)
>>>   {
>>>       BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>>> diff --git a/blockdev.c b/blockdev.c
>>> index 9e3b9e9..3ff14a7 100644
>>> --- a/blockdev.c
>>> +++ b/blockdev.c
>>> @@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque,
>>> int ret)
>>>   typedef void (CallbackFn)(void *opaque, int ret);
>>> -/* Temporary. Removed in the next patch. */
>>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>> -                                    void *opaque,
>>> -                                    void (*callback)(void *, int),
>>> -                                    void **new_opaque);
>>> -
>>> -void undo_transaction_wrapper(BlkTransactionState *common);
>>> -
>>>   /**
>>>    * Create a new transactional callback wrapper.
>>>    *
>>> @@ -1389,7 +1381,7 @@ void
>>> undo_transaction_wrapper(BlkTransactionState *common);
>>>    *
>>>    * @return The callback to be used instead of @callback.
>>>    */
>>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>> +static CallbackFn *new_transaction_wrapper(BlkTransactionState 
>>> *common,
>>>                                              void *opaque,
>>>                                              CallbackFn *callback,
>>>                                              void **new_opaque)
>>> @@ -1416,7 +1408,7 @@ CallbackFn
>>> *new_transaction_wrapper(BlkTransactionState *common,
>>>   /**
>>>    * Undo any actions performed by the above call.
>>>    */
>>> -void undo_transaction_wrapper(BlkTransactionState *common)
>>> +static void undo_transaction_wrapper(BlkTransactionState *common)
>>>   {
>>>       BlkTransactionList *btl = common->list;
>>>       BlkTransactionState *bts;
>>> @@ -1449,6 +1441,7 @@ void
>>> undo_transaction_wrapper(BlkTransactionState *common)
>>>       blk_put_transaction_state(common);
>>>   }
>>> +static void block_job_cb(void *opaque, int ret);
>>>   static void _drive_backup(const char *device, const char *target,
>>>                             bool has_format, const char *format,
>>>                             enum MirrorSyncMode sync,
>>> @@ -1767,6 +1760,9 @@ static void
>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>       BlockDriverState *bs;
>>>       DriveBackup *backup;
>>>       Error *local_err = NULL;
>>> +    CallbackFn *cb;
>>> +    void *opaque;
>>> +    BdrvDirtyBitmap *bmap = NULL;
>>>       assert(common->action->kind ==
>>> TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
>>>       backup = common->action->drive_backup;
>>> @@ -1777,6 +1773,19 @@ static void
>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>           return;
>>>       }
>>> +    /* BackupBlockJob is opaque to us, so look up the bitmap
>>> ourselves */
>>> +    if (backup->has_bitmap) {
>>> +        bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap);
>>> +        if (!bmap) {
>>> +            error_setg(errp, "Bitmap '%s' could not be found",
>>> backup->bitmap);
>>> +            return;
>>> +        }
>>> +    }
>>> +
>>> +    /* Create our transactional callback wrapper,
>>> +       and register that we'd like to call .cb() later. */
>>> +    cb = new_transaction_wrapper(common, bs, block_job_cb, &opaque);
>>> +
>>>       /* AioContext is released in .clean() */
>>>       state->aio_context = bdrv_get_aio_context(bs);
>>>       aio_context_acquire(state->aio_context);
>>> @@ -1789,7 +1798,7 @@ static void
>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>                     backup->has_bitmap, backup->bitmap,
>>>                     backup->has_on_source_error, 
>>> backup->on_source_error,
>>>                     backup->has_on_target_error, 
>>> backup->on_target_error,
>>> -                  NULL, NULL,
>>> +                  cb, opaque,
>>>                     &local_err);
>>>       if (local_err) {
>>>           error_propagate(errp, local_err);
>>> @@ -1798,6 +1807,11 @@ static void
>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>       state->bs = bs;
>>>       state->job = state->bs->job;
>>> +    /* Keep the job alive until .cb(), too. */
>>> +    block_job_incref(state->job);
>>> +    if (bmap) {
>>> +        bdrv_dirty_bitmap_incref(bmap);
>>> +    }
>>>   }
>>>   static void drive_backup_abort(BlkTransactionState *common)
>>> @@ -1809,6 +1823,10 @@ static void
>>> drive_backup_abort(BlkTransactionState *common)
>>>       if (bs && bs->job && bs->job == state->job) {
>>>           block_job_cancel_sync(bs->job);
>>>       }
>>> +
>>> +    /* Undo any callback actions we may have done. Putting down
>>> references
>>> +     * obtained during prepare() is handled by cb(). */
>>> +    undo_transaction_wrapper(common);
>>>   }
>>>   static void drive_backup_clean(BlkTransactionState *common)
>>> @@ -1820,6 +1838,27 @@ static void
>>> drive_backup_clean(BlkTransactionState *common)
>>>       }
>>>   }
>>> +static void drive_backup_cb(BlkTransactionState *common)
>>> +{
>>> +    BlkTransactionData *btd = common->opaque;
>>> +    BlockDriverState *bs = btd->opaque;
>>> +    DriveBackupState *state = DO_UPCAST(DriveBackupState, common,
>>> common);
>>> +
>>> +    assert(state->bs == bs);
>>> +    if (bs->job) {
>>> +        assert(state->job == bs->job);
>>> +    }
>>> +
>>> +    state->aio_context = bdrv_get_aio_context(bs);
>>> +    aio_context_acquire(state->aio_context);
>>> +
>>> +    /* Note: We also have the individual job's return code in
>>> btd->ret */
>>> +    backup_transaction_complete(state->job, common->list->status);
>>> +    block_job_decref(state->job);
>>> +
>>> +    aio_context_release(state->aio_context);
>>> +}
>>> +
>>>   typedef struct BlockdevBackupState {
>>>       BlkTransactionState common;
>>>       BlockDriverState *bs;
>>> @@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = {
>>>           .instance_size = sizeof(DriveBackupState),
>>>           .prepare = drive_backup_prepare,
>>>           .abort = drive_backup_abort,
>>> -        .clean = drive_backup_clean
>>> +        .clean = drive_backup_clean,
>>> +        .cb = drive_backup_cb
>>>       },
>>>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>>>           .instance_size = sizeof(BlockdevBackupState),
>>> diff --git a/include/block/block_int.h b/include/block/block_int.h
>>> index e0d5561..731684d 100644
>>> --- a/include/block/block_int.h
>>> +++ b/include/block/block_int.h
>>> @@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs,
>>> BlockDriverState *target,
>>>                     BlockCompletionFunc *cb, void *opaque,
>>>                     Error **errp);
>>> +/*
>>> + * backup_transaction_complete
>>> + * @job The BackupJob that was associated with a transaction
>>
>> s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no
>> structure named "BackupJob", but this looks like there might be one)
>>
>>> + * @ret Amalgamated return code for the entire transaction
>>
>> Hm. The call to this function you're introducing in this patch will
>> probably stay the only one so there won't be anyone who'll have to worry
>> about what this means, but if there was, they probably won't reach a
>> conclusive result.
>>
>> I know what it means because I've seen patch 3 (right now it means
>> "everything OR-ed together so it's 0 for success or some non-zero (maybe
>> positive, maybe negative, depending on whether you have an even or an
>> odd number of errors, and depending on whether the jobs return negative
>> values for errors or not) for error"), but I wouldn't be able to infer
>> it from this. At the least you should add that 0 means success and
>> everything else means error (if you take my suggestion for patch 3, it
>> would be 0 for success and -errno for error, where that error number is
>> one of the errors encountered).
>>
>> Other than that, looks good (as far as I can tell with my still limited
>> insights into patch 3).
>>
>> Max
>>
>
> It's my opinion that this patch should give you insight into patch #3, 
> instead of the other way around. This patch is useful for 
> demonstrating the general flow, because I kept all drive_backup 
> specific concerns strictly separated from patch #3.

It only truly helps me understand patch 3 under the assumption that this 
patch is correct. For reviewing, I cannot really take that assumption. ;-)

I mean, it does help me with the interfaces patch 3 offers, but it still 
doesn't help me with the objects introduced in patch 3, for instance, 
because those are all contained in patch 3 itself. And if I cannot 
verify patch 3 on its own, I cannot really verify that what this patch 
does is correct in regards to the interfaces offered there.

> I'll write a more comprehensive document for the docs/ folder soon, 
> but the general shape of the idea is "The cleanup actions defined in 
> the .cb() method will be executed after every job in the transactional 
> group has completed."

Right, again, assuming your implementation is correct, but questioning 
that is part of the natural reviewing process. :-)

> But there's some fine print:
>
> "'every job' refers only to jobs that have the capability to register 
> a post-transaction callback, which currently means only drive_backup."
>
> The general approach to this is, mechanically:
>
> (1) Extend the life of objects of interest with reference counts, 
> including Jobs, Bitmaps, and BlkTransactionStates.
>
> (2) "steal" the callback from a BlockJob and, when invoked, update our 
> management information for the transaction group in BlkTransactionList.
>
> (3) Once all jobs we have sent out return, execute the .cb() methods 
> as indicated in the BlkTransactionList.
>
>
>
> So, if you were adding a callback to a different QMP transaction:
> - Look at new_transaction_wrapper; you'll use this to bump up various 
> references used internally for this whole system, and it'll keep 
> qmp_transaction from being able to delete the list and state objects.
>
>   This function will give to you (as a gimmick) a new callback and 
> opaque data structure pointer for you to give to the job that you are 
> starting. I obfuscate this just a bit to make it clear that you should 
> be using this function every time to help manage the transactional state.
>
> - Now, the job will run on its own happy way, When it finishes, it 
> will call transaction_callback, which is the function that 
> "intercepts" the callbacks, and dispatches the original enveloped 
> callbacks. It ditches the original data we used to know how to call 
> the original callback, and begins storing completion information for 
> jobs instead.
>
> - transaction_callback will call blk_transaction_complete to store 
> completion info for one job. In turn, it calls 
> put_blk_transaction_list to decrement the pending jobs count (the 
> "jobs" refcount) and once that hits zero ...
>
> - All of the callback mechanisms associated with each 
> BlockTransactionState are run via blk_run_transaction_callbacks.
>
> - This is where drive_backup_cb is going to get invoked again, and 
> then we will splinter back out into backup-specific concerns, with 
> Jobs and Bitmaps and what not.

Yes, I can see the control flow; my main problem lies in the three 
different structs introduced in patch 3. I'll tell you why I'm confused 
by them:

First, there is BlkTransactionList. Sounds to me like it should be a 
list of transactions. Judging from its members, it is rather a list of 
operations ("actions") for a single transaction, however. Apparently, 
there is a double-use for the word "transaction": I'd consider a 
transaction to be the thing started by the QMP command which is a group 
of atomic operations. However, that one structure is called 
BlkTransactionState, but it apparently this is only the state of a 
single operation in the transaction. Pre-existing, but doesn't make it 
better. You added comments for its list entry members, with the first 
one stating "All transactions in the current group" and the second being 
a subset of that. However, again, I consider these to be operations and 
not transactions. The group is the transaction.

So we have that adding to my confusion. Furthermore, I think we don't 
need to list entry members (entry and list_entry) in 
BlkTransactionState. I think it should be fine to drop the 
snap_bdrv_states list in qmp_transaction() and just add all 
BlkTransactionStates to the BlkTransactionList (or however you name it 
if you rename it) in the preparation while () loop there. I don't think 
we need to add them only in blk_transaction_complete() (and we should 
drop that from there if it's been done in qmp_transaction() already, of 
course). Then we can really just use that list generally as *the* list 
of operations in a transaction.

Second, there is BlkTransactionData. From the name I cannot judge at all 
how it is supposed to differ from BlkTransactionState, because, well, it 
contains some data about a transaction... Maybe, because I consider 
BlkTransactionState to be misnamed, BlkTransactionData actually contains 
data about the transaction and not about a single operation? But from 
looking at the members, I have no idea. It has "opaque" and "ret"... I 
don't know how that is vital data to the transaction itself (and I don't 
think it is, it is rather data for the completion callback, I guess?), 
it only looks like some sort of pattern I have seen for AIO callbacks 
and the like.

Third, there is BlkTransactionWrapper. I suppose it wraps a 
transaction...? Or maybe at least an operation, because apparently I can 
no longer be sure whether a transaction is a transaction or an 
operation... But there is no transaction or even a single operation in 
there. It's just a structure describing a callback. I don't know how 
that is a "transaction wrapper"...

So, I don't understand any of the names given to the objects, and while 
I might understand what they are used for by looking at them, I have a 
*really* hard time understanding the code that uses them, because if I 
don't stare at their definitions all the time, I will immediately forget 
what they contain and what they are used for because I don't understand 
the names at all (and there is nothing which explicitly tells me what 
they are used for either). The fact that all of them are starting with 
BlkTransaction makes mixing them up even easier.

> From the point of view of the "transaction action developer," the 
> interface to the new feature is found via the 
> "new_transaction_wrapper" function, But some care must be taken to 
> make sure this callback actually gets used once it has been requested. 
> The whole thing can be aborted with the "undo_transaction_wrapper" 
> function.
>
> All of the other functions that got added to blockdev.c in Patch #3 
> are there to assist these two "interface" functions in doing what they 
> claim to.
>
> Everything in patch 4 and 5 just adds more reference count flexibility 
> to things that are only of interest to drive_backup.
>
> Patch 6 allows us to inject our special callback wrapper into the 
> qmp_drive_backup function.
>
>
> Clear as mud?

Control flow and concept, clear. Implementation, still very muddy, sorry...

Max

> --js
>
>>> + */
>>> +void backup_transaction_complete(BlockJob *job, int ret);
>>> +
>>> +
>>>   void blk_dev_change_media_cb(BlockBackend *blk, bool load);
>>>   bool blk_dev_has_removable_media(BlockBackend *blk);
>>>   void blk_dev_eject_request(BlockBackend *blk, bool force);
>>
>>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
  2015-03-17 23:40     ` John Snow
@ 2015-03-18 13:44       ` Max Reitz
  0 siblings, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-18 13:44 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-17 at 19:40, John Snow wrote:
>
>
> On 03/17/2015 04:44 PM, Max Reitz wrote:
>> On 2015-03-04 at 23:15, John Snow wrote:
>>> The original test was not particularly good about keeping the
>>> relationships between bitmaps, drives, and images very explicit,
>>> so this patch adds an explicit 'drive' dict that is used to
>>> keep these relationships explicit.
>>>
>>> This is necessary in order to test two full backup chains
>>> simultaneously for two drives, which will happen in a forthcoming
>>> test that examines failure scenarios for incremental backups created
>>> for multiple drives in a single transaction.
>>>
>>> Highlights:
>>>
>>> - Each test case carries around a list of drives
>>> - Each bitmap now acknowledges the full backup belonging to the drive
>>>    as its "last target" if it hasn't made an incremental backup yet.
>>> - Most functions generally try to accept a drive argument instead of
>>>    target or format arguments.
>>> - Filenames are now based on their formatting and id name.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>>   tests/qemu-iotests/124 | 212
>>> +++++++++++++++++++++++++++++--------------------
>>>   1 file changed, 126 insertions(+), 86 deletions(-)
>>
>> How about putting the previous patch in your transactionless series and
>> squashing this one into the respective patches there?
>>
>
> I don't want to startle the cat.
>
>
> I'd rather get the 'transactionless' checked in, then worry about 
> features that are important for transactions in the series that 
> introduces those features.
>
> If we keep thinking "Oh, but this function hasn't gone upstream in the 
> first place, so let's fix it there" in this series, then the first 
> series getting committed is going to wind up dependent on this series 
> getting reviewed as we keep changing things.
>
> Since this group of patches is more experimental, I'd really prefer to 
> keep it separate that way.
>
> Even if this patch is ugly. Sorry about that.

Well, you're targetting 2.4 with your other series anyway. I understand 
that it's been a couple of revisions already and you really want to get 
it merged by now, but this is a test, so if you just squash it in there, 
I don't think it will hold up that series much.

(A good rule for tests is: As long as they're passing and catch all 
desirable cases, nobody will really complain)

Up to you, but I guess you can see how this patch just looks like "I 
should have done it differently in the first place", and because that 
"first place" is still in flight, I'm wondering why you're not just 
fixing it there.

Max

>
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>>
>> Some remarks below, whether you follow them or not is up to you.
>>
>>> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
>>> index da0bf6d..2eccc3e 100644
>>> --- a/tests/qemu-iotests/124
>>> +++ b/tests/qemu-iotests/124
>>> @@ -29,14 +29,18 @@ def io_write_patterns(img, patterns):
>>>           iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
>>>   class Bitmap:
>>> -    def __init__(self, name, node):
>>> +    def __init__(self, name, drive):
>>>           self.name = name
>>> -        self.node = node
>>> +        self.drive = drive
>>>           self.pattern = os.path.join(iotests.test_dir.replace('%',
>>> '%%'),
>>> -                                    '%s.backup.%%i.img' % name)
>>> +                                    '%s.%s.backup.%%i.img' %
>>> (drive['id'],
>>> + name))
>>>           self.num = 0
>>>           self.backups = list()
>>> +    def base_target(self):
>>> +        return self.drive['backup']
>>> +
>>>       def new_target(self, num=None):
>>>           if num is None:
>>>               num = self.num
>>> @@ -46,7 +50,9 @@ class Bitmap:
>>>           return target
>>>       def last_target(self):
>>> -        return self.backups[-1]
>>> +        if self.backups:
>>> +            return self.backups[-1]
>>> +        return self.base_target()
>>>       def del_target(self):
>>>           os.remove(self.backups.pop())
>>> @@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>       def setUp(self):
>>>           self.bitmaps = list()
>>>           self.files = list()
>>> +        self.drives = list()
>>>           self.vm = iotests.VM()
>>> -        self.test_img = os.path.join(iotests.test_dir, 'base.img')
>>> -        self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
>>> -        self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
>>> -        self.img_create(self.test_img, iotests.imgfmt)
>>> -        self.vm.add_drive(self.test_img)
>>> +        self.err_img = os.path.join(iotests.test_dir, 'err.%s' %
>>> iotests.imgfmt)
>>
>> Nice. *g*
>>
>> (using the format name as the file extension)
>>
>> ((even though you're not doing it in __init__))
>>
>
> Can't please everyone. :)
>
>>> +
>>>           # Create a base image with a distinctive patterning
>>> -        io_write_patterns(self.test_img, (('0x41', 0, 512),
>>> -                                          ('0xd5', '1M', '32k'),
>>> -                                          ('0xdc', '32M', '124k')))
>>> +        drive0 = self.add_node('drive0')
>>> +        self.img_create(drive0['file'], drive0['fmt'])
>>> +        self.vm.add_drive(drive0['file'])
>>> +        io_write_patterns(drive0['file'], (('0x41', 0, 512),
>>> +                                           ('0xd5', '1M', '32k'),
>>> +                                           ('0xdc', '32M', '124k')))
>>>           self.vm.launch()
>>> +    def add_node(self, node_id, fmt=iotests.imgfmt, path=None,
>>> backup=None):
>>> +        if path is None:
>>> +            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id,
>>> fmt))
>>> +        if backup is None:
>>> +            backup = os.path.join(iotests.test_dir,
>>> +                                  '%s.full.backup.%s' % (node_id, 
>>> fmt))
>>> +
>>> +        self.drives.append({
>>> +            'id': node_id,
>>> +            'file': path,
>>> +            'backup': backup,
>>> +            'fmt': fmt })
>>> +        return self.drives[-1]
>>> +
>>> +
>>>       def img_create(self, img, fmt=iotests.imgfmt, size='64M',
>>>                      parent=None, parentFormat=None):
>>>           plist = list()
>>> @@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>           self.files.append(img)
>>> -    def create_full_backup(self, drive='drive0'):
>>> -        res = self.vm.qmp('drive-backup', device=drive,
>>> -                          sync='full', format=iotests.imgfmt,
>>> -                          target=self.full_bak)
>>> +    def create_full_backup(self, drive=None):
>>> +        if drive is None:
>>> +            drive = self.drives[-1]
>>> +
>>> +        res = self.vm.qmp('drive-backup', device=drive['id'],
>>> +                          sync='full', format=drive['fmt'],
>>> +                          target=drive['backup'])
>>>           self.assert_qmp(res, 'return', {})
>>> -        self.wait_until_completed(drive)
>>> -        self.check_full_backup()
>>> -        self.files.append(self.full_bak)
>>> +        self.wait_until_completed(drive['id'])
>>> +        self.check_full_backup(drive)
>>> +        self.files.append(drive['backup'])
>>> +        return drive['backup']
>>> -    def check_full_backup(self):
>>> -        self.assertTrue(iotests.compare_images(self.test_img,
>>> self.full_bak))
>>> +    def check_full_backup(self, drive=None):
>>> +        if drive is None:
>>> +            drive = self.drives[-1]
>>> +        self.assertTrue(iotests.compare_images(drive['file'],
>>> drive['backup']))
>>> -    def add_bitmap(self, name, node='drive0'):
>>> -        bitmap = Bitmap(name, node)
>>> +    def add_bitmap(self, name, drive):
>>> +        bitmap = Bitmap(name, drive)
>>>           self.bitmaps.append(bitmap)
>>> -        result = self.vm.qmp('block-dirty-bitmap-add', 
>>> node=bitmap.node,
>>> -                             name=bitmap.name)
>>> +        result = self.vm.qmp('block-dirty-bitmap-add', 
>>> node=drive['id'],
>>> +                             name=name)
>>
>> No real need to replace bitmap.name by name here, but no harm in 
>> doing it.
>>
>>>           self.assert_qmp(result, 'return', {})
>>>           return bitmap
>>> @@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>           if bitmap is None:
>>>               bitmap = self.bitmaps[-1]
>>> -        # If this is the first incremental backup for a bitmap,
>>> -        # use the full backup as a backing image. Otherwise, use
>>> -        # the last incremental backup.
>>>           if parent is None:
>>> -            if bitmap.num == 0:
>>> -                parent = self.full_bak
>>> -            else:
>>> -                parent = bitmap.last_target()
>>> +            parent = bitmap.last_target()
>>>           target = bitmap.new_target(num)
>>> -        self.img_create(target, iotests.imgfmt, parent=parent)
>>> +        self.img_create(target, bitmap.drive['fmt'], parent=parent)
>>> -        result = self.vm.qmp('drive-backup', device=bitmap.node,
>>> +        result = self.vm.qmp('drive-backup', 
>>> device=bitmap.drive['id'],
>>>                                sync='dirty-bitmap', bitmap=bitmap.name,
>>> -                             format=iotests.imgfmt, target=target,
>>> +                             format=bitmap.drive['fmt'], 
>>> target=target,
>>>                                mode='existing')
>>>           self.assert_qmp(result, 'return', {})
>>> -        event = self.wait_until_completed(bitmap.node,
>>> check_offset=validate,
>>> -                                          allow_failures=(not 
>>> validate))
>>> -        if 'error' in event['data']:
>>> +        return self.wait_incremental(bitmap, validate)
>>> +
>>> +
>>> +    def wait_incremental(self, bitmap=None,
>>> +                         validate=True, error='Input/output error'):
>>> +        if bitmap is None:
>>> +            bitmap = self.bitmaps[-1]
>>> +
>>> +        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
>>> +                                   match={'data': {'device':
>>> + bitmap.drive['id']}})
>>> +        if validate:
>>> +            self.assert_qmp_absent(event, 'data/error')
>>> +            return self.check_incremental(bitmap)
>>> +        else:
>>> +            self.assert_qmp(event, 'data/error', error)
>>>               bitmap.del_target()
>>>               return False
>>> -        if validate:
>>> -            return self.check_incremental(target)
>>> -    def check_incremental(self, target=None):
>>> -        if target is None:
>>> -            target = self.bitmaps[-1].last_target()
>>> -        self.assertTrue(iotests.compare_images(self.test_img, target))
>>> +    def check_incremental(self, bitmap=None):
>>> +        if bitmap is None:
>>> +            bitmap = self.bitmaps[-1]
>>> + self.assertTrue(iotests.compare_images(bitmap.drive['file'],
>>> + bitmap.last_target()))
>>>           return True
>>> @@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>           i.e.; after IO requests begin modifying the drive.
>>>           '''
>>>           self.create_full_backup()
>>> -        self.add_bitmap('bitmap0', 'drive0')
>>> +        self.add_bitmap('bitmap0', self.drives[0])
>>>           # Sanity: Create a "hollow" incremental backup
>>>           self.create_incremental()
>>>           # Three writes: One complete overwrite, one new segment,
>>>           # and one partial overlap.
>>> -        self.hmp_io_writes('drive0', (('0xab', 0, 512),
>>> -                                      ('0xfe', '16M', '256k'),
>>> -                                      ('0x64', '32736k', '64k')))
>>> +        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
>>> +                                                  ('0xfe', '16M',
>>> '256k'),
>>> +                                                  ('0x64', '32736k',
>>> '64k')))
>>>           self.create_incremental()
>>>           # Three more writes, one of each kind, like above
>>> -        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
>>> -                                      ('0x55', '8M', '352k'),
>>> -                                      ('0x78', '15872k', '1M')))
>>> +        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
>>> +                                                  ('0x55', '8M',
>>> '352k'),
>>> +                                                  ('0x78', '15872k',
>>> '1M')))
>>>           self.create_incremental()
>>>           return True
>>> @@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>           bitmap AFTER writes have already occurred. Use transactions
>>> to create
>>>           a full backup and synchronize both bitmaps to this backup.
>>>           Create an incremental backup through both bitmaps and verify
>>> that
>>> -        both backups match the full backup.
>>> +        both backups match the current drive0 image.
>>>           '''
>>> -        bitmap0 = self.add_bitmap('bitmap0', 'drive0')
>>> -        self.hmp_io_writes('drive0', (('0xab', 0, 512),
>>> -                                      ('0xfe', '16M', '256k'),
>>> -                                      ('0x64', '32736k', '64k')))
>>> -        bitmap1 = self.add_bitmap('bitmap1', 'drive0')
>>> +
>>> +        drive0 = self.drives[0]
>>> +        bitmap0 = self.add_bitmap('bitmap0', self.drives[0])
>>> +        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
>>> +                                          ('0xfe', '16M', '256k'),
>>> +                                          ('0x64', '32736k', '64k')))
>>> +        bitmap1 = self.add_bitmap('bitmap1', drive0)
>>>           result = self.vm.qmp('transaction', actions=[
>>>               {
>>>                   'type': 'block-dirty-bitmap-clear',
>>> -                'data': { 'node': 'drive0',
>>> -                          'name': 'bitmap0' },
>>> +                'data': { 'node': bitmap0.drive['id'],
>>> +                          'name': bitmap0.name },
>>>               },
>>>               {
>>>                   'type': 'block-dirty-bitmap-clear',
>>> -                'data': { 'node': 'drive0',
>>> -                          'name': 'bitmap1' },
>>> +                'data': { 'node': bitmap1.drive['id'],
>>> +                          'name': bitmap1.name },
>>>               },
>>>               {
>>>                   'type': 'drive-backup',
>>> -                'data': { 'device': 'drive0',
>>> +                'data': { 'device': drive0['id'],
>>>                             'sync': 'full',
>>> -                          'format': iotests.imgfmt,
>>> -                          'target': self.full_bak },
>>> +                          'format': drive0['fmt'],
>>> +                          'target': drive0['backup'] },
>>>               }
>>>           ])
>>>           self.assert_qmp(result, 'return', {})
>>>           self.wait_until_completed()
>>> -        self.files.append(self.full_bak)
>>> +        self.files.append(drive0['backup'])
>>>           self.check_full_backup()
>>> -        self.hmp_io_writes('drive0', (('0x9a', 0, 512),
>>> -                                      ('0x55', '8M', '352k'),
>>> -                                      ('0x78', '15872k', '1M')))
>>> +        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
>>> +                                          ('0x55', '8M', '352k'),
>>> +                                          ('0x78', '15872k', '1M')))
>>>           # Both bitmaps should be in sync and create fully valid
>>>           # incremental backups
>>>           res1 = self.create_incremental(bitmap0)
>>> @@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>           afterwards and verify that the backup created is correct.
>>>           '''
>>> -        # Create a blkdebug interface to this img as 'drive1'
>>> +        # Create a blkdebug interface to this img as 'drive1',
>>> +        # but don't actually create a new image.
>>> +        drive1 = self.add_node('drive1', self.drives[0]['fmt'],
>>> +                               path=self.drives[0]['file'],
>>> + backup=self.drives[0]['backup'])
>>>           result = self.vm.qmp('blockdev-add', options={
>>>               'id': 'drive1',
>>
>> "'id': drive1['id']" would work, too.
>>
>> Max
>>
>
> You're right! I missed one.
>
>>> -            'driver': iotests.imgfmt,
>>> +            'driver': drive1['fmt'],
>>>               'file': {
>>>                   'driver': 'blkdebug',
>>>                   'image': {
>>>                       'driver': 'file',
>>> -                    'filename': self.test_img
>>> +                    'filename': drive1['file']
>>>                   },
>>>                   'set-state': [{
>>>                       'event': 'flush_to_disk',
>>> @@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>>           })
>>>           self.assert_qmp(result, 'return', {})
>>> -        self.create_full_backup()
>>> -        self.add_bitmap('bitmap0', 'drive1')
>>> +        self.create_full_backup(self.drives[0])
>>> +        self.add_bitmap('bitmap0', drive1)
>>>           # Note: at this point, during a normal execution,
>>>           # Assume that the VM resumes and begins issuing IO requests
>>> here.
>>> -        self.hmp_io_writes('drive1', (('0xab', 0, 512),
>>> -                                      ('0xfe', '16M', '256k'),
>>> -                                      ('0x64', '32736k', '64k')))
>>> +        self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
>>> +                                          ('0xfe', '16M', '256k'),
>>> +                                          ('0x64', '32736k', '64k')))
>>>           result = self.create_incremental(validate=False)
>>>           self.assertFalse(result)
>>> -        self.hmp_io_writes('drive1', (('0x9a', 0, 512),
>>> -                                      ('0x55', '8M', '352k'),
>>> -                                      ('0x78', '15872k', '1M')))
>>> +        self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
>>> +                                          ('0x55', '8M', '352k'),
>>> +                                          ('0x78', '15872k', '1M')))
>>>           self.create_incremental()
>>>       def test_sync_dirty_bitmap_missing(self):
>>>           self.assert_no_active_block_jobs()
>>> -        self.files.append(self.foo_img)
>>> -        result = self.vm.qmp('drive-backup', device='drive0',
>>> -                             sync='dirty-bitmap', 
>>> format=iotests.imgfmt,
>>> -                             target=self.foo_img)
>>> +        self.files.append(self.err_img)
>>> +        result = self.vm.qmp('drive-backup',
>>> device=self.drives[0]['id'],
>>> +                             sync='dirty-bitmap',
>>> format=self.drives[0]['fmt'],
>>> +                             target=self.err_img)
>>>           self.assert_qmp(result, 'error/class', 'GenericError')
>>>       def test_sync_dirty_bitmap_not_found(self):
>>>           self.assert_no_active_block_jobs()
>>> -        self.files.append(self.foo_img)
>>> -        result = self.vm.qmp('drive-backup', device='drive0',
>>> +        self.files.append(self.err_img)
>>> +        result = self.vm.qmp('drive-backup',
>>> device=self.drives[0]['id'],
>>>                                sync='dirty-bitmap', bitmap='unknown',
>>> -                             format=iotests.imgfmt, 
>>> target=self.foo_img)
>>> +                             format=self.drives[0]['fmt'],
>>> target=self.err_img)
>>>           self.assert_qmp(result, 'error/class', 'GenericError')
>>
>>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
  2015-03-18 13:41       ` Max Reitz
@ 2015-03-18 19:51         ` John Snow
  2015-03-18 20:20           ` Max Reitz
  0 siblings, 1 reply; 43+ messages in thread
From: John Snow @ 2015-03-18 19:51 UTC (permalink / raw)
  To: Max Reitz, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha



On 03/18/2015 09:41 AM, Max Reitz wrote:
> On 2015-03-17 at 19:27, John Snow wrote:
>>
>>
>> On 03/17/2015 03:49 PM, Max Reitz wrote:
>>> On 2015-03-04 at 23:15, John Snow wrote:
>>>> This patch actually implements the transactional callback system
>>>> for the drive_backup transaction.
>>>>
>>>> (1) We manually pick up a reference to the bitmap if present to allow
>>>>      its cleanup to be delayed until after all drive_backup jobs
>>>> launched
>>>>      by the transaction have fully completed.
>>>>
>>>> (2) We create a functional closure that envelops the original
>>>> drive_backup
>>>>      callback, to be able to intercept the completion status and
>>>> return code
>>>>      for the job.
>>>>
>>>> (3) We add the drive_backup_cb method for the drive_backup action,
>>>> which
>>>>      unpacks the completion information and invokes the final cleanup.
>>>>
>>>> (4) backup_transaction_complete will perform the final cleanup on the
>>>>      backup job.
>>>>
>>>> (5) In the case of transaction cancellation, drive_backup_cb is still
>>>>      responsible for cleaning up the mess we may have already made.
>>>>
>>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>>> ---
>>>>   block/backup.c            |  9 +++++++
>>>>   blockdev.c                | 64
>>>> ++++++++++++++++++++++++++++++++++++++---------
>>>>   include/block/block_int.h |  8 ++++++
>>>>   3 files changed, 69 insertions(+), 12 deletions(-)
>>>>
>>>> diff --git a/block/backup.c b/block/backup.c
>>>> index 4332df4..3673fb0 100644
>>>> --- a/block/backup.c
>>>> +++ b/block/backup.c
>>>> @@ -233,6 +233,15 @@ typedef struct {
>>>>       int ret;
>>>>   } BackupCompleteData;
>>>> +void backup_transaction_complete(BlockJob *job, int ret)
>>>> +{
>>>> +    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>>>> +
>>>> +    if (s->sync_bitmap) {
>>>> +        bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, ret, NULL);
>>>> +    }
>>>> +}
>>>> +
>>>>   static void backup_complete(BlockJob *job, void *opaque)
>>>>   {
>>>>       BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>>>> diff --git a/blockdev.c b/blockdev.c
>>>> index 9e3b9e9..3ff14a7 100644
>>>> --- a/blockdev.c
>>>> +++ b/blockdev.c
>>>> @@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque,
>>>> int ret)
>>>>   typedef void (CallbackFn)(void *opaque, int ret);
>>>> -/* Temporary. Removed in the next patch. */
>>>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>>> -                                    void *opaque,
>>>> -                                    void (*callback)(void *, int),
>>>> -                                    void **new_opaque);
>>>> -
>>>> -void undo_transaction_wrapper(BlkTransactionState *common);
>>>> -
>>>>   /**
>>>>    * Create a new transactional callback wrapper.
>>>>    *
>>>> @@ -1389,7 +1381,7 @@ void
>>>> undo_transaction_wrapper(BlkTransactionState *common);
>>>>    *
>>>>    * @return The callback to be used instead of @callback.
>>>>    */
>>>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>>> +static CallbackFn *new_transaction_wrapper(BlkTransactionState
>>>> *common,
>>>>                                              void *opaque,
>>>>                                              CallbackFn *callback,
>>>>                                              void **new_opaque)
>>>> @@ -1416,7 +1408,7 @@ CallbackFn
>>>> *new_transaction_wrapper(BlkTransactionState *common,
>>>>   /**
>>>>    * Undo any actions performed by the above call.
>>>>    */
>>>> -void undo_transaction_wrapper(BlkTransactionState *common)
>>>> +static void undo_transaction_wrapper(BlkTransactionState *common)
>>>>   {
>>>>       BlkTransactionList *btl = common->list;
>>>>       BlkTransactionState *bts;
>>>> @@ -1449,6 +1441,7 @@ void
>>>> undo_transaction_wrapper(BlkTransactionState *common)
>>>>       blk_put_transaction_state(common);
>>>>   }
>>>> +static void block_job_cb(void *opaque, int ret);
>>>>   static void _drive_backup(const char *device, const char *target,
>>>>                             bool has_format, const char *format,
>>>>                             enum MirrorSyncMode sync,
>>>> @@ -1767,6 +1760,9 @@ static void
>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>       BlockDriverState *bs;
>>>>       DriveBackup *backup;
>>>>       Error *local_err = NULL;
>>>> +    CallbackFn *cb;
>>>> +    void *opaque;
>>>> +    BdrvDirtyBitmap *bmap = NULL;
>>>>       assert(common->action->kind ==
>>>> TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
>>>>       backup = common->action->drive_backup;
>>>> @@ -1777,6 +1773,19 @@ static void
>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>           return;
>>>>       }
>>>> +    /* BackupBlockJob is opaque to us, so look up the bitmap
>>>> ourselves */
>>>> +    if (backup->has_bitmap) {
>>>> +        bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap);
>>>> +        if (!bmap) {
>>>> +            error_setg(errp, "Bitmap '%s' could not be found",
>>>> backup->bitmap);
>>>> +            return;
>>>> +        }
>>>> +    }
>>>> +
>>>> +    /* Create our transactional callback wrapper,
>>>> +       and register that we'd like to call .cb() later. */
>>>> +    cb = new_transaction_wrapper(common, bs, block_job_cb, &opaque);
>>>> +
>>>>       /* AioContext is released in .clean() */
>>>>       state->aio_context = bdrv_get_aio_context(bs);
>>>>       aio_context_acquire(state->aio_context);
>>>> @@ -1789,7 +1798,7 @@ static void
>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>                     backup->has_bitmap, backup->bitmap,
>>>>                     backup->has_on_source_error,
>>>> backup->on_source_error,
>>>>                     backup->has_on_target_error,
>>>> backup->on_target_error,
>>>> -                  NULL, NULL,
>>>> +                  cb, opaque,
>>>>                     &local_err);
>>>>       if (local_err) {
>>>>           error_propagate(errp, local_err);
>>>> @@ -1798,6 +1807,11 @@ static void
>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>       state->bs = bs;
>>>>       state->job = state->bs->job;
>>>> +    /* Keep the job alive until .cb(), too. */
>>>> +    block_job_incref(state->job);
>>>> +    if (bmap) {
>>>> +        bdrv_dirty_bitmap_incref(bmap);
>>>> +    }
>>>>   }
>>>>   static void drive_backup_abort(BlkTransactionState *common)
>>>> @@ -1809,6 +1823,10 @@ static void
>>>> drive_backup_abort(BlkTransactionState *common)
>>>>       if (bs && bs->job && bs->job == state->job) {
>>>>           block_job_cancel_sync(bs->job);
>>>>       }
>>>> +
>>>> +    /* Undo any callback actions we may have done. Putting down
>>>> references
>>>> +     * obtained during prepare() is handled by cb(). */
>>>> +    undo_transaction_wrapper(common);
>>>>   }
>>>>   static void drive_backup_clean(BlkTransactionState *common)
>>>> @@ -1820,6 +1838,27 @@ static void
>>>> drive_backup_clean(BlkTransactionState *common)
>>>>       }
>>>>   }
>>>> +static void drive_backup_cb(BlkTransactionState *common)
>>>> +{
>>>> +    BlkTransactionData *btd = common->opaque;
>>>> +    BlockDriverState *bs = btd->opaque;
>>>> +    DriveBackupState *state = DO_UPCAST(DriveBackupState, common,
>>>> common);
>>>> +
>>>> +    assert(state->bs == bs);
>>>> +    if (bs->job) {
>>>> +        assert(state->job == bs->job);
>>>> +    }
>>>> +
>>>> +    state->aio_context = bdrv_get_aio_context(bs);
>>>> +    aio_context_acquire(state->aio_context);
>>>> +
>>>> +    /* Note: We also have the individual job's return code in
>>>> btd->ret */
>>>> +    backup_transaction_complete(state->job, common->list->status);
>>>> +    block_job_decref(state->job);
>>>> +
>>>> +    aio_context_release(state->aio_context);
>>>> +}
>>>> +
>>>>   typedef struct BlockdevBackupState {
>>>>       BlkTransactionState common;
>>>>       BlockDriverState *bs;
>>>> @@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = {
>>>>           .instance_size = sizeof(DriveBackupState),
>>>>           .prepare = drive_backup_prepare,
>>>>           .abort = drive_backup_abort,
>>>> -        .clean = drive_backup_clean
>>>> +        .clean = drive_backup_clean,
>>>> +        .cb = drive_backup_cb
>>>>       },
>>>>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>>>>           .instance_size = sizeof(BlockdevBackupState),
>>>> diff --git a/include/block/block_int.h b/include/block/block_int.h
>>>> index e0d5561..731684d 100644
>>>> --- a/include/block/block_int.h
>>>> +++ b/include/block/block_int.h
>>>> @@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs,
>>>> BlockDriverState *target,
>>>>                     BlockCompletionFunc *cb, void *opaque,
>>>>                     Error **errp);
>>>> +/*
>>>> + * backup_transaction_complete
>>>> + * @job The BackupJob that was associated with a transaction
>>>
>>> s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no
>>> structure named "BackupJob", but this looks like there might be one)
>>>
>>>> + * @ret Amalgamated return code for the entire transaction
>>>
>>> Hm. The call to this function you're introducing in this patch will
>>> probably stay the only one so there won't be anyone who'll have to worry
>>> about what this means, but if there was, they probably won't reach a
>>> conclusive result.
>>>
>>> I know what it means because I've seen patch 3 (right now it means
>>> "everything OR-ed together so it's 0 for success or some non-zero (maybe
>>> positive, maybe negative, depending on whether you have an even or an
>>> odd number of errors, and depending on whether the jobs return negative
>>> values for errors or not) for error"), but I wouldn't be able to infer
>>> it from this. At the least you should add that 0 means success and
>>> everything else means error (if you take my suggestion for patch 3, it
>>> would be 0 for success and -errno for error, where that error number is
>>> one of the errors encountered).
>>>
>>> Other than that, looks good (as far as I can tell with my still limited
>>> insights into patch 3).
>>>
>>> Max
>>>
>>
>> It's my opinion that this patch should give you insight into patch #3,
>> instead of the other way around. This patch is useful for
>> demonstrating the general flow, because I kept all drive_backup
>> specific concerns strictly separated from patch #3.
>
> It only truly helps me understand patch 3 under the assumption that this
> patch is correct. For reviewing, I cannot really take that assumption. ;-)
>
> I mean, it does help me with the interfaces patch 3 offers, but it still
> doesn't help me with the objects introduced in patch 3, for instance,
> because those are all contained in patch 3 itself. And if I cannot
> verify patch 3 on its own, I cannot really verify that what this patch
> does is correct in regards to the interfaces offered there.
>
>> I'll write a more comprehensive document for the docs/ folder soon,
>> but the general shape of the idea is "The cleanup actions defined in
>> the .cb() method will be executed after every job in the transactional
>> group has completed."
>
> Right, again, assuming your implementation is correct, but questioning
> that is part of the natural reviewing process. :-)
>

Yes, that's fine! I am just trying to help you unravel this strange loop 
(ha ha) and/or try to understand what is not necessarily clear with the 
information as presented.

>> But there's some fine print:
>>
>> "'every job' refers only to jobs that have the capability to register
>> a post-transaction callback, which currently means only drive_backup."
>>
>> The general approach to this is, mechanically:
>>
>> (1) Extend the life of objects of interest with reference counts,
>> including Jobs, Bitmaps, and BlkTransactionStates.
>>
>> (2) "steal" the callback from a BlockJob and, when invoked, update our
>> management information for the transaction group in BlkTransactionList.
>>
>> (3) Once all jobs we have sent out return, execute the .cb() methods
>> as indicated in the BlkTransactionList.
>>
>>
>>
>> So, if you were adding a callback to a different QMP transaction:
>> - Look at new_transaction_wrapper; you'll use this to bump up various
>> references used internally for this whole system, and it'll keep
>> qmp_transaction from being able to delete the list and state objects.
>>
>>   This function will give to you (as a gimmick) a new callback and
>> opaque data structure pointer for you to give to the job that you are
>> starting. I obfuscate this just a bit to make it clear that you should
>> be using this function every time to help manage the transactional state.
>>
>> - Now, the job will run on its own happy way, When it finishes, it
>> will call transaction_callback, which is the function that
>> "intercepts" the callbacks, and dispatches the original enveloped
>> callbacks. It ditches the original data we used to know how to call
>> the original callback, and begins storing completion information for
>> jobs instead.
>>
>> - transaction_callback will call blk_transaction_complete to store
>> completion info for one job. In turn, it calls
>> put_blk_transaction_list to decrement the pending jobs count (the
>> "jobs" refcount) and once that hits zero ...
>>
>> - All of the callback mechanisms associated with each
>> BlockTransactionState are run via blk_run_transaction_callbacks.
>>
>> - This is where drive_backup_cb is going to get invoked again, and
>> then we will splinter back out into backup-specific concerns, with
>> Jobs and Bitmaps and what not.
>
> Yes, I can see the control flow; my main problem lies in the three
> different structs introduced in patch 3. I'll tell you why I'm confused
> by them:
>

Paydirt!

> First, there is BlkTransactionList. Sounds to me like it should be a
> list of transactions. Judging from its members, it is rather a list of
> operations ("actions") for a single transaction, however. Apparently,
> there is a double-use for the word "transaction": I'd consider a
> transaction to be the thing started by the QMP command which is a group
> of atomic operations. However, that one structure is called
> BlkTransactionState, but it apparently this is only the state of a
> single operation in the transaction. Pre-existing, but doesn't make it
> better. You added comments for its list entry members, with the first
> one stating "All transactions in the current group" and the second being
> a subset of that. However, again, I consider these to be operations and
> not transactions. The group is the transaction.
>

Ah, yes. Good points. We need to decide on consistent terminology.

Transaction: The entire group.
Action: One particular command within a transaction group.
Action Op: One particular stage of one command (e.g. prepare, commit)

BlkTransactionState is an existing structure that does indeed refer 
instead to one particular action's state, not that of the entire 
transaction. This can be changed to be clearer. Better names might be 
BlkTransactionActionState or BlkActionState.

The BlkTransactionList here is indeed intended to be a list for the 
entire transaction, so it is actually a "list of actions" and not, 
perhaps as its name implies, a list of transactions. (We have no data 
structures with a scope this vast.)

The BlkTransactionList is an object that is created once per transaction 
group (once per qmp_transaction()) and keeps a count of how many jobs 
we've launched (and thus how many to expect to return to us.)

Once a job completes, it adds that *action's state* 
(BlkTransactionState) to its list. Once all jobs sent out have returned, 
it will iterate this list and execute their .cb() actions.

So perhaps an amendment would be:

typedef struct BlkTransactionList {
     int pending_jobs;
     int transaction_rc;
     QLIST(...) completed_actions;
} BlkTransactionList;

to make this part clearer.


> So we have that adding to my confusion. Furthermore, I think we don't
> need to list entry members (entry and list_entry) in
> BlkTransactionState. I think it should be fine to drop the
> snap_bdrv_states list in qmp_transaction() and just add all
> BlkTransactionStates to the BlkTransactionList (or however you name it
> if you rename it) in the preparation while () loop there. I don't think
> we need to add them only in blk_transaction_complete() (and we should
> drop that from there if it's been done in qmp_transaction() already, of
> course). Then we can really just use that list generally as *the* list
> of operations in a transaction.
>

Yes, there's not necessarily a strong reason to keep two lists.

One of the existing reasons, though, is that not all transactions launch 
jobs or have been modified to work with this system yet, so we 
definitely don't want to consider all of these entries in THIS system 
just yet.

So one of the simplifications two lists makes is that we don't have to 
worry about "unsupported actions" making it into the list. Everything in 
*my* list can be processed without further thought.

We can also just add more state into the BlkTransactionState (the 
ActionState) to help us distinguish, and then we can get rid of both lists.

A state machine tag or something might be useful in that way.

> Second, there is BlkTransactionData. From the name I cannot judge at all
> how it is supposed to differ from BlkTransactionState, because, well, it
> contains some data about a transaction... Maybe, because I consider
> BlkTransactionState to be misnamed, BlkTransactionData actually contains
> data about the transaction and not about a single operation? But from
> looking at the members, I have no idea. It has "opaque" and "ret"... I
> don't know how that is vital data to the transaction itself (and I don't
> think it is, it is rather data for the completion callback, I guess?),
> it only looks like some sort of pattern I have seen for AIO callbacks
> and the like.
>

BlkTransactionData was meant to contain completion info about a "job" 
that was launched by an action. I tried to avoid tying it explicitly to 
a BlockJob -- it can be any action with a callback, really.

Anyway, this structure contains "Data that was delivered to the original 
callback." In our case here, it's data that would've gone to 
block_job_complete.

So it contains an opaque pointer (A BDS, we secretly know) and a return 
code for that individual action's task.

> Third, there is BlkTransactionWrapper. I suppose it wraps a
> transaction...? Or maybe at least an operation, because apparently I can
> no longer be sure whether a transaction is a transaction or an
> operation... But there is no transaction or even a single operation in
> there. It's just a structure describing a callback. I don't know how
> that is a "transaction wrapper"...
>

We have achieved maximum Strawberry.

The BlkTransactionWrapper was meant to wrap any data necessary to be 
able to deliver the original callback we're stealing.

So in our case, it's for *one action's callback* and in this one 
instance, it's being used to hold the data needed to manually invoke the 
block_job_complete callback.

So both BTD and BTW here contain callback related information for one 
particular action.

They are differentiated by "Wrapper" having a "Before we execute the 
original callback" context, and "Data" having a "We've already executed 
the original callback" context.

Can they be combined into one structure? Yes, as long as we keep a tag 
that lets us know what state we're in.

Can they be inlined into "BlkTransactionState"? Yes -- but I opted to 
keep it in a separate glob to not clutter up the BlkTransactionState 
with values only useful for an optional feature.

Is that wise? Maybe, maybe not.

I'm open to names. The most semantically accurate would be 
BlkTransactionActionCallbackData if we combined BTD/BTW to be one 
structure. It's a mouthful, though.

> So, I don't understand any of the names given to the objects, and while
> I might understand what they are used for by looking at them, I have a
> *really* hard time understanding the code that uses them, because if I
> don't stare at their definitions all the time, I will immediately forget
> what they contain and what they are used for because I don't understand
> the names at all (and there is nothing which explicitly tells me what
> they are used for either). The fact that all of them are starting with
> BlkTransaction makes mixing them up even easier.
>

Understood.

>> From the point of view of the "transaction action developer," the
>> interface to the new feature is found via the
>> "new_transaction_wrapper" function, But some care must be taken to
>> make sure this callback actually gets used once it has been requested.
>> The whole thing can be aborted with the "undo_transaction_wrapper"
>> function.
>>
>> All of the other functions that got added to blockdev.c in Patch #3
>> are there to assist these two "interface" functions in doing what they
>> claim to.
>>
>> Everything in patch 4 and 5 just adds more reference count flexibility
>> to things that are only of interest to drive_backup.
>>
>> Patch 6 allows us to inject our special callback wrapper into the
>> qmp_drive_backup function.
>>
>>
>> Clear as mud?
>
> Control flow and concept, clear. Implementation, still very muddy, sorry...
>
> Max
>
>> --js
>>
>>>> + */
>>>> +void backup_transaction_complete(BlockJob *job, int ret);
>>>> +
>>>> +
>>>>   void blk_dev_change_media_cb(BlockBackend *blk, bool load);
>>>>   bool blk_dev_has_removable_media(BlockBackend *blk);
>>>>   void blk_dev_eject_request(BlockBackend *blk, bool force);
>>>
>>>
>

-- 
—js

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
  2015-03-18 19:51         ` John Snow
@ 2015-03-18 20:20           ` Max Reitz
  0 siblings, 0 replies; 43+ messages in thread
From: Max Reitz @ 2015-03-18 20:20 UTC (permalink / raw)
  To: John Snow, qemu-block
  Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha

On 2015-03-18 at 15:51, John Snow wrote:
>
>
> On 03/18/2015 09:41 AM, Max Reitz wrote:
>> On 2015-03-17 at 19:27, John Snow wrote:
>>>
>>>
>>> On 03/17/2015 03:49 PM, Max Reitz wrote:
>>>> On 2015-03-04 at 23:15, John Snow wrote:
>>>>> This patch actually implements the transactional callback system
>>>>> for the drive_backup transaction.
>>>>>
>>>>> (1) We manually pick up a reference to the bitmap if present to allow
>>>>>      its cleanup to be delayed until after all drive_backup jobs
>>>>> launched
>>>>>      by the transaction have fully completed.
>>>>>
>>>>> (2) We create a functional closure that envelops the original
>>>>> drive_backup
>>>>>      callback, to be able to intercept the completion status and
>>>>> return code
>>>>>      for the job.
>>>>>
>>>>> (3) We add the drive_backup_cb method for the drive_backup action,
>>>>> which
>>>>>      unpacks the completion information and invokes the final 
>>>>> cleanup.
>>>>>
>>>>> (4) backup_transaction_complete will perform the final cleanup on the
>>>>>      backup job.
>>>>>
>>>>> (5) In the case of transaction cancellation, drive_backup_cb is still
>>>>>      responsible for cleaning up the mess we may have already made.
>>>>>
>>>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>>>> ---
>>>>>   block/backup.c            |  9 +++++++
>>>>>   blockdev.c                | 64
>>>>> ++++++++++++++++++++++++++++++++++++++---------
>>>>>   include/block/block_int.h |  8 ++++++
>>>>>   3 files changed, 69 insertions(+), 12 deletions(-)
>>>>>
>>>>> diff --git a/block/backup.c b/block/backup.c
>>>>> index 4332df4..3673fb0 100644
>>>>> --- a/block/backup.c
>>>>> +++ b/block/backup.c
>>>>> @@ -233,6 +233,15 @@ typedef struct {
>>>>>       int ret;
>>>>>   } BackupCompleteData;
>>>>> +void backup_transaction_complete(BlockJob *job, int ret)
>>>>> +{
>>>>> +    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>>>>> +
>>>>> +    if (s->sync_bitmap) {
>>>>> +        bdrv_dirty_bitmap_decref(job->bs, s->sync_bitmap, ret, 
>>>>> NULL);
>>>>> +    }
>>>>> +}
>>>>> +
>>>>>   static void backup_complete(BlockJob *job, void *opaque)
>>>>>   {
>>>>>       BackupBlockJob *s = container_of(job, BackupBlockJob, common);
>>>>> diff --git a/blockdev.c b/blockdev.c
>>>>> index 9e3b9e9..3ff14a7 100644
>>>>> --- a/blockdev.c
>>>>> +++ b/blockdev.c
>>>>> @@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque,
>>>>> int ret)
>>>>>   typedef void (CallbackFn)(void *opaque, int ret);
>>>>> -/* Temporary. Removed in the next patch. */
>>>>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>>>> -                                    void *opaque,
>>>>> -                                    void (*callback)(void *, int),
>>>>> -                                    void **new_opaque);
>>>>> -
>>>>> -void undo_transaction_wrapper(BlkTransactionState *common);
>>>>> -
>>>>>   /**
>>>>>    * Create a new transactional callback wrapper.
>>>>>    *
>>>>> @@ -1389,7 +1381,7 @@ void
>>>>> undo_transaction_wrapper(BlkTransactionState *common);
>>>>>    *
>>>>>    * @return The callback to be used instead of @callback.
>>>>>    */
>>>>> -CallbackFn *new_transaction_wrapper(BlkTransactionState *common,
>>>>> +static CallbackFn *new_transaction_wrapper(BlkTransactionState
>>>>> *common,
>>>>>                                              void *opaque,
>>>>>                                              CallbackFn *callback,
>>>>>                                              void **new_opaque)
>>>>> @@ -1416,7 +1408,7 @@ CallbackFn
>>>>> *new_transaction_wrapper(BlkTransactionState *common,
>>>>>   /**
>>>>>    * Undo any actions performed by the above call.
>>>>>    */
>>>>> -void undo_transaction_wrapper(BlkTransactionState *common)
>>>>> +static void undo_transaction_wrapper(BlkTransactionState *common)
>>>>>   {
>>>>>       BlkTransactionList *btl = common->list;
>>>>>       BlkTransactionState *bts;
>>>>> @@ -1449,6 +1441,7 @@ void
>>>>> undo_transaction_wrapper(BlkTransactionState *common)
>>>>>       blk_put_transaction_state(common);
>>>>>   }
>>>>> +static void block_job_cb(void *opaque, int ret);
>>>>>   static void _drive_backup(const char *device, const char *target,
>>>>>                             bool has_format, const char *format,
>>>>>                             enum MirrorSyncMode sync,
>>>>> @@ -1767,6 +1760,9 @@ static void
>>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>>       BlockDriverState *bs;
>>>>>       DriveBackup *backup;
>>>>>       Error *local_err = NULL;
>>>>> +    CallbackFn *cb;
>>>>> +    void *opaque;
>>>>> +    BdrvDirtyBitmap *bmap = NULL;
>>>>>       assert(common->action->kind ==
>>>>> TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
>>>>>       backup = common->action->drive_backup;
>>>>> @@ -1777,6 +1773,19 @@ static void
>>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>>           return;
>>>>>       }
>>>>> +    /* BackupBlockJob is opaque to us, so look up the bitmap
>>>>> ourselves */
>>>>> +    if (backup->has_bitmap) {
>>>>> +        bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap);
>>>>> +        if (!bmap) {
>>>>> +            error_setg(errp, "Bitmap '%s' could not be found",
>>>>> backup->bitmap);
>>>>> +            return;
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    /* Create our transactional callback wrapper,
>>>>> +       and register that we'd like to call .cb() later. */
>>>>> +    cb = new_transaction_wrapper(common, bs, block_job_cb, &opaque);
>>>>> +
>>>>>       /* AioContext is released in .clean() */
>>>>>       state->aio_context = bdrv_get_aio_context(bs);
>>>>>       aio_context_acquire(state->aio_context);
>>>>> @@ -1789,7 +1798,7 @@ static void
>>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>>                     backup->has_bitmap, backup->bitmap,
>>>>>                     backup->has_on_source_error,
>>>>> backup->on_source_error,
>>>>>                     backup->has_on_target_error,
>>>>> backup->on_target_error,
>>>>> -                  NULL, NULL,
>>>>> +                  cb, opaque,
>>>>>                     &local_err);
>>>>>       if (local_err) {
>>>>>           error_propagate(errp, local_err);
>>>>> @@ -1798,6 +1807,11 @@ static void
>>>>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>>>>>       state->bs = bs;
>>>>>       state->job = state->bs->job;
>>>>> +    /* Keep the job alive until .cb(), too. */
>>>>> +    block_job_incref(state->job);
>>>>> +    if (bmap) {
>>>>> +        bdrv_dirty_bitmap_incref(bmap);
>>>>> +    }
>>>>>   }
>>>>>   static void drive_backup_abort(BlkTransactionState *common)
>>>>> @@ -1809,6 +1823,10 @@ static void
>>>>> drive_backup_abort(BlkTransactionState *common)
>>>>>       if (bs && bs->job && bs->job == state->job) {
>>>>>           block_job_cancel_sync(bs->job);
>>>>>       }
>>>>> +
>>>>> +    /* Undo any callback actions we may have done. Putting down
>>>>> references
>>>>> +     * obtained during prepare() is handled by cb(). */
>>>>> +    undo_transaction_wrapper(common);
>>>>>   }
>>>>>   static void drive_backup_clean(BlkTransactionState *common)
>>>>> @@ -1820,6 +1838,27 @@ static void
>>>>> drive_backup_clean(BlkTransactionState *common)
>>>>>       }
>>>>>   }
>>>>> +static void drive_backup_cb(BlkTransactionState *common)
>>>>> +{
>>>>> +    BlkTransactionData *btd = common->opaque;
>>>>> +    BlockDriverState *bs = btd->opaque;
>>>>> +    DriveBackupState *state = DO_UPCAST(DriveBackupState, common,
>>>>> common);
>>>>> +
>>>>> +    assert(state->bs == bs);
>>>>> +    if (bs->job) {
>>>>> +        assert(state->job == bs->job);
>>>>> +    }
>>>>> +
>>>>> +    state->aio_context = bdrv_get_aio_context(bs);
>>>>> +    aio_context_acquire(state->aio_context);
>>>>> +
>>>>> +    /* Note: We also have the individual job's return code in
>>>>> btd->ret */
>>>>> +    backup_transaction_complete(state->job, common->list->status);
>>>>> +    block_job_decref(state->job);
>>>>> +
>>>>> +    aio_context_release(state->aio_context);
>>>>> +}
>>>>> +
>>>>>   typedef struct BlockdevBackupState {
>>>>>       BlkTransactionState common;
>>>>>       BlockDriverState *bs;
>>>>> @@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = {
>>>>>           .instance_size = sizeof(DriveBackupState),
>>>>>           .prepare = drive_backup_prepare,
>>>>>           .abort = drive_backup_abort,
>>>>> -        .clean = drive_backup_clean
>>>>> +        .clean = drive_backup_clean,
>>>>> +        .cb = drive_backup_cb
>>>>>       },
>>>>>       [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
>>>>>           .instance_size = sizeof(BlockdevBackupState),
>>>>> diff --git a/include/block/block_int.h b/include/block/block_int.h
>>>>> index e0d5561..731684d 100644
>>>>> --- a/include/block/block_int.h
>>>>> +++ b/include/block/block_int.h
>>>>> @@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs,
>>>>> BlockDriverState *target,
>>>>>                     BlockCompletionFunc *cb, void *opaque,
>>>>>                     Error **errp);
>>>>> +/*
>>>>> + * backup_transaction_complete
>>>>> + * @job The BackupJob that was associated with a transaction
>>>>
>>>> s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no
>>>> structure named "BackupJob", but this looks like there might be one)
>>>>
>>>>> + * @ret Amalgamated return code for the entire transaction
>>>>
>>>> Hm. The call to this function you're introducing in this patch will
>>>> probably stay the only one so there won't be anyone who'll have to 
>>>> worry
>>>> about what this means, but if there was, they probably won't reach a
>>>> conclusive result.
>>>>
>>>> I know what it means because I've seen patch 3 (right now it means
>>>> "everything OR-ed together so it's 0 for success or some non-zero 
>>>> (maybe
>>>> positive, maybe negative, depending on whether you have an even or an
>>>> odd number of errors, and depending on whether the jobs return 
>>>> negative
>>>> values for errors or not) for error"), but I wouldn't be able to infer
>>>> it from this. At the least you should add that 0 means success and
>>>> everything else means error (if you take my suggestion for patch 3, it
>>>> would be 0 for success and -errno for error, where that error 
>>>> number is
>>>> one of the errors encountered).
>>>>
>>>> Other than that, looks good (as far as I can tell with my still 
>>>> limited
>>>> insights into patch 3).
>>>>
>>>> Max
>>>>
>>>
>>> It's my opinion that this patch should give you insight into patch #3,
>>> instead of the other way around. This patch is useful for
>>> demonstrating the general flow, because I kept all drive_backup
>>> specific concerns strictly separated from patch #3.
>>
>> It only truly helps me understand patch 3 under the assumption that this
>> patch is correct. For reviewing, I cannot really take that 
>> assumption. ;-)
>>
>> I mean, it does help me with the interfaces patch 3 offers, but it still
>> doesn't help me with the objects introduced in patch 3, for instance,
>> because those are all contained in patch 3 itself. And if I cannot
>> verify patch 3 on its own, I cannot really verify that what this patch
>> does is correct in regards to the interfaces offered there.
>>
>>> I'll write a more comprehensive document for the docs/ folder soon,
>>> but the general shape of the idea is "The cleanup actions defined in
>>> the .cb() method will be executed after every job in the transactional
>>> group has completed."
>>
>> Right, again, assuming your implementation is correct, but questioning
>> that is part of the natural reviewing process. :-)
>>
>
> Yes, that's fine! I am just trying to help you unravel this strange 
> loop (ha ha) and/or try to understand what is not necessarily clear 
> with the information as presented.
>
>>> But there's some fine print:
>>>
>>> "'every job' refers only to jobs that have the capability to register
>>> a post-transaction callback, which currently means only drive_backup."
>>>
>>> The general approach to this is, mechanically:
>>>
>>> (1) Extend the life of objects of interest with reference counts,
>>> including Jobs, Bitmaps, and BlkTransactionStates.
>>>
>>> (2) "steal" the callback from a BlockJob and, when invoked, update our
>>> management information for the transaction group in BlkTransactionList.
>>>
>>> (3) Once all jobs we have sent out return, execute the .cb() methods
>>> as indicated in the BlkTransactionList.
>>>
>>>
>>>
>>> So, if you were adding a callback to a different QMP transaction:
>>> - Look at new_transaction_wrapper; you'll use this to bump up various
>>> references used internally for this whole system, and it'll keep
>>> qmp_transaction from being able to delete the list and state objects.
>>>
>>>   This function will give to you (as a gimmick) a new callback and
>>> opaque data structure pointer for you to give to the job that you are
>>> starting. I obfuscate this just a bit to make it clear that you should
>>> be using this function every time to help manage the transactional 
>>> state.
>>>
>>> - Now, the job will run on its own happy way, When it finishes, it
>>> will call transaction_callback, which is the function that
>>> "intercepts" the callbacks, and dispatches the original enveloped
>>> callbacks. It ditches the original data we used to know how to call
>>> the original callback, and begins storing completion information for
>>> jobs instead.
>>>
>>> - transaction_callback will call blk_transaction_complete to store
>>> completion info for one job. In turn, it calls
>>> put_blk_transaction_list to decrement the pending jobs count (the
>>> "jobs" refcount) and once that hits zero ...
>>>
>>> - All of the callback mechanisms associated with each
>>> BlockTransactionState are run via blk_run_transaction_callbacks.
>>>
>>> - This is where drive_backup_cb is going to get invoked again, and
>>> then we will splinter back out into backup-specific concerns, with
>>> Jobs and Bitmaps and what not.
>>
>> Yes, I can see the control flow; my main problem lies in the three
>> different structs introduced in patch 3. I'll tell you why I'm confused
>> by them:
>>
>
> Paydirt!
>
>> First, there is BlkTransactionList. Sounds to me like it should be a
>> list of transactions. Judging from its members, it is rather a list of
>> operations ("actions") for a single transaction, however. Apparently,
>> there is a double-use for the word "transaction": I'd consider a
>> transaction to be the thing started by the QMP command which is a group
>> of atomic operations. However, that one structure is called
>> BlkTransactionState, but it apparently this is only the state of a
>> single operation in the transaction. Pre-existing, but doesn't make it
>> better. You added comments for its list entry members, with the first
>> one stating "All transactions in the current group" and the second being
>> a subset of that. However, again, I consider these to be operations and
>> not transactions. The group is the transaction.
>>
>
> Ah, yes. Good points. We need to decide on consistent terminology.
>
> Transaction: The entire group.
> Action: One particular command within a transaction group.
> Action Op: One particular stage of one command (e.g. prepare, commit)

Sounds good.

> BlkTransactionState is an existing structure that does indeed refer 
> instead to one particular action's state, not that of the entire 
> transaction. This can be changed to be clearer. Better names might be 
> BlkTransactionActionState or BlkActionState.
>
> The BlkTransactionList here is indeed intended to be a list for the 
> entire transaction, so it is actually a "list of actions" and not, 
> perhaps as its name implies, a list of transactions. (We have no data 
> structures with a scope this vast.)
>
> The BlkTransactionList is an object that is created once per 
> transaction group (once per qmp_transaction()) and keeps a count of 
> how many jobs we've launched (and thus how many to expect to return to 
> us.)
>
> Once a job completes, it adds that *action's state* 
> (BlkTransactionState) to its list. Once all jobs sent out have 
> returned, it will iterate this list and execute their .cb() actions.
>
> So perhaps an amendment would be:
>
> typedef struct BlkTransactionList {
>     int pending_jobs;
>     int transaction_rc;
>     QLIST(...) completed_actions;
> } BlkTransactionList;
>
> to make this part clearer.
>
>
>> So we have that adding to my confusion. Furthermore, I think we don't
>> need to list entry members (entry and list_entry) in
>> BlkTransactionState. I think it should be fine to drop the
>> snap_bdrv_states list in qmp_transaction() and just add all
>> BlkTransactionStates to the BlkTransactionList (or however you name it
>> if you rename it) in the preparation while () loop there. I don't think
>> we need to add them only in blk_transaction_complete() (and we should
>> drop that from there if it's been done in qmp_transaction() already, of
>> course). Then we can really just use that list generally as *the* list
>> of operations in a transaction.
>>
>
> Yes, there's not necessarily a strong reason to keep two lists.
>
> One of the existing reasons, though, is that not all transactions 
> launch jobs or have been modified to work with this system yet, so we 
> definitely don't want to consider all of these entries in THIS system 
> just yet.
>
> So one of the simplifications two lists makes is that we don't have to 
> worry about "unsupported actions" making it into the list. Everything 
> in *my* list can be processed without further thought.

Hm, okay. I guess here I got confused because there are so many 
different callbacks... Okay. Makes sense.

Would it make sense to you to replace "Transactions in the current group 
with callbacks" by "Actions in the current transaction with (job) 
completion callbacks"? I think it might be helpful to specify exactly 
which callback this is about (because we do have a couple of callbacks 
here...).

> We can also just add more state into the BlkTransactionState (the 
> ActionState) to help us distinguish, and then we can get rid of both 
> lists.
>
> A state machine tag or something might be useful in that way.
>
>> Second, there is BlkTransactionData. From the name I cannot judge at all
>> how it is supposed to differ from BlkTransactionState, because, well, it
>> contains some data about a transaction... Maybe, because I consider
>> BlkTransactionState to be misnamed, BlkTransactionData actually contains
>> data about the transaction and not about a single operation? But from
>> looking at the members, I have no idea. It has "opaque" and "ret"... I
>> don't know how that is vital data to the transaction itself (and I don't
>> think it is, it is rather data for the completion callback, I guess?),
>> it only looks like some sort of pattern I have seen for AIO callbacks
>> and the like.
>>
>
> BlkTransactionData was meant to contain completion info about a "job" 
> that was launched by an action. I tried to avoid tying it explicitly 
> to a BlockJob -- it can be any action with a callback, really.

Hm. I'm in favor of just calling it "job". The problem is that the 
actions regarding block jobs only launch the block jobs; therefore, the 
action itself is completed by the time qmp_transaction() returns, but 
the block job (which has been launched by the action) is probably still 
running. Therefore, there is a difference between an action and a job.

It may help to really make a clear distinction there, like replace the 
comment "Execute this after +all+ jobs in the transaction finish" by 
"Executed after +all+ jobs launched by the transaction finish" (because 
the jobs aren't part of the transaction).

While it can be any action with a callback, it will be only block jobs 
for now, so once we add something else, we can think about the names 
once again. But I think that "job" is a generic enough description and 
will fit any long-term operation started by a transaction action which 
continues even beyond the transaction itself, so I think we should be 
fine even then.

> Anyway, this structure contains "Data that was delivered to the 
> original callback." In our case here, it's data that would've gone to 
> block_job_complete.
>
> So it contains an opaque pointer (A BDS, we secretly know) and a 
> return code for that individual action's task.
>
>> Third, there is BlkTransactionWrapper. I suppose it wraps a
>> transaction...? Or maybe at least an operation, because apparently I can
>> no longer be sure whether a transaction is a transaction or an
>> operation... But there is no transaction or even a single operation in
>> there. It's just a structure describing a callback. I don't know how
>> that is a "transaction wrapper"...
>>
>
> We have achieved maximum Strawberry.
>
> The BlkTransactionWrapper was meant to wrap any data necessary to be 
> able to deliver the original callback we're stealing.
>
> So in our case, it's for *one action's callback* and in this one 
> instance, it's being used to hold the data needed to manually invoke 
> the block_job_complete callback.
>
> So both BTD and BTW here contain callback related information for one 
> particular action.
>
> They are differentiated by "Wrapper" having a "Before we execute the 
> original callback" context, and "Data" having a "We've already 
> executed the original callback" context.
>
> Can they be combined into one structure? Yes, as long as we keep a tag 
> that lets us know what state we're in.
>
> Can they be inlined into "BlkTransactionState"? Yes -- but I opted to 
> keep it in a separate glob to not clutter up the BlkTransactionState 
> with values only useful for an optional feature.
>
> Is that wise? Maybe, maybe not.
>
> I'm open to names. The most semantically accurate would be 
> BlkTransactionActionCallbackData if we combined BTD/BTW to be one 
> structure. It's a mouthful, though.

I was playing a bit dumb here: I did see that 
BlkTransaction{Data,Wrapper} described callbacks ("it is rather data for 
the completion callback, I guess?", "It's just a structure describing a 
callback"), but I mainly wanted to point out what I would infer from the 
names alone.

I'm all in favor of making the names overly long if need be. Maybe I'd 
call BTD BlkTransactionCallbackData, BlkTransactionCompletionCBData, 
BlkTransactionCBData, BlkTransactionCBParams, or just 
BlkTACBData/BlkTACBParams instead (it is a structure local to 
blockdev.c, so I'd be fine with abbreviations); BTW I'd call 
BlkTransactionCB/BlkTACB (because it contains the function pointer which 
actually is the callback itself).

Maybe merging both does make sense. The name you proposed looks good to 
me, but I'd abbreviate it (at least BlkTransactionActionCBData, or 
BlkTAActionCBData (which is ugly), or BlkTransactionOpCBData, or 
BlkTAOpCBData, or BlkTACBData (I think omitting the "Action" should be 
fine here, because the important thing is to have the "CB" in it to 
signify that this is not data describing the transaction but a 
callback), or even omit the "Data": BlkTransactionCB/BlkTACB. You are, 
after all, describing a complete callback with it (the function pointer 
and all the parameters)).

I don't know whether that helps. Naming is always hard, I know. Just 
make sure that data about a callback contains the word "callback" or 
"CB" and it should be fine, and add proper comments which describe why 
the structure is named the way it is. If you can explain it, it's 
probably a good name.

Max

>
>> So, I don't understand any of the names given to the objects, and while
>> I might understand what they are used for by looking at them, I have a
>> *really* hard time understanding the code that uses them, because if I
>> don't stare at their definitions all the time, I will immediately forget
>> what they contain and what they are used for because I don't understand
>> the names at all (and there is nothing which explicitly tells me what
>> they are used for either). The fact that all of them are starting with
>> BlkTransaction makes mixing them up even easier.
>>
>
> Understood.
>
>>> From the point of view of the "transaction action developer," the
>>> interface to the new feature is found via the
>>> "new_transaction_wrapper" function, But some care must be taken to
>>> make sure this callback actually gets used once it has been requested.
>>> The whole thing can be aborted with the "undo_transaction_wrapper"
>>> function.
>>>
>>> All of the other functions that got added to blockdev.c in Patch #3
>>> are there to assist these two "interface" functions in doing what they
>>> claim to.
>>>
>>> Everything in patch 4 and 5 just adds more reference count flexibility
>>> to things that are only of interest to drive_backup.
>>>
>>> Patch 6 allows us to inject our special callback wrapper into the
>>> qmp_drive_backup function.
>>>
>>>
>>> Clear as mud?
>>
>> Control flow and concept, clear. Implementation, still very muddy, 
>> sorry...
>>
>> Max
>>
>>> --js
>>>
>>>>> + */
>>>>> +void backup_transaction_complete(BlockJob *job, int ret);
>>>>> +
>>>>> +
>>>>>   void blk_dev_change_media_cb(BlockBackend *blk, bool load);
>>>>>   bool blk_dev_has_removable_media(BlockBackend *blk);
>>>>>   void blk_dev_eject_request(BlockBackend *blk, bool force);
>>>>
>>>>
>>
>

^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~2015-03-18 20:21 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-05  4:15 [Qemu-devel] [PATCH 00/11] block: incremental backup transactions John Snow
2015-03-05  4:15 ` [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations John Snow
2015-03-17 15:14   ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test John Snow
2015-03-11 12:11   ` Kashyap Chamarthy
2015-03-11 14:25     ` John Snow
2015-03-11 16:18       ` Kashyap Chamarthy
2015-03-05  4:15 ` [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature John Snow
2015-03-17 17:47   ` Max Reitz
2015-03-17 18:04     ` John Snow
2015-03-17 18:18       ` Eric Blake
2015-03-17 18:23         ` John Snow
2015-03-17 18:19       ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 04/11] block: add refcount to Job object John Snow
2015-03-17 17:54   ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup John Snow
2015-03-17 18:44   ` Max Reitz
2015-03-17 19:12     ` John Snow
2015-03-17 22:46     ` John Snow
2015-03-18 13:03       ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup John Snow
2015-03-17 18:51   ` Max Reitz
2015-03-17 19:16     ` John Snow
2015-03-17 19:33       ` Max Reitz
2015-03-17 20:15       ` Eric Blake
2015-03-05  4:15 ` [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support John Snow
2015-03-17 19:49   ` Max Reitz
2015-03-17 23:27     ` John Snow
2015-03-18 13:41       ` Max Reitz
2015-03-18 19:51         ` John Snow
2015-03-18 20:20           ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue John Snow
2015-03-17 20:04   ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring John Snow
2015-03-17 20:44   ` Max Reitz
2015-03-17 23:40     ` John Snow
2015-03-18 13:44       ` Max Reitz
2015-03-05  4:15 ` [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring John Snow
2015-03-17 20:50   ` Max Reitz
2015-03-17 23:44     ` John Snow
2015-03-05  4:15 ` [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test John Snow
2015-03-17 20:59   ` Max Reitz
2015-03-17 21:04     ` John Snow

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.