block: incremental backup transactions using BlockJobTxn
v11 cover block: incremental backup transactions using BlockJobTxn
Welcome to the Incremental Backup Transactions Newsletter!
What's new?
I replaced the per-action "transactional-cancel" parameter with a per-transaction paremeter named "completion-mode" which is implemented as an enum in case we want to add new behaviors in the future, such as a "jobs only" cancel mode.
For now, it's "grouped" or "individual", and if you use it with actions that do not support the latent transactional cancel, you will receive an error for your troubles.
Version 10 primarily changed V7's patches 10-11 and replaced them with patches 10-12 that are cut a little differently.
This is based on top of the work by Stefan Hajnoczi and Fam Zheng.
Recap: motivation for block job transactions
--------------------------------------------
If an incremental backup block job fails then we reclaim the bitmap so the job can be retried. The problem comes when multiple jobs are started as part of a qmp 'transaction' command. We need to group these jobs in a transaction so that either all jobs complete successfully or all bitmaps are reclaimed.
Without transactions, there is a case where some jobs complete successfully and throw away their bitmaps, making it impossible to retry the backup by rerunning the command if one of the jobs fails.
How does this implementation work?
----------------------------------
These patches add a BlockJobTxn object with the following API:
txn = block_job_txn_new(); block_job_txn_add_job(txn, job1); block_job_txn_add_job(txn, job2);
The jobs either both complete successfully or they both fail/cancel. If the user cancels job1 then job2 will also be cancelled and vice versa.
Jobs objects stay alive waiting for other jobs to complete, even if the coroutines have returned. They can be cancelled by the user during this time. Job blockers are still in effect and no other block job can run on this device in the meantime (since QEMU currently only allows 1 job per device). This is the main drawback to this approach but reasonable since you probably don't want to run other jobs/operations until you're sure the backup was successful (you won't be able to retry a failed backup if there's a new job running).
[History]
v11: Renamed "err-cancel" to "completion-mode"
"none" becomes "individual"
"all" becomes "grouped"
v10: Took series back from Fam. (jsnow)
Replaced per-action parameter with per-transaction properties. Patches 10,11 were split into 10-12.
v9: this version fixes a reference count problem with job->bs,
in patch 05.
v8: Rebase on to master.
Minor fixes addressing John Snow's comments.
v7: Add Eric's rev-by in 1, 11.
Add Max's rev-by in 4, 5, 9, 10, 11.
Add John's rev-by in 5, 6, 8.
Fix wording for 6. [John]
Fix comment of block_job_txn_add_job() in 9. [Max] Remove superfluous hunks, and document default value in 11. [Eric] Update Makefile dep in 14. [Max]
________________________________________________________________________________
For convenience, this branch is available at:
https://github.com/jnsnow/qemu.git branch block-transpop
https://github.com/jnsnow/qemu/tree/block-transpop
This version is tagged block-transpop-v11:
https://github.com/jnsnow/qemu/releases/tag/block-transpop-v11
Fam Zheng (6):
backup: Extract dirty bitmap handling as a separate function
blockjob: Introduce reference count and fix reference to job->bs
blockjob: Add .commit and .abort block job actions
blockjob: Add "completed" and "ret" in BlockJob
blockjob: Simplify block_job_finish_sync
block: Add block job transactions
John Snow (7):
qapi: Add transaction support to block-dirty-bitmap operations
iotests: add transactional incremental backup test
block: rename BlkTransactionState and BdrvActionOps block/backup: Rely on commit/abort for cleanup
block: Add BlockJobTxn support to backup_run
block: add transactional properties
iotests: 124 - transactional failure test
Stefan Hajnoczi (1):
tests: add BlockJobTxn unit test
block.c | 19 +- block/backup.c | 50 ++++-- block/mirror.c | 2 +- blockdev.c | 432 ++++++++++++++++++++++++++++++++++----------- blockjob.c | 189 ++++++++++++++++---- docs/bitmaps.md | 6 +- include/block/block.h | 2 +- include/block/block_int.h | 6 +- include/block/blockjob.h | 85 ++++++++- qapi-schema.json | 56 +++++- qemu-img.c | 3 - qmp-commands.hx | 2 +- tests/Makefile | 3 + tests/qemu-iotests/124 | 182 ++++++++++++++++++- tests/qemu-iotests/124.out | 4 +- tests/test-blockjob-txn.c | 250 ++++++++++++++++++++++++++ 16 files changed, 1118 insertions(+), 173 deletions(-) create mode 100644 tests/test-blockjob-txn.c
Signature · 1 line
2.4.3
PATCH v11 1/14 qapi: Add transaction support to block-dirty-bitmap operations
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.
--- block.c | 19 +++++++- blockdev.c | 114 +++++++++++++++++++++++++++++++++++++++++++++- docs/bitmaps.md | 6 +-- include/block/block.h | 1 - include/block/block_int.h | 3 ++ qapi-schema.json | 6 ++- 6 files changed, 139 insertions(+), 10 deletions(-)
block.c +17 -2
@@ -3399,10 +3399,25 @@ void bdrv_reset_dirty_bitmap(BdrvDirtyBitmap *bitmap,hbitmap_reset (bitmap->bitmap, cur_sector, nr_sectors); } -void bdrv_clear_dirty_bitmap (BdrvDirtyBitmap *bitmap) +void bdrv_clear_dirty_bitmap (BdrvDirtyBitmap *bitmap,HBitmap **out) {assert (bdrv_dirty_bitmap_enabled (bitmap)); -hbitmap_reset_all (bitmap->bitmap); +if (!out) { +hbitmap_reset_all (bitmap->bitmap); + }else { +HBitmap *backup = bitmap->bitmap; + bitmap->bitmap =hbitmap_alloc (bitmap->size, +hbitmap_granularity (backup)); + *out = backup; + } +} + +void bdrv_undo_clear_dirty_bitmap (BdrvDirtyBitmap *bitmap,HBitmap *in) +{ +HBitmap *tmp = bitmap->bitmap; +assert (bdrv_dirty_bitmap_enabled (bitmap)); + bitmap->bitmap = in; +hbitmap_free (tmp); } void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
blockdev.c +113 -1
132 lines not on this page · raw message
docs/bitmaps.md +1 -5
@@ -97,11 +97,7 @@ which is included at the end of this document. } ``` -## Transactions (Not yet implemented) - -* Transactional commands are forthcoming in a future version, - and are not yet available for use. This section serves as - documentation of intent for their design and usage. +## Transactions ### Justification
include/block/block.h +0 -1
@@ -493,7 +493,6 @@ void bdrv_set_dirty_bitmap(BdrvDirtyBitmap *bitmap, int64_t cur_sector, int nr_sectors);void bdrv_reset_dirty_bitmap (BdrvDirtyBitmap *bitmap,int64_t cur_sector,int nr_sectors); -void bdrv_clear_dirty_bitmap (BdrvDirtyBitmap *bitmap);void bdrv_dirty_iter_init (BdrvDirtyBitmap *bitmap,struct HBitmapIter *hbi);void bdrv_set_dirty_iter (struct HBitmapIter *hbi,int64_t offset);int64_t bdrv_get_dirty_count (BdrvDirtyBitmap *bitmap);
include/block/block_int.h +3 -0
@@ -675,4 +675,7 @@ void blk_dev_resize_cb(BlockBackend *blk);void bdrv_set_dirty (BlockDriverState *bs,int64_t cur_sector,int nr_sectors);bool bdrv_requests_pending (BlockDriverState *bs); +void bdrv_clear_dirty_bitmap (BdrvDirtyBitmap *bitmap,HBitmap **out); +void bdrv_undo_clear_dirty_bitmap (BdrvDirtyBitmap *bitmap,HBitmap *in); +#endif /* BLOCK_INT_H */
qapi-schema.json +5 -1
@@ -1531,6 +1531,8 @@ # abort since 1.6 # blockdev-snapshot-internal-sync since 1.7 # blockdev-backup since 2.3 +# block-dirty-bitmap-add since 2.5 +# block-dirty-bitmap-clear since 2.5 ## { 'union': 'TransactionAction', 'data': {
@@ -1538,7 +1540,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' } } ##
Signature · 1 line
2.4.3
PATCH v11 2/14 iotests: add transactional incremental backup test
In reply to John SnowTest simple usage cases for using transactions to create and synchronize incremental backups.
--- tests/qemu-iotests/124 | 54 ++++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 ++-- 2 files changed, 56 insertions(+), 2 deletions(-)
tests/qemu-iotests/124 +54 -0
@@ -36,6 +36,23 @@ def try_remove(img): pass +def transaction_action(action, **kwargs): + return { + 'type': action, + 'data': kwargs + } + + +def transaction_bitmap_clear(node, name, **kwargs): + return transaction_action('block-dirty-bitmap-clear', + node=node, name=name, **kwargs) + + +def transaction_drive_backup(device, target, **kwargs): + return transaction_action('drive-backup', device=device, target=target, + **kwargs) + + class Bitmap: def __init__(self, name, drive): self.name = name
@@ -264,6 +281,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): return self.do_incremental_simple(granularity=131072) + 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 current drive0 image. + ''' + + drive0 = self.drives[0] + bitmap0 = self.add_bitmap('bitmap0', drive0) + 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=[ + transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name), + transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name), + transaction_drive_backup(drive0['id'], drive0['backup'], + sync='full', format=drive0['fmt']) + ]) + self.assert_qmp(result, 'return', {}) + self.wait_until_completed(drive0['id']) + self.files.append(drive0['backup']) + + self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + # Both bitmaps should be correctly in sync. + self.create_incremental(bitmap0) + self.create_incremental(bitmap1) + self.vm.shutdown() + self.check_backups() + + def test_incremental_failure(self): '''Test: Verify backups made after a failure are correct.
tests/qemu-iotests/124.out +2 -2
@@ -1,5 +1,5 @@ -....... +........ ---------------------------------------------------------------------- -Ran 7 tests +Ran 8 tests OK
Signature · 1 line
2.4.3
PATCH v11 3/14 block: rename BlkTransactionState and BdrvActionOps
In reply to John SnowThese structures are misnomers, somewhat.
(1) BlockTransactionState is not state for a transaction, but is rather state for a single transaction action. Rename it "BlkActionState" to be more accurate.
(2) The BdrvActionOps describes operations for the BlkActionState, above. This name might imply a 'BdrvAction' or a 'BdrvActionState', which there isn't.
Rename this to 'BlkActionOps' to match 'BlkActionState'.
Lastly, update the surrounding in-line documentation and comments to reflect the current nature of how Transactions operate.
This patch changes only comments and names, and should not affect behavior in any way.
--- blockdev.c | 124 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 69 insertions(+), 55 deletions(-)
blockdev.c +69 -55
@@ -1337,44 +1337,58 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,/* New and old BlockDriverState structs for atomic group operations */ -typedef struct BlkTransactionState BlkTransactionState ; +typedef struct BlkActionState BlkActionState ; -/* 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. */ -typedef struct BdrvActionOps { -/* Size of state struct, in bytes. */ +/** +* BlkActionOps: +* Table of operations that define an Action. +* +* @instance_size: Size of state struct, in bytes. +* @prepare: Prepare the work, must NOT be NULL. +* @commit: Commit the changes, can be NULL. +* @abort: Abort the changes on fail, can be NULL. +* @clean: Clean up resources after all transaction actions have called +* commit() or abort(). Can be NULL. +* +* Only prepare() may fail. In a single transaction, only one of commit() or +* abort() will be called. clean() will always be called if it is present. +*/ +typedef struct BlkActionOps {size_t instance_size; -/* Prepare the work, must NOT be NULL. */ -void (*prepare)(BlkTransactionState *common,Error **errp); -/* Commit the changes, can be NULL. */ -void (*commit)(BlkTransactionState *common); -/* Abort the changes on fail, can be NULL. */ -void (*abort)(BlkTransactionState *common); -/* Clean up resource in the end, can be NULL. */ -void (*clean)(BlkTransactionState *common); -}BdrvActionOps ; +void (*prepare)(BlkActionState *common,Error **errp); +void (*commit)(BlkActionState *common); +void (*abort)(BlkActionState *common); +void (*clean)(BlkActionState *common); +}BlkActionOps ; -/* -* This structure must be arranged as first member in child type, assuming -* that compiler will also arrange it to the same address with parent instance. -* Later it will be used in free(). +/** +* BlkActionState: +* Describes one Action's state within a Transaction. +* +* @action: QAPI-defined enum identifying which Action to perform. +* @ops: Table of ActionOps this Action can perform. +* @entry: List membership for all Actions in this Transaction. +* +* This structure must be arranged as first member in a subclassed type, +* assuming that the compiler will also arrange it to the same offsets as the +* base class. */ -struct BlkTransactionState { +struct BlkActionState {TransactionAction *action; -const BdrvActionOps *ops; -QSIMPLEQ_ENTRY (BlkTransactionState) entry; +const BlkActionOps *ops; +QSIMPLEQ_ENTRY (BlkActionState) entry; };/* internal snapshot private data */ typedef struct InternalSnapshotState { -BlkTransactionState common; +BlkActionState common;BlockDriverState *bs;AioContext *aio_context;QEMUSnapshotInfo sn;bool created; }InternalSnapshotState ; -static void internal_snapshot_prepare (BlkTransactionState *common, +static void internal_snapshot_prepare (BlkActionState *common,Error **errp) {Error *local_err =NULL ;
@@ -1473,7 +1487,7 @@ static void internal_snapshot_prepare(BlkTransactionState *common, state->created = true; } -static void internal_snapshot_abort (BlkTransactionState *common) +static void internal_snapshot_abort (BlkActionState *common) {InternalSnapshotState *state =DO_UPCAST (InternalSnapshotState, common, common);
@@ -1496,7 +1510,7 @@ static void internal_snapshot_abort(BlkTransactionState *common) } } -static void internal_snapshot_clean (BlkTransactionState *common) +static void internal_snapshot_clean (BlkActionState *common) {InternalSnapshotState *state =DO_UPCAST (InternalSnapshotState, common, common);
@@ -1511,13 +1525,13 @@ static void internal_snapshot_clean(BlkTransactionState *common)/* external snapshot private data */ typedef struct ExternalSnapshotState { -BlkTransactionState common; +BlkActionState common;BlockDriverState *old_bs;BlockDriverState *new_bs;AioContext *aio_context; }ExternalSnapshotState ; -static void external_snapshot_prepare (BlkTransactionState *common, +static void external_snapshot_prepare (BlkActionState *common,Error **errp) {int flags, ret;
@@ -1633,7 +1647,7 @@ static void external_snapshot_prepare(BlkTransactionState *common, } } -static void external_snapshot_commit (BlkTransactionState *common) +static void external_snapshot_commit (BlkActionState *common) {ExternalSnapshotState *state =DO_UPCAST (ExternalSnapshotState, common, common);
@@ -1649,7 +1663,7 @@ static void external_snapshot_commit(BlkTransactionState *common) NULL); } -static void external_snapshot_abort (BlkTransactionState *common) +static void external_snapshot_abort (BlkActionState *common) {ExternalSnapshotState *state =DO_UPCAST (ExternalSnapshotState, common, common);
@@ -1658,7 +1672,7 @@ static void external_snapshot_abort(BlkTransactionState *common) } } -static void external_snapshot_clean (BlkTransactionState *common) +static void external_snapshot_clean (BlkActionState *common) {ExternalSnapshotState *state =DO_UPCAST (ExternalSnapshotState, common, common);
@@ -1669,13 +1683,13 @@ static void external_snapshot_clean(BlkTransactionState *common) }typedef struct DriveBackupState { -BlkTransactionState common; +BlkActionState common;BlockDriverState *bs;AioContext *aio_context;BlockJob *job; }DriveBackupState ; -static void drive_backup_prepare (BlkTransactionState *common,Error **errp) +static void drive_backup_prepare (BlkActionState *common,Error **errp) {DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common);BlockBackend *blk;
@@ -1720,7 +1734,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp) state->job = state->bs->job; } -static void drive_backup_abort (BlkTransactionState *common) +static void drive_backup_abort (BlkActionState *common) {DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common);BlockDriverState *bs = state->bs;
@@ -1731,7 +1745,7 @@ static void drive_backup_abort(BlkTransactionState *common) } } -static void drive_backup_clean (BlkTransactionState *common) +static void drive_backup_clean (BlkActionState *common) {DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common);
@@ -1742,13 +1756,13 @@ static void drive_backup_clean(BlkTransactionState *common) }typedef struct BlockdevBackupState { -BlkTransactionState common; +BlkActionState common;BlockDriverState *bs;BlockJob *job;AioContext *aio_context; }BlockdevBackupState ; -static void blockdev_backup_prepare (BlkTransactionState *common,Error **errp) +static void blockdev_backup_prepare (BlkActionState *common,Error **errp) {BlockdevBackupState *state =DO_UPCAST (BlockdevBackupState, common, common);BlockdevBackup *backup;
@@ -1800,7 +1814,7 @@ static void blockdev_backup_prepare(BlkTransactionState *common, Error **errp) state->job = state->bs->job; } -static void blockdev_backup_abort (BlkTransactionState *common) +static void blockdev_backup_abort (BlkActionState *common) {BlockdevBackupState *state =DO_UPCAST (BlockdevBackupState, common, common);BlockDriverState *bs = state->bs;
@@ -1811,7 +1825,7 @@ static void blockdev_backup_abort(BlkTransactionState *common) } } -static void blockdev_backup_clean (BlkTransactionState *common) +static void blockdev_backup_clean (BlkActionState *common) {BlockdevBackupState *state =DO_UPCAST (BlockdevBackupState, common, common);
@@ -1822,7 +1836,7 @@ static void blockdev_backup_clean(BlkTransactionState *common) } typedef struct BlockDirtyBitmapState { - BlkTransactionState common; + BlkActionState common; BdrvDirtyBitmap *bitmap; BlockDriverState *bs; AioContext *aio_context;
@@ -1830,7 +1844,7 @@ typedef struct BlockDirtyBitmapState {bool prepared; } BlockDirtyBitmapState; -static void block_dirty_bitmap_add_prepare (BlkTransactionState *common, +static void block_dirty_bitmap_add_prepare (BlkActionState *common,Error **errp) {Error *local_err =NULL ;
@@ -1838,7 +1852,7 @@ static void block_dirty_bitmap_add_prepare(BlkTransactionState *common,BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common); - action = common->action->block_dirty_bitmap_add; + action = common->action->u.block_dirty_bitmap_add;/* AIO context taken and released within qmp_block_dirty_bitmap_add */ qmp_block_dirty_bitmap_add(action->node, action->name, action->has_granularity, action->granularity,
@@ -1851,13 +1865,13 @@ static void block_dirty_bitmap_add_prepare(BlkTransactionState *common, } } -static void block_dirty_bitmap_add_abort (BlkTransactionState *common) +static void block_dirty_bitmap_add_abort (BlkActionState *common) {BlockDirtyBitmapAdd *action;BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common); - action = common->action->block_dirty_bitmap_add; + action = common->action->u.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. */
@@ -1866,14 +1880,14 @@ static void block_dirty_bitmap_add_abort(BlkTransactionState *common) } } -static void block_dirty_bitmap_clear_prepare (BlkTransactionState *common, +static void block_dirty_bitmap_clear_prepare (BlkActionState *common,Error **errp) {BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common);BlockDirtyBitmap *action; - action = common->action->block_dirty_bitmap_clear; + action = common->action->u.block_dirty_bitmap_clear; state->bitmap = block_dirty_bitmap_lookup(action->node, action->name, &state->bs,
@@ -1895,7 +1909,7 @@ static void block_dirty_bitmap_clear_prepare(BlkTransactionState *common,/* AioContext is released in .clean() */ } -static void block_dirty_bitmap_clear_abort (BlkTransactionState *common) +static void block_dirty_bitmap_clear_abort (BlkActionState *common) {BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common);
@@ -1903,7 +1917,7 @@ static void block_dirty_bitmap_clear_abort(BlkTransactionState *common)bdrv_undo_clear_dirty_bitmap (state->bitmap, state->backup); } -static void block_dirty_bitmap_clear_commit (BlkTransactionState *common) +static void block_dirty_bitmap_clear_commit (BlkActionState *common) {BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common);
@@ -1911,7 +1925,7 @@ static void block_dirty_bitmap_clear_commit(BlkTransactionState *common)hbitmap_free (state->backup); } -static void block_dirty_bitmap_clear_clean (BlkTransactionState *common) +static void block_dirty_bitmap_clear_clean (BlkActionState *common) {BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common);
@@ -1921,17 +1935,17 @@ static void block_dirty_bitmap_clear_clean(BlkTransactionState *common) } } -static void abort_prepare (BlkTransactionState *common,Error **errp) +static void abort_prepare (BlkActionState *common,Error **errp) {error_setg (errp,"Transaction aborted using Abort action" ); } -static void abort_commit (BlkTransactionState *common) +static void abort_commit (BlkActionState *common) {g_assert_not_reached ();/* this action never succeeds */ } -static const BdrvActionOps actions[] = { +static const BlkActionOps actions[] = { [TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC ] = { .instance_size =sizeof (ExternalSnapshotState), .prepare = external_snapshot_prepare,
@@ -1952,7 +1966,7 @@ static const BdrvActionOps actions[] = { .clean = blockdev_backup_clean, }, [TRANSACTION_ACTION_KIND_ABORT] = { - .instance_size = sizeof(BlkTransactionState), + .instance_size = sizeof(BlkActionState), .prepare = abort_prepare, .commit = abort_commit, },
@@ -1983,10 +1997,10 @@ static const BdrvActionOps actions[] = {void qmp_transaction (TransactionActionList *dev_list,Error **errp) {TransactionActionList *dev_entry = dev_list; -BlkTransactionState *state, *next; +BlkActionState *state, *next;Error *local_err =NULL ; - QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states; + QSIMPLEQ_HEAD(snap_bdrv_states, BlkActionState) snap_bdrv_states;QSIMPLEQ_INIT (&snap_bdrv_states);/* drain all i/o before any operations */
@@ -1995,7 +2009,7 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)/* We don't do anything in this loop that commits us to the operations */ while (NULL != dev_entry) {TransactionAction *dev_info =NULL ; -const BdrvActionOps *ops; +const BlkActionOps *ops; dev_info = dev_entry->value; dev_entry = dev_entry->next;
Signature · 1 line
2.4.3
PATCH v11 4/14 backup: Extract dirty bitmap handling as a separate function
In reply to John SnowFrom: Fam Zheng <[email protected]>
This will be reused by the coming new transactional completion code.
--- block/backup.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-)
block/backup.c +17 -10
@@ -221,6 +221,22 @@ static void backup_iostatus_reset(BlockJob *job) } } +static void backup_cleanup_sync_bitmap (BackupBlockJob *job,int ret) +{ +BdrvDirtyBitmap *bm; +BlockDriverState *bs = job->common.bs; + +if (ret <0 ||block_job_is_cancelled (&job->common)) { +/* Merge the successor back into the parent, delete nothing. */ + bm =bdrv_reclaim_dirty_bitmap (bs, job->sync_bitmap,NULL ); +assert (bm); + }else { +/* Everything is fine, delete this bitmap and install the backup. */ + bm =bdrv_dirty_bitmap_abdicate (bs, job->sync_bitmap,NULL ); +assert (bm); + } +} +static const BlockJobDriver backup_job_driver = { .instance_size =sizeof (BackupBlockJob), .job_type =BLOCK_JOB_TYPE_BACKUP ,
@@ -443,16 +459,7 @@ static void coroutine_fn backup_run(void *opaque)qemu_co_rwlock_unlock (&job->flush_rwlock);if (job->sync_bitmap) { -BdrvDirtyBitmap *bm; -if (ret <0 ||block_job_is_cancelled (&job->common)) { -/* Merge the successor back into the parent, delete nothing. */ - bm =bdrv_reclaim_dirty_bitmap (bs, job->sync_bitmap,NULL ); -assert (bm); - }else { -/* Everything is fine, delete this bitmap and install the backup. */ - bm =bdrv_dirty_bitmap_abdicate (bs, job->sync_bitmap,NULL ); -assert (bm); - } +backup_cleanup_sync_bitmap (job, ret); }hbitmap_free (job->bitmap);
Signature · 1 line
2.4.3
PATCH v11 5/14 blockjob: Introduce reference count and fix reference to job->bs
In reply to John SnowFrom: Fam Zheng <[email protected]>
Add reference count to block job, meanwhile move the ownership of the reference to job->bs from the caller (which is released in two completion callbacks) to the block job itself. It is necessary for block_job_complete_sync to work, because block job shouldn't live longer than its bs, as asserted in bdrv_delete.
Now block_job_complete_sync can be simplified.
--- block/mirror.c | 2 +- blockdev.c | 28 ---------------------------- blockjob.c | 25 ++++++++++++++++--------- include/block/blockjob.h | 18 +++++++++++++++--- qemu-img.c | 3 --- 5 files changed, 32 insertions(+), 44 deletions(-)
block/mirror.c +1 -1
@@ -741,7 +741,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target, s->dirty_bitmap =bdrv_create_dirty_bitmap (bs, granularity,NULL , errp);if (!s->dirty_bitmap) {g_free (s->replaces); -block_job_release (bs); +block_job_unref (&s->common);return ; }bdrv_set_enable_write_cache (s->target, true);
blockdev.c +0 -28
@@ -283,32 +283,6 @@ typedef struct {BlockDriverState *bs; } BDRVPutRefBH; -static void bdrv_put_ref_bh (void *opaque) -{ -BDRVPutRefBH *s = opaque; - -bdrv_unref (s->bs); -qemu_bh_delete (s->bh); -g_free (s); -} - -/* -* Release a BDS reference in a BH -* -* It is not safe to use bdrv_unref() from a callback function when the callers -* still need the BlockDriverState. In such cases we schedule a BH to release -* the reference. -*/ -static void bdrv_put_ref_bh_schedule (BlockDriverState *bs) -{ -BDRVPutRefBH *s; - - s =g_new (BDRVPutRefBH,1 ); - s->bh =qemu_bh_new (bdrv_put_ref_bh, s); - s->bs = bs; -qemu_bh_schedule (s->bh); -} -static int parse_block_error_action (const char *buf,bool is_read,Error **errp) {if (!strcmp (buf,"ignore" )) {
@@ -2536,8 +2510,6 @@ static void block_job_cb(void *opaque, int ret) }else {block_job_event_completed (bs->job, msg); } - -bdrv_put_ref_bh_schedule (bs); } void qmp_block_stream(const char *device,
blockjob.c +16 -9
@@ -60,6 +60,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, job->cb = cb; job->opaque = opaque; job->busy = true; + job->refcnt =1 ; bs->job = job;/* Only set speed when necessary to avoid NotSupported error */
@@ -68,7 +69,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,block_job_set_speed (job, speed, &local_err);if (local_err) { -block_job_release (bs); +block_job_unref (job);error_propagate (errp, local_err);return NULL ; }
@@ -76,15 +77,21 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs,return job; } -void block_job_release (BlockDriverState *bs) +void block_job_ref (BlockJob *job) { -BlockJob *job = bs->job; + ++job->refcnt; +} - bs->job =NULL ; -bdrv_op_unblock_all (bs, job->blocker); -error_free (job->blocker); -g_free (job->id); -g_free (job); +void block_job_unref (BlockJob *job) +{ +if (--job->refcnt ==0 ) { + job->bs->job =NULL ; +bdrv_op_unblock_all (job->bs, job->blocker); +bdrv_unref (job->bs); +error_free (job->blocker); +g_free (job->id); +g_free (job); + } }void block_job_completed (BlockJob *job,int ret)
@@ -93,7 +100,7 @@ void block_job_completed(BlockJob *job, int ret)assert (bs->job == job); job->cb (job->opaque, ret); -block_job_release (bs); +block_job_unref (job); }void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp)
include/block/blockjob.h +15 -3
@@ -130,6 +130,9 @@ struct BlockJob {/** The opaque value that is passed to the completion function. */ void *opaque; + +/** Reference count of the block job */ +int refcnt; };/**
@@ -174,12 +177,21 @@ void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns);void block_job_yield (BlockJob *job);/** -* block_job_release: +* block_job_ref: * @bs: The block device. * -* Release job resources when an error occurred or job completed. +* Grab a reference to the block job. Should be paired with block_job_unref. */ -void block_job_release (BlockDriverState *bs); +void block_job_ref (BlockJob *job); + +/** +* block_job_unref: +* @bs: The block device. +* +* Release reference to the block job and release resources if it is the last +* reference. +*/ +void block_job_unref (BlockJob *job);/** * block_job_completed:
qemu-img.c +0 -3
@@ -645,9 +645,6 @@ static void common_block_job_cb(void *opaque, int ret)if (ret <0 ) {error_setg_errno (cbi->errp, -ret,"Block job failed" ); } - -/* Drop this block job's reference */ -bdrv_unref (cbi->bs); }static void run_block_job (BlockJob *job,Error **errp)
Signature · 1 line
2.4.3
PATCH v11 6/14 blockjob: Add .commit and .abort block job actions
In reply to John SnowFrom: Fam Zheng <[email protected]>
--- include/block/blockjob.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
include/block/blockjob.h +20 -0
@@ -50,6 +50,26 @@ typedef struct BlockJobDriver {* manually. */ void (*complete)(BlockJob *job,Error **errp); + +/** +* If the callback is not NULL, it will be invoked when all the jobs +* belonging to the same transaction complete; or upon this job's +* completion if it is not in a transaction. Skipped if NULL. +* +* All jobs will complete with a call to either .commit() or .abort() but +* never both. +*/ +void (*commit)(BlockJob *job); + +/** +* If the callback is not NULL, it will be invoked when any job in the +* same transaction fails; or upon this job's failure (due to error or +* cancellation) if it is not in a transaction. Skipped if NULL. +* +* All jobs will complete with a call to either .commit() or .abort() but +* never both. +*/ +void (*abort)(BlockJob *job); } BlockJobDriver;/**
Signature · 1 line
2.4.3
PATCH v11 7/14 blockjob: Add "completed" and "ret" in BlockJob
In reply to John SnowFrom: Fam Zheng <[email protected]>
They are set when block_job_completed is called.
--- blockjob.c | 3 +++ include/block/blockjob.h | 9 +++++++++ 2 files changed, 12 insertions(+)
blockjob.c +3 -0
@@ -99,6 +99,9 @@ void block_job_completed(BlockJob *job, int ret)BlockDriverState *bs = job->bs;assert (bs->job == job); +assert (!job->completed); + job->completed = true; + job->ret = ret; job->cb (job->opaque, ret);block_job_unref (job); }
include/block/blockjob.h +9 -0
@@ -153,6 +153,15 @@ struct BlockJob {/** Reference count of the block job */ int refcnt; + +/* True if this job has reported completion by calling block_job_completed. +*/ +bool completed; + +/* ret code passed to block_job_completed. +*/ +int ret; + };/**
Signature · 1 line
2.4.3
PATCH v11 8/14 blockjob: Simplify block_job_finish_sync
In reply to John SnowFrom: Fam Zheng <[email protected]>
With job->completed and job->ret to replace BlockFinishData.
--- blockjob.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-)
blockjob.c +7 -21
@@ -188,43 +188,29 @@ struct BlockFinishData {int ret; }; -static void block_job_finish_cb (void *opaque,int ret) -{ -struct BlockFinishData *data = opaque; - - data->cancelled =block_job_is_cancelled (data->job); - data->ret = ret; - data->cb (data->opaque, ret); -} -static int block_job_finish_sync (BlockJob *job,void (*finish)(BlockJob *,Error **errp),Error **errp) { -struct BlockFinishData data;BlockDriverState *bs = job->bs;Error *local_err =NULL ; +int ret;assert (bs->job == job); -/* Set up our own callback to store the result and chain to -* the original callback. -*/ - data.job = job; - data.cb = job->cb; - data.opaque = job->opaque; - data.ret = -EINPROGRESS ; - job->cb = block_job_finish_cb; - job->opaque = &data; +block_job_ref (job);finish (job, &local_err);if (local_err) {error_propagate (errp, local_err); +block_job_unref (job);return -EBUSY ; } -while (data.ret == -EINPROGRESS ) { +while (!job->completed) {aio_poll (bdrv_get_aio_context (bs), true); } -return (data.cancelled && data.ret ==0 ) ? -ECANCELED : data.ret; + ret = (job->cancelled && job->ret ==0 ) ? -ECANCELED : job->ret; +block_job_unref (job); +return ret; }/* A wrapper around block_job_cancel() taking an Error ** parameter so it may be
Signature · 1 line
2.4.3
PATCH v11 9/14 block: Add block job transactions
In reply to John SnowFrom: Fam Zheng <[email protected]>
Sometimes block jobs must execute as a transaction group. Finishing jobs wait until all other jobs are ready to complete successfully. Failure or cancellation of one job cancels the other jobs in the group.
[Rewrite the implementation which is now contained in block_job_completed. --Fam]
--- blockjob.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++- include/block/block.h | 1 + include/block/blockjob.h | 38 +++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-)
blockjob.c +133 -2
@@ -37,6 +37,19 @@#include "qemu/timer.h" #include "qapi-event.h" +/* Transactional group of block jobs */ +struct BlockJobTxn { + +/* Is this txn being cancelled? */ +bool aborting; + +/* List of jobs */ + QLIST_HEAD(, BlockJob) jobs; + +/* Reference count */ +int refcnt; +}; +void *block_job_create (const BlockJobDriver *driver,BlockDriverState *bs,int64_t speed,BlockCompletionFunc *cb,void *opaque,Error **errp)
@@ -94,6 +107,86 @@ void block_job_unref(BlockJob *job) } } +static void block_job_completed_single (BlockJob *job) +{ +if (!job->ret) { +if (job->driver->commit) { + job->driver->commit (job); + } + }else { +if (job->driver->abort) { + job->driver->abort (job); + } + } + job->cb (job->opaque, job->ret); +if (job->txn) { +block_job_txn_unref (job->txn); + } +block_job_unref (job); +} + +static void block_job_completed_txn_abort (BlockJob *job) +{ +AioContext *ctx; +BlockJobTxn *txn = job->txn; +BlockJob *other_job, *next; + +if (txn->aborting) { +/* +* We are cancelled by another job, which will handle everything. +*/ +return ; + } + txn->aborting = true; +/* We are the first failed job. Cancel other jobs. */ + QLIST_FOREACH(other_job, &txn->jobs, txn_list) { + ctx =bdrv_get_aio_context (other_job->bs); +aio_context_acquire (ctx); + } + QLIST_FOREACH(other_job, &txn->jobs, txn_list) { +if (other_job == job || other_job->completed) { +/* Other jobs are "effectively" cancelled by us, set the status for +* them; this job, however, may or may not be cancelled, depending +* on the caller, so leave it. */ +if (other_job != job) { + other_job->cancelled = true; + } +continue ; + } +block_job_cancel_sync (other_job); +assert (other_job->completed); + } + QLIST_FOREACH_SAFE(other_job, &txn->jobs, txn_list, next) { + ctx =bdrv_get_aio_context (other_job->bs); +block_job_completed_single (other_job); +aio_context_release (ctx); + } +} + +static void block_job_completed_txn_success (BlockJob *job) +{ +AioContext *ctx; +BlockJobTxn *txn = job->txn; +BlockJob *other_job, *next; +/* +* Successful completion, see if there are other running jobs in this +* txn. +*/ + QLIST_FOREACH(other_job, &txn->jobs, txn_list) { +if (!other_job->completed) { +return ; + } + } +/* We are the last completed job, commit the transaction. */ + QLIST_FOREACH_SAFE(other_job, &txn->jobs, txn_list, next) { + ctx =bdrv_get_aio_context (other_job->bs); +aio_context_acquire (ctx); +assert (other_job->ret ==0 ); +block_job_completed_single (other_job); +aio_context_release (ctx); + } +} +void block_job_completed (BlockJob *job,int ret) {BlockDriverState *bs = job->bs;
@@ -102,8 +195,13 @@ void block_job_completed(BlockJob *job, int ret)assert (!job->completed); job->completed = true; job->ret = ret; - job->cb (job->opaque, ret); -block_job_unref (job); +if (!job->txn) { +block_job_completed_single (job); + }else if (ret <0 ||block_job_is_cancelled (job)) { +block_job_completed_txn_abort (job); + }else { +block_job_completed_txn_success (job); + } }void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp)
@@ -402,3 +500,36 @@ void block_job_defer_to_main_loop(BlockJob *job,qemu_bh_schedule (data->bh); } + +BlockJobTxn *block_job_txn_new (void ) +{ +BlockJobTxn *txn =g_new0 (BlockJobTxn,1 ); +QLIST_INIT (&txn->jobs); + txn->refcnt =1 ; +return txn; +} + +static void block_job_txn_ref (BlockJobTxn *txn) +{ + txn->refcnt++; +} + +void block_job_txn_unref (BlockJobTxn *txn) +{ +if (txn && --txn->refcnt ==0 ) { +g_free (txn); + } +} + +void block_job_txn_add_job (BlockJobTxn *txn,BlockJob *job) +{ +if (!txn) { +return ; + } + +assert (!job->txn); + job->txn = txn; + +QLIST_INSERT_HEAD (&txn->jobs, job, txn_list); +block_job_txn_ref (txn); +}
include/block/block.h +1 -0
@@ -14,6 +14,7 @@ typedef struct BlockDriver BlockDriver;typedef struct BlockJob BlockJob ;typedef struct BdrvChild BdrvChild ;typedef struct BdrvChildRole BdrvChildRole ; +typedef struct BlockJobTxn BlockJobTxn ; typedef struct BlockDriverInfo { /* in bytes, 0 if irrelevant */
include/block/blockjob.h +38 -0
@@ -162,6 +162,9 @@ struct BlockJob {*/ int ret; +/** Non-NULL if this job is part of a transaction */ +BlockJobTxn *txn; +QLIST_ENTRY (BlockJob) txn_list; };/**
@@ -405,4 +408,39 @@ void block_job_defer_to_main_loop(BlockJob *job, BlockJobDeferToMainLoopFn *fn, void *opaque); +/** +* block_job_txn_new: +* +* Allocate and return a new block job transaction. Jobs can be added to the +* transaction using block_job_txn_add_job(). +* +* The transaction is automatically freed when the last job completes or is +* cancelled. +* +* All jobs in the transaction either complete successfully or fail/cancel as a +* group. Jobs wait for each other before completing. Cancelling one job +* cancels all jobs in the transaction. +*/ +BlockJobTxn *block_job_txn_new (void ); + +/** +* block_job_txn_unref: +* +* Release a reference that was previously acquired with block_job_txn_add_job +* or block_job_txn_new. If it's the last reference to the object, it will be +* freed. +*/ +void block_job_txn_unref (BlockJobTxn *txn); + +/** +* block_job_txn_add_job: +* @txn: The transaction (may be NULL) +* @job: Job to add to the transaction +* +* Add @job to the transaction. The @job must not already be in a transaction. +* The caller must call either block_job_txn_unref() or block_job_completed() +* to release the reference that is automatically grabbed here. +*/ +void block_job_txn_add_job (BlockJobTxn *txn,BlockJob *job); +#endif
Signature · 1 line
2.4.3
PATCH v11 10/14 block/backup: Rely on commit/abort for cleanup
In reply to John SnowSwitch over to the new .commit/.abort handlers for cleaning up incremental bitmaps.
--- block/backup.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-)
block/backup.c +18 -4
@@ -237,11 +237,29 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret) } } +static void backup_commit (BlockJob *job) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); +if (s->sync_bitmap) { +backup_cleanup_sync_bitmap (s,0 ); + } +} + +static void backup_abort (BlockJob *job) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); +if (s->sync_bitmap) { +backup_cleanup_sync_bitmap (s,-1 ); + } +} +static const BlockJobDriver backup_job_driver = { .instance_size =sizeof (BackupBlockJob), .job_type =BLOCK_JOB_TYPE_BACKUP , .set_speed = backup_set_speed, .iostatus_reset = backup_iostatus_reset, + .commit = backup_commit, + .abort = backup_abort, }; static BlockErrorAction backup_error_action(BackupBlockJob *job,
@@ -457,10 +475,6 @@ static void coroutine_fn backup_run(void *opaque)/* wait until pending backup_do_cow() calls have completed */ qemu_co_rwlock_wrlock (&job->flush_rwlock);qemu_co_rwlock_unlock (&job->flush_rwlock); - -if (job->sync_bitmap) { -backup_cleanup_sync_bitmap (job, ret); - }hbitmap_free (job->bitmap);if (target->blk) {
Signature · 1 line
2.4.3
PATCH v11 11/14 block: Add BlockJobTxn support to backup_run
In reply to John SnowAllow a BlockJobTxn to be passed into backup_run, which will allow the job to join a transactional group if present.
Propagate this new parameter outward into new QMP helper functions in blockdev.c to allow transaction commands to pass forward their BlockJobTxn object in a forthcoming patch.
--- block/backup.c | 3 +- blockdev.c | 112 ++++++++++++++++++++++++++++++++++------------ include/block/block_int.h | 3 +- 3 files changed, 88 insertions(+), 30 deletions(-)
block/backup.c +2 -1
@@ -493,7 +493,7 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,BlockdevOnError on_source_error, BlockdevOnError on_target_error, BlockCompletionFunc *cb, void *opaque, - Error **errp) + BlockJobTxn *txn, Error **errp) { int64_t len;
@@ -575,6 +575,7 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, sync_bitmap :NULL ; job->common.len = len; job->common.co =qemu_coroutine_create (backup_run); +block_job_txn_add_job (txn, &job->common);qemu_coroutine_enter (job->common.co, job);return ;
blockdev.c +84 -28
177 lines not on this page · raw message
include/block/block_int.h +2 -1
@@ -651,6 +651,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,* @on_target_error: The action to take upon error writing to the target. * @cb: Completion function for the job. * @opaque: Opaque pointer value passed to @cb. +* @txn: Transaction that this job is part of (may be NULL). * * Start a backup operation on @bs. Clusters in @bs are written to @target * until the job is cancelled or manually completed.
@@ -661,7 +662,7 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,BlockdevOnError on_source_error, BlockdevOnError on_target_error, BlockCompletionFunc *cb, void *opaque, - Error **errp); + BlockJobTxn *txn, Error **errp);void blk_set_bs (BlockBackend *blk,BlockDriverState *bs);
Signature · 1 line
2.4.3
PATCH v11 12/14 block: add transactional properties
In reply to John SnowAdd both transactional properties to the QMP transactional interface, and add the BlockJobTxn that we create as a result of the err-cancel property to the BlkActionState structure.
--- blockdev.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- qapi-schema.json | 50 +++++++++++++++++++++++++++++++++--- qmp-commands.hx | 2 +- 3 files changed, 122 insertions(+), 8 deletions(-)
blockdev.c +74 -4
@@ -1121,7 +1121,7 @@ static void blockdev_do_action(TransactionActionKind type, void *data, action.u.data = data; list.value = &action; list.next =NULL ; -qmp_transaction (&list, errp); +qmp_transaction (&list, false,NULL , errp); } void qmp_blockdev_snapshot_sync(bool has_device, const char *device,
@@ -1341,6 +1341,7 @@ typedef struct BlkActionOps {* * @action: QAPI-defined enum identifying which Action to perform. * @ops: Table of ActionOps this Action can perform. +* @block_job_txn: Transaction which this action belongs to. * @entry: List membership for all Actions in this Transaction. * * This structure must be arranged as first member in a subclassed type,
@@ -1350,6 +1351,8 @@ typedef struct BlkActionOps {struct BlkActionState {TransactionAction *action;const BlkActionOps *ops; +BlockJobTxn *block_job_txn; +TransactionProperties *txn_props;QSIMPLEQ_ENTRY (BlkActionState) entry; };
@@ -1362,6 +1365,20 @@ typedef struct InternalSnapshotState {bool created; } InternalSnapshotState; + +static int action_check_completion_mode (BlkActionState *s,Error **errp) +{ +if (s->txn_props->completion_mode !=ACTION_COMPLETION_MODE_INDIVIDUAL ) { +error_setg (errp, +"Action '%s' does not support Transaction property " +"completion-mode = %s" , + TransactionActionKind_lookup[s->action->type], + ActionCompletionMode_lookup[s->txn_props->completion_mode]); +return -1 ; + } +return 0 ; +} +static void internal_snapshot_prepare (BlkActionState *common,Error **errp) {
@@ -1387,6 +1404,10 @@ static void internal_snapshot_prepare(BlkActionState *common, name = internal->name;/* 2. check for validation */ +if (action_check_completion_mode (common, errp) <0 ) { +return ; + } + blk =blk_by_name (device);if (!blk) { error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
@@ -1544,6 +1565,10 @@ static void external_snapshot_prepare(BlkActionState *common, }/* start processing */ +if (action_check_completion_mode (common, errp) <0 ) { +return ; + } + state->old_bs =bdrv_lookup_bs (has_device ? device :NULL , has_node_name ? node_name :NULL , &local_err);
@@ -1711,7 +1736,7 @@ static void drive_backup_prepare(BlkActionState *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, &local_err); + common->block_job_txn, &local_err);if (local_err) {error_propagate (errp, local_err);return ;
@@ -1800,7 +1825,7 @@ static void blockdev_backup_prepare(BlkActionState *common, Error **errp) backup->has_speed, backup->speed, backup->has_on_source_error, backup->on_source_error, backup->has_on_target_error, backup->on_target_error, - NULL, &local_err); + common->block_job_txn, &local_err);if (local_err) {error_propagate (errp, local_err);return ;
@@ -1847,6 +1872,10 @@ static void block_dirty_bitmap_add_prepare(BlkActionState *common,BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, common, common); +if (action_check_completion_mode (common, errp) <0 ) { +return ; + } + action = common->action->u.block_dirty_bitmap_add;/* AIO context taken and released within qmp_block_dirty_bitmap_add */ qmp_block_dirty_bitmap_add(action->node, action->name,
@@ -1882,6 +1911,10 @@ static void block_dirty_bitmap_clear_prepare(BlkActionState *common, common, common);BlockDirtyBitmap *action; +if (action_check_completion_mode (common, errp) <0 ) { +return ; + } + action = common->action->u.block_dirty_bitmap_clear; state->bitmap = block_dirty_bitmap_lookup(action->node, action->name,
@@ -1985,19 +2018,50 @@ static const BlkActionOps actions[] = { } }; +/** +* Allocate a TransactionProperties structure if necessary, and fill +* that structure with desired defaults if they are unset. +*/ +static TransactionProperties *get_transaction_properties ( +TransactionProperties *props) +{ +if (!props) { + props =g_new0 (TransactionProperties,1 ); + } + +if (!props->has_completion_mode) { + props->has_completion_mode = true; + props->completion_mode =ACTION_COMPLETION_MODE_INDIVIDUAL ; + } + +return props; +} +/* * 'Atomic' group operations. The operations are performed as a set, and if * any fail then we roll back all operations in the group. */ -void qmp_transaction (TransactionActionList *dev_list,Error **errp) +void qmp_transaction (TransactionActionList *dev_list, +bool has_props, +struct TransactionProperties *props, +Error **errp) {TransactionActionList *dev_entry = dev_list; +BlockJobTxn *block_job_txn =NULL ;BlkActionState *state, *next;Error *local_err =NULL ; QSIMPLEQ_HEAD(snap_bdrv_states, BlkActionState) snap_bdrv_states;QSIMPLEQ_INIT (&snap_bdrv_states); +/* Does this transaction get canceled as a group on failure? +* If not, we don't really need to make a BlockJobTxn. +*/ + props =get_transaction_properties (props); +if (props->completion_mode !=ACTION_COMPLETION_MODE_INDIVIDUAL ) { + block_job_txn =block_job_txn_new (); + } +/* drain all i/o before any operations */ bdrv_drain_all ();
@@ -2017,6 +2081,8 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp) state =g_malloc0 (ops->instance_size); state->ops = ops; state->action = dev_info; + state->block_job_txn = block_job_txn; + state->txn_props = props;QSIMPLEQ_INSERT_TAIL (&snap_bdrv_states, state, entry); state->ops->prepare (state, &local_err);
@@ -2049,6 +2115,10 @@ exit: }g_free (state); } +if (!has_props) { +qapi_free_TransactionProperties (props); + } +block_job_txn_unref (block_job_txn); }
qapi-schema.json +47 -3
74 lines not on this page · raw message
qmp-commands.hx +1 -1
@@ -1262,7 +1262,7 @@ EQMP }, { .name = "transaction", - .args_type = "actions:q", + .args_type = "actions:q,properties:q?", .mhandler.cmd_new = qmp_marshal_transaction, },
Signature · 1 line
2.4.3
PATCH v11 13/14 iotests: 124 - transactional failure test
In reply to John SnowUse 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.
To support the 'err-cancel' QMP argument name it's necessary for transaction_action() to convert underscores in Python argument names to hyphens for QMP argument names.
--- tests/qemu-iotests/124 | 130 ++++++++++++++++++++++++++++++++++++++++++++- tests/qemu-iotests/124.out | 4 +- 2 files changed, 130 insertions(+), 4 deletions(-)
tests/qemu-iotests/124 +128 -2
@@ -39,7 +39,7 @@ def try_remove(img): def transaction_action(action, **kwargs): return { 'type': action, - 'data': kwargs + 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.iteritems()) }
@@ -139,9 +139,12 @@ class TestIncrementalBackup(iotests.QMPTestCase): def do_qmp_backup(self, error='Input/output error', **kwargs): res = self.vm.qmp('drive-backup', **kwargs) self.assert_qmp(res, 'return', {}) + return self.wait_qmp_backup(kwargs['device'], error) + + def wait_qmp_backup(self, device, error='Input/output error'): event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", - match={'data': {'device': kwargs['device']}}) + match={'data': {'device': device}}) self.assertNotEqual(event, None) try:
@@ -156,6 +159,12 @@ class TestIncrementalBackup(iotests.QMPTestCase): return False + def wait_qmp_backup_cancelled(self, device): + event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED', + match={'data': {'device': device}}) + self.assertNotEqual(event, None) + + def create_anchor_backup(self, drive=None): if drive is None: drive = self.drives[-1]
@@ -375,6 +384,123 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.check_backups() + 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_anchor_backup(drive0) + self.create_anchor_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 = [ + transaction_drive_backup(drive0['id'], target0, sync='incremental', + format=drive0['fmt'], mode='existing', + bitmap=dr0bm0.name), + transaction_drive_backup(drive1['id'], target1, sync='incremental', + format=drive1['fmt'], mode='existing', + bitmap=dr1bm0.name) + ] + result = self.vm.qmp('transaction', actions=transaction, + properties={'completion-mode': 'grouped'} ) + self.assert_qmp(result, 'return', {}) + + # Observe that drive0's backup is cancelled and drive1 completes with + # an error. + self.wait_qmp_backup_cancelled(drive0['id']) + self.assertFalse(self.wait_qmp_backup(drive1['id'])) + 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 target and eliminate our record of the + # unsuccessful drive1 target. Then re-run the same transaction. + dr0bm0.del_target() + dr1bm0.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, + properties={'completion-mode':'grouped'}) + self.assert_qmp(result, 'return', {}) + + # Both should complete successfully this time. + self.assertTrue(self.wait_qmp_backup(drive0['id'])) + self.assertTrue(self.wait_qmp_backup(drive1['id'])) + self.make_reference_backup(dr0bm0) + self.make_reference_backup(dr1bm0) + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + # And the images should of course validate. + self.vm.shutdown() + self.check_backups() + + def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() self.files.append(self.err_img)
tests/qemu-iotests/124.out +2 -2
@@ -1,5 +1,5 @@ -........ +......... ---------------------------------------------------------------------- -Ran 8 tests +Ran 9 tests OK
Signature · 1 line
2.4.3
PATCH v11 14/14 tests: add BlockJobTxn unit test
In reply to John SnowFrom: Stefan Hajnoczi <[email protected]>
The BlockJobTxn unit test verifies that both single jobs and pairs of jobs behave as a transaction group. Either all jobs complete successfully or the group is cancelled.
--- tests/Makefile | 3 + tests/test-blockjob-txn.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 tests/test-blockjob-txn.c
tests/Makefile +3 -0
@@ -47,6 +47,8 @@ check-unit-y += tests/test-thread-pool$(EXESUF) gcov-files-test-thread-pool-y = thread-pool.c gcov-files-test-hbitmap-y = util/hbitmap.c check-unit-y += tests/test-hbitmap$(EXESUF) +gcov-files-test-hbitmap-y = blockjob.c +check-unit-y += tests/test-blockjob-txn$(EXESUF) check-unit-y += tests/test-x86-cpuid$(EXESUF) # all code tested by test-x86-cpuid is inside topology.h gcov-files-test-x86-cpuid-y =
@@ -390,6 +392,7 @@ tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(test-block-obj-y) tests/test-aio$(EXESUF): tests/test-aio.o $(test-block-obj-y) tests/test-rfifolock$(EXESUF): tests/test-rfifolock.o $(test-util-obj-y) tests/test-throttle$(EXESUF): tests/test-throttle.o $(test-block-obj-y) +tests/test-blockjob-txn$(EXESUF): tests/test-blockjob-txn.o $(test-block-obj-y) $(test-util-obj-y) tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(test-block-obj-y) tests/test-iov$(EXESUF): tests/test-iov.o $(test-util-obj-y) tests/test-hbitmap$(EXESUF): tests/test-hbitmap.o $(test-util-obj-y)
tests/test-blockjob-txn.c new file +250 -0
250 lines not on this page · raw message
Signature · 1 line
2.4.3
Stefan Hajnoczi
AppliedQuoted 112 lines · John Snow
Welcome to the Incremental Backup Transactions Newsletter!
What's new?
I replaced the per-action "transactional-cancel" parameter with a per-transaction paremeter named "completion-mode" which is implemented as an enum in case we want to add new behaviors in the future, such as a "jobs only" cancel mode.
For now, it's "grouped" or "individual", and if you use it with actions that do not support the latent transactional cancel, you will receive an error for your troubles.
Version 10 primarily changed V7's patches 10-11 and replaced them with patches 10-12 that are cut a little differently.
This is based on top of the work by Stefan Hajnoczi and Fam Zheng.
Recap: motivation for block job transactions
--------------------------------------------
If an incremental backup block job fails then we reclaim the bitmap so the job can be retried. The problem comes when multiple jobs are started as part of a qmp 'transaction' command. We need to group these jobs in a transaction so that either all jobs complete successfully or all bitmaps are reclaimed.Without transactions, there is a case where some jobs complete successfully and throw away their bitmaps, making it impossible to retry the backup by rerunning the command if one of the jobs fails.
How does this implementation work?
----------------------------------
These patches add a BlockJobTxn object with the following API:txn = block_job_txn_new(); block_job_txn_add_job(txn, job1); block_job_txn_add_job(txn, job2);The jobs either both complete successfully or they both fail/cancel. If the user cancels job1 then job2 will also be cancelled and vice versa.
Jobs objects stay alive waiting for other jobs to complete, even if the coroutines have returned. They can be cancelled by the user during this time. Job blockers are still in effect and no other block job can run on this device in the meantime (since QEMU currently only allows 1 job per device). This is the main drawback to this approach but reasonable since you probably don't want to run other jobs/operations until you're sure the backup was successful (you won't be able to retry a failed backup if there's a new job running).
[History]
v11: Renamed "err-cancel" to "completion-mode"
"none" becomes "individual"
"all" becomes "grouped"v10: Took series back from Fam. (jsnow)
Replaced per-action parameter with per-transaction properties. Patches 10,11 were split into 10-12.v9: this version fixes a reference count problem with
job->bs,
in patch 05.v8: Rebase on to master.
Minor fixes addressing John Snow's comments.v7: Add Eric's rev-by in 1, 11.
Add Max's rev-by in 4, 5, 9, 10, 11.
Add John's rev-by in 5, 6, 8.
Fix wording for 6. [John]
Fix comment ofblock_job_txn_add_job()in 9. [Max] Remove superfluous hunks, and document default value in 11. [Eric] UpdateMakefiledep in 14. [Max]________________________________________________________________________________
For convenience, this branch is available at:
https://github.com/jnsnow/qemu.git branch block-transpop
https://github.com/jnsnow/qemu/tree/block-transpopThis version is tagged block-transpop-v11:
https://github.com/jnsnow/qemu/releases/tag/block-transpop-v11Fam Zheng (6):
backup: Extract dirty bitmap handling as a separate function
blockjob: Introduce reference count and fix reference tojob->bs
blockjob: Add .commit and .abort block job actions
blockjob: Add "completed" and "ret" in BlockJob
blockjob: Simplifyblock_job_finish_sync
block: Add block job transactionsJohn Snow (7):
qapi: Add transaction support to block-dirty-bitmap operations
iotests: add transactional incremental backup test
block: rename BlkTransactionState and BdrvActionOps block/backup: Rely on commit/abort for cleanup
block: Add BlockJobTxn support tobackup_run
block: add transactional properties
iotests: 124 - transactional failure testStefan Hajnoczi (1):
tests: add BlockJobTxn unit testblock.c | 19 +- block/backup.c | 50 ++++-- block/mirror.c | 2 +- blockdev.c | 432 ++++++++++++++++++++++++++++++++++----------- blockjob.c | 189 ++++++++++++++++---- docs/bitmaps.md | 6 +- include/block/block.h | 2 +- include/block/block_int.h | 6 +- include/block/blockjob.h | 85 ++++++++- qapi-schema.json | 56 +++++-
Quoted 12 lines
qemu-img.c | 3 - qmp-commands.hx | 2 +- tests/Makefile | 3 + tests/qemu-iotests/124 | 182 ++++++++++++++++++- tests/qemu-iotests/124.out | 4 +- tests/test-blockjob-txn.c | 250 ++++++++++++++++++++++++++ 16 files changed, 1118 insertions(+), 173 deletions(-) create mode 100644 tests/test-blockjob-txn.cSignature · 1 line
2.4.3
Thanks, applied to my block tree:
https://github.com/stefanha/qemu/commits/block
Stefan