All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 00/42] Generic background jobs
@ 2018-05-09 16:25 Kevin Wolf
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
                   ` (42 more replies)
  0 siblings, 43 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:25 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Before we can make x-blockdev-create a background job, we need to
generalise the job infrastructure so that it can be used without any
associated block node.

This series extracts a Job object from the block job infrastructure,
which should contain everything related to jobs that doesn't require the
block layer to be involved.

Job creation with a centralised job-create command (if we even want
this) as well as the actual conversion of x-blockdev-create to Job is
left for another series. With 42 patches, the series must be perfect and
I can't add any more patches.

Kevin Wolf (42):
  blockjob: Fix assertion in block_job_finalize()
  blockjob: Wrappers for progress counter access
  blockjob: Move RateLimit to BlockJob
  blockjob: Implement block_job_set_speed() centrally
  blockjob: Introduce block_job_ratelimit_get_delay()
  blockjob: Add block_job_driver()
  blockjob: Remove block_job_pause/resume_all()
  job: Create Job, JobDriver and job_create()
  job: Rename BlockJobType into JobType
  job: Add JobDriver.job_type
  job: Add job_delete()
  job: Maintain a list of all jobs
  job: Move state transitions to Job
  job: Add reference counting
  job: Move cancelled to Job
  job: Add Job.aio_context
  job: Move defer_to_main_loop to Job
  job: Move coroutine and related code to Job
  job: Add job_sleep_ns()
  job: Move pause/resume functions to Job
  job: Replace BlockJob.completed with job_is_completed()
  job: Move BlockJobCreateFlags to Job
  blockjob: Split block_job_event_pending()
  job: Add job_event_*()
  job: Move single job finalisation to Job
  job: Convert block_job_cancel_async() to Job
  job: Add job_drain()
  job: Move .complete callback to Job
  job: Move job_finish_sync() to Job
  job: Switch transactions to JobTxn
  job: Move transactions to Job
  job: Move completion and cancellation to Job
  job: Add job_yield()
  job: Add job_dismiss()
  job: Add job_is_ready()
  job: Add job_transition_to_ready()
  job: Move progress fields to Job
  job: Add JOB_STATUS_CHANGE QMP event
  job: Add lifecycle QMP commands
  job: Add query-jobs QMP command
  iotests: Move qmp_to_opts() to VM
  qemu-iotests: Test job-* with block jobs

 qapi/block-core.json          |   81 +--
 qapi/job.json                 |  236 +++++++++
 qapi/qapi-schema.json         |    1 +
 include/block/block_int.h     |    2 +-
 include/block/blockjob.h      |  315 +-----------
 include/block/blockjob_int.h  |  187 +------
 include/qemu/job.h            |  561 +++++++++++++++++++++
 block.c                       |    2 +-
 block/backup.c                |  111 ++---
 block/commit.c                |   75 ++-
 block/mirror.c                |  139 +++---
 block/replication.c           |   10 +-
 block/stream.c                |   68 ++-
 blockdev.c                    |   66 +--
 blockjob.c                    | 1095 ++++++-----------------------------------
 job-qmp.c                     |  194 ++++++++
 job.c                         |  994 +++++++++++++++++++++++++++++++++++++
 qemu-img.c                    |   22 +-
 tests/test-bdrv-drain.c       |   63 +--
 tests/test-blockjob-txn.c     |   74 +--
 tests/test-blockjob.c         |  141 +++---
 MAINTAINERS                   |    3 +
 Makefile                      |    9 +
 Makefile.objs                 |    6 +-
 block/trace-events            |    5 -
 tests/qemu-iotests/030        |    6 +-
 tests/qemu-iotests/040        |    2 +
 tests/qemu-iotests/041        |   23 +-
 tests/qemu-iotests/095        |    2 +-
 tests/qemu-iotests/095.out    |    6 +
 tests/qemu-iotests/109        |    2 +-
 tests/qemu-iotests/109.out    |  178 ++++++-
 tests/qemu-iotests/124        |    8 +
 tests/qemu-iotests/127.out    |    7 +
 tests/qemu-iotests/141        |   10 +-
 tests/qemu-iotests/141.out    |   29 ++
 tests/qemu-iotests/144        |    2 +-
 tests/qemu-iotests/144.out    |    7 +
 tests/qemu-iotests/155        |    2 +-
 tests/qemu-iotests/156        |    2 +-
 tests/qemu-iotests/156.out    |    7 +
 tests/qemu-iotests/185        |   12 +-
 tests/qemu-iotests/185.out    |   10 +
 tests/qemu-iotests/191        |    4 +-
 tests/qemu-iotests/191.out    |  132 +++++
 tests/qemu-iotests/219        |  201 ++++++++
 tests/qemu-iotests/219.out    |  327 ++++++++++++
 tests/qemu-iotests/group      |    1 +
 tests/qemu-iotests/iotests.py |   45 +-
 trace-events                  |   14 +
 50 files changed, 3568 insertions(+), 1931 deletions(-)
 create mode 100644 qapi/job.json
 create mode 100644 include/qemu/job.h
 create mode 100644 job-qmp.c
 create mode 100644 job.c
 create mode 100755 tests/qemu-iotests/219
 create mode 100644 tests/qemu-iotests/219.out

-- 
2.13.6

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

* [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
@ 2018-05-09 16:25 ` Kevin Wolf
  2018-05-09 17:28   ` Eric Blake
                     ` (2 more replies)
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access Kevin Wolf
                   ` (41 subsequent siblings)
  42 siblings, 3 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:25 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Every job gets a non-NULL job->txn on creation, but it doesn't
necessarily keep it until it is decommissioned: Finalising a job removes
it from its transaction. Therefore, calling 'blockdev-job-finalize' a
second time on an already concluded job causes an assertion failure.

Remove job->txn from the assertion in block_job_finalize() to fix this.
block_job_do_finalize() still has the same assertion, but if a job is
already removed from its transaction, block_job_apply_verb() will
already error out before we run into that assertion.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 blockjob.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/blockjob.c b/blockjob.c
index 4de48166b2..b38ed7e265 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -702,7 +702,7 @@ void block_job_complete(BlockJob *job, Error **errp)
 
 void block_job_finalize(BlockJob *job, Error **errp)
 {
-    assert(job && job->id && job->txn);
+    assert(job && job->id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
         return;
     }
-- 
2.13.6

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

* [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
@ 2018-05-09 16:25 ` Kevin Wolf
  2018-05-11 22:12   ` Max Reitz
  2018-05-14 19:14   ` John Snow
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob Kevin Wolf
                   ` (40 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:25 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Block job drivers are not expected to mess with the internals of the
BlockJob object, so provide wrapper functions for one of the cases where
they still do it: Updating the progress counter.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 include/block/blockjob.h | 19 +++++++++++++++++++
 block/backup.c           | 22 +++++++++++++---------
 block/commit.c           | 16 ++++++++--------
 block/mirror.c           | 11 +++++------
 block/stream.c           | 14 ++++++++------
 blockjob.c               | 10 ++++++++++
 6 files changed, 63 insertions(+), 29 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index fc645dac68..a2cc52233b 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -278,6 +278,25 @@ void block_job_finalize(BlockJob *job, Error **errp);
 void block_job_dismiss(BlockJob **job, Error **errp);
 
 /**
+ * block_job_progress_update:
+ * @job: The job that has made progress
+ * @done: How much progress the job made
+ *
+ * Updates the progress counter of the job.
+ */
+void block_job_progress_update(BlockJob *job, uint64_t done);
+
+/**
+ * block_job_progress_set_remaining:
+ * @job: The job whose expected progress end value is set
+ * @remaining: Expected end value of the progress counter of the job
+ *
+ * Sets the expected end value of the progress counter of a job so that a
+ * completion percentage can be calculated when the progress is updated.
+ */
+void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);
+
+/**
  * block_job_query:
  * @job: The job to get information about.
  *
diff --git a/block/backup.c b/block/backup.c
index 453cd62c24..5d95805472 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -39,6 +39,7 @@ typedef struct BackupBlockJob {
     BlockdevOnError on_source_error;
     BlockdevOnError on_target_error;
     CoRwlock flush_rwlock;
+    uint64_t len;
     uint64_t bytes_read;
     int64_t cluster_size;
     bool compress;
@@ -118,7 +119,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
 
         trace_backup_do_cow_process(job, start);
 
-        n = MIN(job->cluster_size, job->common.len - start);
+        n = MIN(job->cluster_size, job->len - start);
 
         if (!bounce_buffer) {
             bounce_buffer = blk_blockalign(blk, job->cluster_size);
@@ -159,7 +160,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
          * offset field is an opaque progress value, it is not a disk offset.
          */
         job->bytes_read += n;
-        job->common.offset += n;
+        block_job_progress_update(&job->common, n);
     }
 
 out:
@@ -261,7 +262,7 @@ void backup_do_checkpoint(BlockJob *job, Error **errp)
         return;
     }
 
-    len = DIV_ROUND_UP(backup_job->common.len, backup_job->cluster_size);
+    len = DIV_ROUND_UP(backup_job->len, backup_job->cluster_size);
     hbitmap_set(backup_job->copy_bitmap, 0, len);
 }
 
@@ -420,8 +421,9 @@ static void backup_incremental_init_copy_bitmap(BackupBlockJob *job)
         bdrv_set_dirty_iter(dbi, next_cluster * job->cluster_size);
     }
 
-    job->common.offset = job->common.len -
-                         hbitmap_count(job->copy_bitmap) * job->cluster_size;
+    /* TODO block_job_progress_set_remaining() would make more sense */
+    block_job_progress_update(&job->common,
+        job->len - hbitmap_count(job->copy_bitmap) * job->cluster_size);
 
     bdrv_dirty_iter_free(dbi);
 }
@@ -437,7 +439,9 @@ static void coroutine_fn backup_run(void *opaque)
     QLIST_INIT(&job->inflight_reqs);
     qemu_co_rwlock_init(&job->flush_rwlock);
 
-    nb_clusters = DIV_ROUND_UP(job->common.len, job->cluster_size);
+    nb_clusters = DIV_ROUND_UP(job->len, job->cluster_size);
+    block_job_progress_set_remaining(&job->common, job->len);
+
     job->copy_bitmap = hbitmap_alloc(nb_clusters, 0);
     if (job->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
         backup_incremental_init_copy_bitmap(job);
@@ -461,7 +465,7 @@ static void coroutine_fn backup_run(void *opaque)
         ret = backup_run_incremental(job);
     } else {
         /* Both FULL and TOP SYNC_MODE's require copying.. */
-        for (offset = 0; offset < job->common.len;
+        for (offset = 0; offset < job->len;
              offset += job->cluster_size) {
             bool error_is_read;
             int alloced = 0;
@@ -620,7 +624,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
         goto error;
     }
 
-    /* job->common.len is fixed, so we can't allow resize */
+    /* job->len is fixed, so we can't allow resize */
     job = block_job_create(job_id, &backup_job_driver, txn, bs,
                            BLK_PERM_CONSISTENT_READ,
                            BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
@@ -676,7 +680,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
     /* Required permissions are already taken with target's blk_new() */
     block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
                        &error_abort);
-    job->common.len = len;
+    job->len = len;
 
     return &job->common;
 
diff --git a/block/commit.c b/block/commit.c
index 1432baeef4..50b191c980 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -146,21 +146,21 @@ static void coroutine_fn commit_run(void *opaque)
     int64_t n = 0; /* bytes */
     void *buf = NULL;
     int bytes_written = 0;
-    int64_t base_len;
+    int64_t len, base_len;
 
-    ret = s->common.len = blk_getlength(s->top);
-
-    if (s->common.len < 0) {
+    ret = len = blk_getlength(s->top);
+    if (len < 0) {
         goto out;
     }
+    block_job_progress_set_remaining(&s->common, len);
 
     ret = base_len = blk_getlength(s->base);
     if (base_len < 0) {
         goto out;
     }
 
-    if (base_len < s->common.len) {
-        ret = blk_truncate(s->base, s->common.len, PREALLOC_MODE_OFF, NULL);
+    if (base_len < len) {
+        ret = blk_truncate(s->base, len, PREALLOC_MODE_OFF, NULL);
         if (ret) {
             goto out;
         }
@@ -168,7 +168,7 @@ static void coroutine_fn commit_run(void *opaque)
 
     buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
 
-    for (offset = 0; offset < s->common.len; offset += n) {
+    for (offset = 0; offset < len; offset += n) {
         bool copy;
 
         /* Note that even when no rate limit is applied we need to yield
@@ -198,7 +198,7 @@ static void coroutine_fn commit_run(void *opaque)
             }
         }
         /* Publish progress */
-        s->common.offset += n;
+        block_job_progress_update(&s->common, n);
 
         if (copy && s->common.speed) {
             delay_ns = ratelimit_calculate_delay(&s->limit, n);
diff --git a/block/mirror.c b/block/mirror.c
index 99da9c0858..77ee9b1791 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -121,7 +121,7 @@ static void mirror_iteration_done(MirrorOp *op, int ret)
             bitmap_set(s->cow_bitmap, chunk_num, nb_chunks);
         }
         if (!s->initial_zeroing_ongoing) {
-            s->common.offset += op->bytes;
+            block_job_progress_update(&s->common, op->bytes);
         }
     }
     qemu_iovec_destroy(&op->qiov);
@@ -792,11 +792,10 @@ static void coroutine_fn mirror_run(void *opaque)
         block_job_pause_point(&s->common);
 
         cnt = bdrv_get_dirty_count(s->dirty_bitmap);
-        /* s->common.offset contains the number of bytes already processed so
-         * far, cnt is the number of dirty bytes remaining and
-         * s->bytes_in_flight is the number of bytes currently being
-         * processed; together those are the current total operation length */
-        s->common.len = s->common.offset + s->bytes_in_flight + cnt;
+        /* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
+         * the number of bytes currently being processed; together those are
+         * the current total operation length */
+        block_job_progress_set_remaining(&s->common, s->bytes_in_flight + cnt);
 
         /* Note that even when no rate limit is applied we need to yield
          * periodically with no pending I/O so that bdrv_drain_all() returns.
diff --git a/block/stream.c b/block/stream.c
index 1a85708fcf..8369852bda 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -107,6 +107,7 @@ static void coroutine_fn stream_run(void *opaque)
     BlockBackend *blk = s->common.blk;
     BlockDriverState *bs = blk_bs(blk);
     BlockDriverState *base = s->base;
+    int64_t len;
     int64_t offset = 0;
     uint64_t delay_ns = 0;
     int error = 0;
@@ -118,11 +119,12 @@ static void coroutine_fn stream_run(void *opaque)
         goto out;
     }
 
-    s->common.len = bdrv_getlength(bs);
-    if (s->common.len < 0) {
-        ret = s->common.len;
+    len = bdrv_getlength(bs);
+    if (len < 0) {
+        ret = len;
         goto out;
     }
+    block_job_progress_set_remaining(&s->common, len);
 
     buf = qemu_blockalign(bs, STREAM_BUFFER_SIZE);
 
@@ -135,7 +137,7 @@ static void coroutine_fn stream_run(void *opaque)
         bdrv_enable_copy_on_read(bs);
     }
 
-    for ( ; offset < s->common.len; offset += n) {
+    for ( ; offset < len; offset += n) {
         bool copy;
 
         /* Note that even when no rate limit is applied we need to yield
@@ -159,7 +161,7 @@ static void coroutine_fn stream_run(void *opaque)
 
             /* Finish early if end of backing file has been reached */
             if (ret == 0 && n == 0) {
-                n = s->common.len - offset;
+                n = len - offset;
             }
 
             copy = (ret == 1);
@@ -185,7 +187,7 @@ static void coroutine_fn stream_run(void *opaque)
         ret = 0;
 
         /* Publish progress */
-        s->common.offset += n;
+        block_job_progress_update(&s->common, n);
         if (copy && s->common.speed) {
             delay_ns = ratelimit_calculate_delay(&s->limit, n);
         } else {
diff --git a/blockjob.c b/blockjob.c
index b38ed7e265..bdd34f3a76 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -810,6 +810,16 @@ int block_job_complete_sync(BlockJob *job, Error **errp)
     return block_job_finish_sync(job, &block_job_complete, errp);
 }
 
+void block_job_progress_update(BlockJob *job, uint64_t done)
+{
+    job->offset += done;
+}
+
+void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining)
+{
+    job->len = job->offset + remaining;
+}
+
 BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
 {
     BlockJobInfo *info;
-- 
2.13.6

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

* [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access Kevin Wolf
@ 2018-05-09 16:25 ` Kevin Wolf
  2018-05-11 22:14   ` Max Reitz
  2018-05-14 19:19   ` John Snow
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally Kevin Wolf
                   ` (39 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:25 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Every block job has a RateLimit, and they all do the exact same thing
with it, so it should be common infrastructure. Move the struct field
for a start.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 include/block/blockjob.h | 4 ++++
 block/backup.c           | 5 ++---
 block/commit.c           | 5 ++---
 block/mirror.c           | 6 +++---
 block/stream.c           | 5 ++---
 5 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index a2cc52233b..22bf418209 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -27,6 +27,7 @@
 #define BLOCKJOB_H
 
 #include "block/block.h"
+#include "qemu/ratelimit.h"
 
 typedef struct BlockJobDriver BlockJobDriver;
 typedef struct BlockJobTxn BlockJobTxn;
@@ -118,6 +119,9 @@ typedef struct BlockJob {
     /** Speed that was set with @block_job_set_speed.  */
     int64_t speed;
 
+    /** Rate limiting data structure for implementing @speed. */
+    RateLimit limit;
+
     /** The completion function that will be called when the job completes.  */
     BlockCompletionFunc *cb;
 
diff --git a/block/backup.c b/block/backup.c
index 5d95805472..7585c4391e 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -35,7 +35,6 @@ typedef struct BackupBlockJob {
     /* bitmap for sync=incremental */
     BdrvDirtyBitmap *sync_bitmap;
     MirrorSyncMode sync_mode;
-    RateLimit limit;
     BlockdevOnError on_source_error;
     BlockdevOnError on_target_error;
     CoRwlock flush_rwlock;
@@ -199,7 +198,7 @@ static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
         error_setg(errp, QERR_INVALID_PARAMETER, "speed");
         return;
     }
-    ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
+    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
 }
 
 static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
@@ -346,7 +345,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
      * (without, VM does not reboot)
      */
     if (job->common.speed) {
-        uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
+        uint64_t delay_ns = ratelimit_calculate_delay(&job->common.limit,
                                                       job->bytes_read);
         job->bytes_read = 0;
         block_job_sleep_ns(&job->common, delay_ns);
diff --git a/block/commit.c b/block/commit.c
index 50b191c980..beec5d0ad6 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -35,7 +35,6 @@ enum {
 
 typedef struct CommitBlockJob {
     BlockJob common;
-    RateLimit limit;
     BlockDriverState *commit_top_bs;
     BlockBackend *top;
     BlockBackend *base;
@@ -201,7 +200,7 @@ static void coroutine_fn commit_run(void *opaque)
         block_job_progress_update(&s->common, n);
 
         if (copy && s->common.speed) {
-            delay_ns = ratelimit_calculate_delay(&s->limit, n);
+            delay_ns = ratelimit_calculate_delay(&s->common.limit, n);
         } else {
             delay_ns = 0;
         }
@@ -225,7 +224,7 @@ static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
         error_setg(errp, QERR_INVALID_PARAMETER, "speed");
         return;
     }
-    ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
+    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
 }
 
 static const BlockJobDriver commit_job_driver = {
diff --git a/block/mirror.c b/block/mirror.c
index 77ee9b1791..95da62544f 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -36,7 +36,6 @@ typedef struct MirrorBuffer {
 
 typedef struct MirrorBlockJob {
     BlockJob common;
-    RateLimit limit;
     BlockBackend *target;
     BlockDriverState *mirror_top_bs;
     BlockDriverState *source;
@@ -450,7 +449,8 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
         offset += io_bytes;
         nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity);
         if (s->common.speed) {
-            delay_ns = ratelimit_calculate_delay(&s->limit, io_bytes_acct);
+            delay_ns = ratelimit_calculate_delay(&s->common.limit,
+                                                 io_bytes_acct);
         }
     }
     return delay_ns;
@@ -916,7 +916,7 @@ static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
         error_setg(errp, QERR_INVALID_PARAMETER, "speed");
         return;
     }
-    ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
+    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
 }
 
 static void mirror_complete(BlockJob *job, Error **errp)
diff --git a/block/stream.c b/block/stream.c
index 8369852bda..a1d4768c2e 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -33,7 +33,6 @@ enum {
 
 typedef struct StreamBlockJob {
     BlockJob common;
-    RateLimit limit;
     BlockDriverState *base;
     BlockdevOnError on_error;
     char *backing_file_str;
@@ -189,7 +188,7 @@ static void coroutine_fn stream_run(void *opaque)
         /* Publish progress */
         block_job_progress_update(&s->common, n);
         if (copy && s->common.speed) {
-            delay_ns = ratelimit_calculate_delay(&s->limit, n);
+            delay_ns = ratelimit_calculate_delay(&s->common.limit, n);
         } else {
             delay_ns = 0;
         }
@@ -219,7 +218,7 @@ static void stream_set_speed(BlockJob *job, int64_t speed, Error **errp)
         error_setg(errp, QERR_INVALID_PARAMETER, "speed");
         return;
     }
-    ratelimit_set_speed(&s->limit, speed, SLICE_TIME);
+    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
 }
 
 static const BlockJobDriver stream_job_driver = {
-- 
2.13.6

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

* [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (2 preceding siblings ...)
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob Kevin Wolf
@ 2018-05-09 16:25 ` Kevin Wolf
  2018-05-11 22:19   ` Max Reitz
  2018-05-14 19:27   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay() Kevin Wolf
                   ` (38 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:25 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

All block job drivers support .set_speed and all of them duplicate the
same code to implement it. Move that code to blockjob.c and remove the
now useless callback.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 include/block/blockjob.h     |  2 ++
 include/block/blockjob_int.h |  3 ---
 block/backup.c               | 13 -------------
 block/commit.c               | 14 --------------
 block/mirror.c               | 14 --------------
 block/stream.c               | 14 --------------
 blockjob.c                   | 12 ++++--------
 7 files changed, 6 insertions(+), 66 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 22bf418209..5aa8a6aaec 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -29,6 +29,8 @@
 #include "block/block.h"
 #include "qemu/ratelimit.h"
 
+#define SLICE_TIME 100000000ULL /* ns */
+
 typedef struct BlockJobDriver BlockJobDriver;
 typedef struct BlockJobTxn BlockJobTxn;
 
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 642adce68b..870bd346a8 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -41,9 +41,6 @@ struct BlockJobDriver {
     /** String describing the operation, part of query-block-jobs QMP API */
     BlockJobType job_type;
 
-    /** Optional callback for job types that support setting a speed limit */
-    void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
-
     /** Mandatory: Entrypoint for the Coroutine. */
     CoroutineEntry *start;
 
diff --git a/block/backup.c b/block/backup.c
index 7585c4391e..8468fd9f94 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -27,7 +27,6 @@
 #include "qemu/error-report.h"
 
 #define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16)
-#define SLICE_TIME 100000000ULL /* ns */
 
 typedef struct BackupBlockJob {
     BlockJob common;
@@ -190,17 +189,6 @@ static int coroutine_fn backup_before_write_notify(
     return backup_do_cow(job, req->offset, req->bytes, NULL, true);
 }
 
-static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
-{
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
-
-    if (speed < 0) {
-        error_setg(errp, QERR_INVALID_PARAMETER, "speed");
-        return;
-    }
-    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
-}
-
 static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
 {
     BdrvDirtyBitmap *bm;
@@ -540,7 +528,6 @@ static const BlockJobDriver backup_job_driver = {
     .instance_size          = sizeof(BackupBlockJob),
     .job_type               = BLOCK_JOB_TYPE_BACKUP,
     .start                  = backup_run,
-    .set_speed              = backup_set_speed,
     .commit                 = backup_commit,
     .abort                  = backup_abort,
     .clean                  = backup_clean,
diff --git a/block/commit.c b/block/commit.c
index beec5d0ad6..46cbeaec3e 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -31,8 +31,6 @@ enum {
     COMMIT_BUFFER_SIZE = 512 * 1024, /* in bytes */
 };
 
-#define SLICE_TIME 100000000ULL /* ns */
-
 typedef struct CommitBlockJob {
     BlockJob common;
     BlockDriverState *commit_top_bs;
@@ -216,21 +214,9 @@ out:
     block_job_defer_to_main_loop(&s->common, commit_complete, data);
 }
 
-static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
-{
-    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
-
-    if (speed < 0) {
-        error_setg(errp, QERR_INVALID_PARAMETER, "speed");
-        return;
-    }
-    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
-}
-
 static const BlockJobDriver commit_job_driver = {
     .instance_size = sizeof(CommitBlockJob),
     .job_type      = BLOCK_JOB_TYPE_COMMIT,
-    .set_speed     = commit_set_speed,
     .start         = commit_run,
 };
 
diff --git a/block/mirror.c b/block/mirror.c
index 95da62544f..21eb04f1d2 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -22,7 +22,6 @@
 #include "qemu/ratelimit.h"
 #include "qemu/bitmap.h"
 
-#define SLICE_TIME    100000000ULL /* ns */
 #define MAX_IN_FLIGHT 16
 #define MAX_IO_BYTES (1 << 20) /* 1 Mb */
 #define DEFAULT_MIRROR_BUF_SIZE (MAX_IN_FLIGHT * MAX_IO_BYTES)
@@ -908,17 +907,6 @@ immediate_exit:
     block_job_defer_to_main_loop(&s->common, mirror_exit, data);
 }
 
-static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
-{
-    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
-
-    if (speed < 0) {
-        error_setg(errp, QERR_INVALID_PARAMETER, "speed");
-        return;
-    }
-    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
-}
-
 static void mirror_complete(BlockJob *job, Error **errp)
 {
     MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
@@ -1003,7 +991,6 @@ static void mirror_drain(BlockJob *job)
 static const BlockJobDriver mirror_job_driver = {
     .instance_size          = sizeof(MirrorBlockJob),
     .job_type               = BLOCK_JOB_TYPE_MIRROR,
-    .set_speed              = mirror_set_speed,
     .start                  = mirror_run,
     .complete               = mirror_complete,
     .pause                  = mirror_pause,
@@ -1014,7 +1001,6 @@ static const BlockJobDriver mirror_job_driver = {
 static const BlockJobDriver commit_active_job_driver = {
     .instance_size          = sizeof(MirrorBlockJob),
     .job_type               = BLOCK_JOB_TYPE_COMMIT,
-    .set_speed              = mirror_set_speed,
     .start                  = mirror_run,
     .complete               = mirror_complete,
     .pause                  = mirror_pause,
diff --git a/block/stream.c b/block/stream.c
index a1d4768c2e..797d7c4f21 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -29,8 +29,6 @@ enum {
     STREAM_BUFFER_SIZE = 512 * 1024, /* in bytes */
 };
 
-#define SLICE_TIME 100000000ULL /* ns */
-
 typedef struct StreamBlockJob {
     BlockJob common;
     BlockDriverState *base;
@@ -210,21 +208,9 @@ out:
     block_job_defer_to_main_loop(&s->common, stream_complete, data);
 }
 
-static void stream_set_speed(BlockJob *job, int64_t speed, Error **errp)
-{
-    StreamBlockJob *s = container_of(job, StreamBlockJob, common);
-
-    if (speed < 0) {
-        error_setg(errp, QERR_INVALID_PARAMETER, "speed");
-        return;
-    }
-    ratelimit_set_speed(&s->common.limit, speed, SLICE_TIME);
-}
-
 static const BlockJobDriver stream_job_driver = {
     .instance_size = sizeof(StreamBlockJob),
     .job_type      = BLOCK_JOB_TYPE_STREAM,
-    .set_speed     = stream_set_speed,
     .start         = stream_run,
 };
 
diff --git a/blockjob.c b/blockjob.c
index bdd34f3a76..5d050e65d3 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -659,22 +659,18 @@ static bool block_job_timer_pending(BlockJob *job)
 
 void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
 {
-    Error *local_err = NULL;
     int64_t old_speed = job->speed;
 
-    if (!job->driver->set_speed) {
-        error_setg(errp, QERR_UNSUPPORTED);
-        return;
-    }
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_SET_SPEED, errp)) {
         return;
     }
-    job->driver->set_speed(job, speed, &local_err);
-    if (local_err) {
-        error_propagate(errp, local_err);
+    if (speed < 0) {
+        error_setg(errp, QERR_INVALID_PARAMETER, "speed");
         return;
     }
 
+    ratelimit_set_speed(&job->limit, speed, SLICE_TIME);
+
     job->speed = speed;
     if (speed && speed <= old_speed) {
         return;
-- 
2.13.6

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

* [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (3 preceding siblings ...)
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-11 22:25   ` Max Reitz
  2018-05-14 19:36   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver() Kevin Wolf
                   ` (37 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This gets us rid of more direct accesses to BlockJob fields from the
job drivers.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 include/block/blockjob_int.h |  8 ++++++++
 block/backup.c               | 18 +++++++-----------
 block/commit.c               |  4 ++--
 block/mirror.c               |  5 +----
 block/stream.c               |  4 ++--
 blockjob.c                   |  9 +++++++++
 6 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 870bd346a8..4d0439c51e 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -166,6 +166,14 @@ void block_job_sleep_ns(BlockJob *job, int64_t ns);
 void block_job_yield(BlockJob *job);
 
 /**
+ * block_job_ratelimit_get_delay:
+ *
+ * Calculate and return delay for the next request in ns. See the documentation
+ * of ratelimit_calculate_delay() for details.
+ */
+int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
+
+/**
  * block_job_pause_all:
  *
  * Asynchronously pause all jobs.
diff --git a/block/backup.c b/block/backup.c
index 8468fd9f94..cfdb6ecdf5 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -325,21 +325,17 @@ static void backup_complete(BlockJob *job, void *opaque)
 
 static bool coroutine_fn yield_and_check(BackupBlockJob *job)
 {
+    uint64_t delay_ns;
+
     if (block_job_is_cancelled(&job->common)) {
         return true;
     }
 
-    /* we need to yield so that bdrv_drain_all() returns.
-     * (without, VM does not reboot)
-     */
-    if (job->common.speed) {
-        uint64_t delay_ns = ratelimit_calculate_delay(&job->common.limit,
-                                                      job->bytes_read);
-        job->bytes_read = 0;
-        block_job_sleep_ns(&job->common, delay_ns);
-    } else {
-        block_job_sleep_ns(&job->common, 0);
-    }
+    /* We need to yield even for delay_ns = 0 so that bdrv_drain_all() can
+     * return. Without a yield, the VM would not reboot. */
+    delay_ns = block_job_ratelimit_get_delay(&job->common, job->bytes_read);
+    job->bytes_read = 0;
+    block_job_sleep_ns(&job->common, delay_ns);
 
     if (block_job_is_cancelled(&job->common)) {
         return true;
diff --git a/block/commit.c b/block/commit.c
index 46cbeaec3e..ba5df6aa0a 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -197,8 +197,8 @@ static void coroutine_fn commit_run(void *opaque)
         /* Publish progress */
         block_job_progress_update(&s->common, n);
 
-        if (copy && s->common.speed) {
-            delay_ns = ratelimit_calculate_delay(&s->common.limit, n);
+        if (copy) {
+            delay_ns = block_job_ratelimit_get_delay(&s->common, n);
         } else {
             delay_ns = 0;
         }
diff --git a/block/mirror.c b/block/mirror.c
index 21eb04f1d2..350f733d0a 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -447,10 +447,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
         assert(io_bytes);
         offset += io_bytes;
         nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity);
-        if (s->common.speed) {
-            delay_ns = ratelimit_calculate_delay(&s->common.limit,
-                                                 io_bytes_acct);
-        }
+        delay_ns = block_job_ratelimit_get_delay(&s->common, io_bytes_acct);
     }
     return delay_ns;
 }
diff --git a/block/stream.c b/block/stream.c
index 797d7c4f21..df9660d2fc 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -185,8 +185,8 @@ static void coroutine_fn stream_run(void *opaque)
 
         /* Publish progress */
         block_job_progress_update(&s->common, n);
-        if (copy && s->common.speed) {
-            delay_ns = ratelimit_calculate_delay(&s->common.limit, n);
+        if (copy) {
+            delay_ns = block_job_ratelimit_get_delay(&s->common, n);
         } else {
             delay_ns = 0;
         }
diff --git a/blockjob.c b/blockjob.c
index 5d050e65d3..b6e27c7f80 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -680,6 +680,15 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
     block_job_enter_cond(job, block_job_timer_pending);
 }
 
+int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
+{
+    if (!job->speed) {
+        return 0;
+    }
+
+    return ratelimit_calculate_delay(&job->limit, n);
+}
+
 void block_job_complete(BlockJob *job, Error **errp)
 {
     /* Should not be reachable via external interface for internal jobs */
-- 
2.13.6

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

* [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (4 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-11 22:28   ` Max Reitz
  2018-05-14 19:43   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all() Kevin Wolf
                   ` (36 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

The backup block job directly accesses the driver field in BlockJob. Add
a wrapper for getting it.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 include/block/blockjob.h | 7 +++++++
 block/backup.c           | 8 +++++---
 blockjob.c               | 5 +++++
 3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 5aa8a6aaec..0b57d53084 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -452,4 +452,11 @@ void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job);
  */
 bool block_job_is_internal(BlockJob *job);
 
+/**
+ * block_job_driver:
+ *
+ * Returns the driver associated with a block job.
+ */
+const BlockJobDriver *block_job_driver(BlockJob *job);
+
 #endif
diff --git a/block/backup.c b/block/backup.c
index cfdb6ecdf5..e14d99560d 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -47,6 +47,8 @@ typedef struct BackupBlockJob {
     HBitmap *copy_bitmap;
 } BackupBlockJob;
 
+static const BlockJobDriver backup_job_driver;
+
 /* See if in-flight requests overlap and wait for them to complete */
 static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
                                                        int64_t start,
@@ -241,7 +243,7 @@ void backup_do_checkpoint(BlockJob *job, Error **errp)
     BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
     int64_t len;
 
-    assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
+    assert(block_job_driver(job) == &backup_job_driver);
 
     if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) {
         error_setg(errp, "The backup job only supports block checkpoint in"
@@ -259,7 +261,7 @@ void backup_wait_for_overlapping_requests(BlockJob *job, int64_t offset,
     BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
     int64_t start, end;
 
-    assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
+    assert(block_job_driver(job) == &backup_job_driver);
 
     start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
     end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
@@ -272,7 +274,7 @@ void backup_cow_request_begin(CowRequest *req, BlockJob *job,
     BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
     int64_t start, end;
 
-    assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
+    assert(block_job_driver(job) == &backup_job_driver);
 
     start = QEMU_ALIGN_DOWN(offset, backup_job->cluster_size);
     end = QEMU_ALIGN_UP(offset + bytes, backup_job->cluster_size);
diff --git a/blockjob.c b/blockjob.c
index b6e27c7f80..985621e256 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -359,6 +359,11 @@ static bool block_job_started(BlockJob *job)
     return job->co;
 }
 
+const BlockJobDriver *block_job_driver(BlockJob *job)
+{
+    return job->driver;
+}
+
 /**
  * All jobs must allow a pause point before entering their job proper. This
  * ensures that jobs can be paused prior to being started, then resumed later.
-- 
2.13.6

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

* [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (5 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-09 17:30   ` Eric Blake
  2018-05-11 22:30   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create() Kevin Wolf
                   ` (35 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Commit 81193349 removed the only use of block_job_pause/resume_all(),
which was in bdrv_drain_all(). The functions are now unused and can be
removed.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob_int.h | 14 --------------
 blockjob.c                   | 27 ---------------------------
 2 files changed, 41 deletions(-)

diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 4d0439c51e..62ec964d09 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -174,20 +174,6 @@ void block_job_yield(BlockJob *job);
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
 
 /**
- * block_job_pause_all:
- *
- * Asynchronously pause all jobs.
- */
-void block_job_pause_all(void);
-
-/**
- * block_job_resume_all:
- *
- * Resume all block jobs.  Must be paired with a preceding block_job_pause_all.
- */
-void block_job_resume_all(void);
-
-/**
  * block_job_early_fail:
  * @bs: The block device.
  *
diff --git a/blockjob.c b/blockjob.c
index 985621e256..9943218a34 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -1010,19 +1010,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     return job;
 }
 
-void block_job_pause_all(void)
-{
-    BlockJob *job = NULL;
-    while ((job = block_job_next(job))) {
-        AioContext *aio_context = blk_get_aio_context(job->blk);
-
-        aio_context_acquire(aio_context);
-        block_job_ref(job);
-        block_job_pause(job);
-        aio_context_release(aio_context);
-    }
-}
-
 void block_job_early_fail(BlockJob *job)
 {
     assert(job->status == BLOCK_JOB_STATUS_CREATED);
@@ -1100,20 +1087,6 @@ void coroutine_fn block_job_pause_point(BlockJob *job)
     }
 }
 
-void block_job_resume_all(void)
-{
-    BlockJob *job, *next;
-
-    QLIST_FOREACH_SAFE(job, &block_jobs, job_list, next) {
-        AioContext *aio_context = blk_get_aio_context(job->blk);
-
-        aio_context_acquire(aio_context);
-        block_job_resume(job);
-        block_job_unref(job);
-        aio_context_release(aio_context);
-    }
-}
-
 /*
  * Conditionally enter a block_job pending a call to fn() while
  * under the block_job_lock critical section.
-- 
2.13.6

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

* [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (6 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-11 22:46   ` Max Reitz
  2018-05-14 19:57   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType Kevin Wolf
                   ` (34 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This is the first step towards creating an infrastructure for generic
background jobs that aren't tied to a block device. For now, Job only
stores its ID and JobDriver, the rest stays in BlockJob.

The following patches will move over more parts of BlockJob to Job if
they are meaningful outside the context of a block job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |  9 +++----
 include/block/blockjob_int.h |  4 +--
 include/qemu/job.h           | 60 ++++++++++++++++++++++++++++++++++++++++++++
 block/backup.c               |  4 ++-
 block/commit.c               |  4 ++-
 block/mirror.c               | 10 +++++---
 block/stream.c               |  4 ++-
 blockjob.c                   | 46 ++++++++++++++++-----------------
 job.c                        | 48 +++++++++++++++++++++++++++++++++++
 tests/test-bdrv-drain.c      |  4 ++-
 tests/test-blockjob-txn.c    |  4 ++-
 tests/test-blockjob.c        | 12 ++++++---
 MAINTAINERS                  |  2 ++
 Makefile.objs                |  2 +-
 14 files changed, 169 insertions(+), 44 deletions(-)
 create mode 100644 include/qemu/job.h
 create mode 100644 job.c

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 0b57d53084..8acc1a236a 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -26,6 +26,7 @@
 #ifndef BLOCKJOB_H
 #define BLOCKJOB_H
 
+#include "qemu/job.h"
 #include "block/block.h"
 #include "qemu/ratelimit.h"
 
@@ -40,6 +41,9 @@ typedef struct BlockJobTxn BlockJobTxn;
  * Long-running operation on a BlockDriverState.
  */
 typedef struct BlockJob {
+    /** Data belonging to the generic Job infrastructure */
+    Job job;
+
     /** The job type, including the job vtable.  */
     const BlockJobDriver *driver;
 
@@ -47,11 +51,6 @@ typedef struct BlockJob {
     BlockBackend *blk;
 
     /**
-     * The ID of the block job. May be NULL for internal jobs.
-     */
-    char *id;
-
-    /**
      * The coroutine that executes the job.  If not NULL, it is
      * reentered when busy is false and the job is cancelled.
      */
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 62ec964d09..e8eca44747 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -35,8 +35,8 @@
  * A class type for block job driver.
  */
 struct BlockJobDriver {
-    /** Derived BlockJob struct size */
-    size_t instance_size;
+    /** Generic JobDriver callbacks and settings */
+    JobDriver job_driver;
 
     /** String describing the operation, part of query-block-jobs QMP API */
     BlockJobType job_type;
diff --git a/include/qemu/job.h b/include/qemu/job.h
new file mode 100644
index 0000000000..b4b49f19e1
--- /dev/null
+++ b/include/qemu/job.h
@@ -0,0 +1,60 @@
+/*
+ * Declarations for background jobs
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012, 2018 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef JOB_H
+#define JOB_H
+
+typedef struct JobDriver JobDriver;
+
+/**
+ * Long-running operation.
+ */
+typedef struct Job {
+    /** The ID of the job. May be NULL for internal jobs. */
+    char *id;
+
+    /** The type of this job. */
+    const JobDriver *driver;
+} Job;
+
+/**
+ * Callbacks and other information about a Job driver.
+ */
+struct JobDriver {
+    /** Derived Job struct size */
+    size_t instance_size;
+};
+
+
+/**
+ * Create a new long-running job and return it.
+ *
+ * @job_id: The id of the newly-created job, or %NULL for internal jobs
+ * @driver: The class object for the newly-created job.
+ * @errp: Error object.
+ */
+void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
+
+#endif
diff --git a/block/backup.c b/block/backup.c
index e14d99560d..9e672bbd5e 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -523,7 +523,9 @@ static void coroutine_fn backup_run(void *opaque)
 }
 
 static const BlockJobDriver backup_job_driver = {
-    .instance_size          = sizeof(BackupBlockJob),
+    .job_driver = {
+        .instance_size          = sizeof(BackupBlockJob),
+    },
     .job_type               = BLOCK_JOB_TYPE_BACKUP,
     .start                  = backup_run,
     .commit                 = backup_commit,
diff --git a/block/commit.c b/block/commit.c
index ba5df6aa0a..18cbb2f9c4 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -215,7 +215,9 @@ out:
 }
 
 static const BlockJobDriver commit_job_driver = {
-    .instance_size = sizeof(CommitBlockJob),
+    .job_driver = {
+        .instance_size = sizeof(CommitBlockJob),
+    },
     .job_type      = BLOCK_JOB_TYPE_COMMIT,
     .start         = commit_run,
 };
diff --git a/block/mirror.c b/block/mirror.c
index 350f733d0a..c75c7100fb 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -913,7 +913,7 @@ static void mirror_complete(BlockJob *job, Error **errp)
 
     if (!s->synced) {
         error_setg(errp, "The active block job '%s' cannot be completed",
-                   job->id);
+                   job->job.id);
         return;
     }
 
@@ -986,7 +986,9 @@ static void mirror_drain(BlockJob *job)
 }
 
 static const BlockJobDriver mirror_job_driver = {
-    .instance_size          = sizeof(MirrorBlockJob),
+    .job_driver = {
+        .instance_size          = sizeof(MirrorBlockJob),
+    },
     .job_type               = BLOCK_JOB_TYPE_MIRROR,
     .start                  = mirror_run,
     .complete               = mirror_complete,
@@ -996,7 +998,9 @@ static const BlockJobDriver mirror_job_driver = {
 };
 
 static const BlockJobDriver commit_active_job_driver = {
-    .instance_size          = sizeof(MirrorBlockJob),
+    .job_driver = {
+        .instance_size          = sizeof(MirrorBlockJob),
+    },
     .job_type               = BLOCK_JOB_TYPE_COMMIT,
     .start                  = mirror_run,
     .complete               = mirror_complete,
diff --git a/block/stream.c b/block/stream.c
index df9660d2fc..f88fc75141 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -209,7 +209,9 @@ out:
 }
 
 static const BlockJobDriver stream_job_driver = {
-    .instance_size = sizeof(StreamBlockJob),
+    .job_driver = {
+        .instance_size = sizeof(StreamBlockJob),
+    },
     .job_type      = BLOCK_JOB_TYPE_STREAM,
     .start         = stream_run,
 };
diff --git a/blockjob.c b/blockjob.c
index 9943218a34..35d604d05a 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -34,7 +34,6 @@
 #include "qapi/qapi-events-block-core.h"
 #include "qapi/qmp/qerror.h"
 #include "qemu/coroutine.h"
-#include "qemu/id.h"
 #include "qemu/timer.h"
 
 /* Right now, this mutex is only needed to synchronize accesses to job->busy
@@ -92,7 +91,8 @@ static int block_job_apply_verb(BlockJob *job, BlockJobVerb bv, Error **errp)
         return 0;
     }
     error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'",
-               job->id, BlockJobStatus_str(job->status), BlockJobVerb_str(bv));
+               job->job.id, BlockJobStatus_str(job->status),
+               BlockJobVerb_str(bv));
     return -EPERM;
 }
 
@@ -159,7 +159,7 @@ BlockJob *block_job_get(const char *id)
     BlockJob *job;
 
     QLIST_FOREACH(job, &block_jobs, job_list) {
-        if (job->id && !strcmp(id, job->id)) {
+        if (job->job.id && !strcmp(id, job->job.id)) {
             return job;
         }
     }
@@ -247,7 +247,7 @@ void block_job_unref(BlockJob *job)
                                         block_job_detach_aio_context, job);
         blk_unref(job->blk);
         error_free(job->blocker);
-        g_free(job->id);
+        g_free(job->job.id);
         assert(!timer_pending(&job->sleep_timer));
         g_free(job);
     }
@@ -297,7 +297,7 @@ static char *child_job_get_parent_desc(BdrvChild *c)
     BlockJob *job = c->opaque;
     return g_strdup_printf("%s job '%s'",
                            BlockJobType_str(job->driver->job_type),
-                           job->id);
+                           job->job.id);
 }
 
 static void child_job_drained_begin(BdrvChild *c)
@@ -351,7 +351,7 @@ int block_job_add_bdrv(BlockJob *job, const char *name, BlockDriverState *bs,
 
 bool block_job_is_internal(BlockJob *job)
 {
-    return (job->id == NULL);
+    return (job->job.id == NULL);
 }
 
 static bool block_job_started(BlockJob *job)
@@ -697,13 +697,13 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
 void block_job_complete(BlockJob *job, Error **errp)
 {
     /* Should not be reachable via external interface for internal jobs */
-    assert(job->id);
+    assert(job->job.id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_COMPLETE, errp)) {
         return;
     }
     if (job->pause_count || job->cancelled || !job->driver->complete) {
         error_setg(errp, "The active block job '%s' cannot be completed",
-                   job->id);
+                   job->job.id);
         return;
     }
 
@@ -712,7 +712,7 @@ void block_job_complete(BlockJob *job, Error **errp)
 
 void block_job_finalize(BlockJob *job, Error **errp)
 {
-    assert(job && job->id);
+    assert(job && job->job.id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
         return;
     }
@@ -723,7 +723,7 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
 {
     BlockJob *job = *jobptr;
     /* similarly to _complete, this is QMP-interface only. */
-    assert(job->id);
+    assert(job->job.id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_DISMISS, errp)) {
         return;
     }
@@ -840,7 +840,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     }
     info = g_new0(BlockJobInfo, 1);
     info->type      = g_strdup(BlockJobType_str(job->driver->job_type));
-    info->device    = g_strdup(job->id);
+    info->device    = g_strdup(job->job.id);
     info->len       = job->len;
     info->busy      = atomic_read(&job->busy);
     info->paused    = job->pause_count > 0;
@@ -871,7 +871,7 @@ static void block_job_event_cancelled(BlockJob *job)
     }
 
     qapi_event_send_block_job_cancelled(job->driver->job_type,
-                                        job->id,
+                                        job->job.id,
                                         job->len,
                                         job->offset,
                                         job->speed,
@@ -885,7 +885,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
     }
 
     qapi_event_send_block_job_completed(job->driver->job_type,
-                                        job->id,
+                                        job->job.id,
                                         job->len,
                                         job->offset,
                                         job->speed,
@@ -899,7 +899,7 @@ static int block_job_event_pending(BlockJob *job)
     block_job_state_transition(job, BLOCK_JOB_STATUS_PENDING);
     if (!job->auto_finalize && !block_job_is_internal(job)) {
         qapi_event_send_block_job_pending(job->driver->job_type,
-                                          job->id,
+                                          job->job.id,
                                           &error_abort);
     }
     return 0;
@@ -937,12 +937,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
             error_setg(errp, "Cannot specify job ID for internal block job");
             return NULL;
         }
-
-        if (!id_wellformed(job_id)) {
-            error_setg(errp, "Invalid job ID '%s'", job_id);
-            return NULL;
-        }
-
         if (block_job_get(job_id)) {
             error_setg(errp, "Job ID '%s' already in use", job_id);
             return NULL;
@@ -956,9 +950,13 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
         return NULL;
     }
 
-    job = g_malloc0(driver->instance_size);
+    job = job_create(job_id, &driver->job_driver, errp);
+    if (job == NULL) {
+        blk_unref(blk);
+        return NULL;
+    }
+
     job->driver        = driver;
-    job->id            = g_strdup(job_id);
     job->blk           = blk;
     job->cb            = cb;
     job->opaque        = opaque;
@@ -1179,7 +1177,7 @@ void block_job_event_ready(BlockJob *job)
     }
 
     qapi_event_send_block_job_ready(job->driver->job_type,
-                                    job->id,
+                                    job->job.id,
                                     job->len,
                                     job->offset,
                                     job->speed, &error_abort);
@@ -1209,7 +1207,7 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
         abort();
     }
     if (!block_job_is_internal(job)) {
-        qapi_event_send_block_job_error(job->id,
+        qapi_event_send_block_job_error(job->job.id,
                                         is_read ? IO_OPERATION_TYPE_READ :
                                         IO_OPERATION_TYPE_WRITE,
                                         action, &error_abort);
diff --git a/job.c b/job.c
new file mode 100644
index 0000000000..87fd48468c
--- /dev/null
+++ b/job.c
@@ -0,0 +1,48 @@
+/*
+ * Background jobs (long-running operations)
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012, 2018 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qemu/job.h"
+#include "qemu/id.h"
+
+void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
+{
+    Job *job;
+
+    if (job_id) {
+        if (!id_wellformed(job_id)) {
+            error_setg(errp, "Invalid job ID '%s'", job_id);
+            return NULL;
+        }
+    }
+
+    job = g_malloc0(driver->instance_size);
+    job->driver        = driver;
+    job->id            = g_strdup(job_id);
+
+    return job;
+}
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index 7673de1062..fe9f412b39 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -520,7 +520,9 @@ static void test_job_complete(BlockJob *job, Error **errp)
 }
 
 BlockJobDriver test_job_driver = {
-    .instance_size  = sizeof(TestBlockJob),
+    .job_driver = {
+        .instance_size  = sizeof(TestBlockJob),
+    },
     .start          = test_job_start,
     .complete       = test_job_complete,
 };
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 5789893dda..48b12d1744 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -74,7 +74,9 @@ static void test_block_job_cb(void *opaque, int ret)
 }
 
 static const BlockJobDriver test_block_job_driver = {
-    .instance_size = sizeof(TestBlockJob),
+    .job_driver = {
+        .instance_size = sizeof(TestBlockJob),
+    },
     .start = test_block_job_run,
 };
 
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 8946bfd37b..b82026180a 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -17,7 +17,9 @@
 #include "sysemu/block-backend.h"
 
 static const BlockJobDriver test_block_job_driver = {
-    .instance_size = sizeof(BlockJob),
+    .job_driver = {
+        .instance_size = sizeof(BlockJob),
+    },
 };
 
 static void block_job_cb(void *opaque, int ret)
@@ -38,9 +40,9 @@ static BlockJob *mk_job(BlockBackend *blk, const char *id,
         g_assert_null(errp);
         g_assert_nonnull(job);
         if (id) {
-            g_assert_cmpstr(job->id, ==, id);
+            g_assert_cmpstr(job->job.id, ==, id);
         } else {
-            g_assert_cmpstr(job->id, ==, blk_name(blk));
+            g_assert_cmpstr(job->job.id, ==, blk_name(blk));
         }
     } else {
         g_assert_nonnull(errp);
@@ -192,7 +194,9 @@ static void coroutine_fn cancel_job_start(void *opaque)
 }
 
 static const BlockJobDriver test_cancel_driver = {
-    .instance_size = sizeof(CancelJob),
+    .job_driver = {
+        .instance_size = sizeof(CancelJob),
+    },
     .start         = cancel_job_start,
     .complete      = cancel_job_complete,
 };
diff --git a/MAINTAINERS b/MAINTAINERS
index 459e3594e1..a97f60d104 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1369,6 +1369,8 @@ L: qemu-block@nongnu.org
 S: Supported
 F: blockjob.c
 F: include/block/blockjob.h
+F: job.c
+F: include/block/job.h
 F: block/backup.c
 F: block/commit.c
 F: block/stream.c
diff --git a/Makefile.objs b/Makefile.objs
index c6c9b8fc21..92b73fc272 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -63,7 +63,7 @@ chardev-obj-y = chardev/
 # block-obj-y is code used by both qemu system emulation and qemu-img
 
 block-obj-y += nbd/
-block-obj-y += block.o blockjob.o
+block-obj-y += block.o blockjob.o job.o
 block-obj-y += block/ scsi/
 block-obj-y += qemu-io-cmds.o
 block-obj-$(CONFIG_REPLICATION) += replication.o
-- 
2.13.6

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

* [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (7 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-11 22:48   ` Max Reitz
  2018-05-14 20:05   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type Kevin Wolf
                   ` (33 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

QAPI types aren't externally visible, so we can rename them without
causing problems. Before we add a job type to Job, rename the enum
so it can be used for more than just block jobs.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 qapi/block-core.json         | 14 +++++++-------
 include/block/blockjob_int.h |  2 +-
 block/backup.c               |  2 +-
 block/commit.c               |  2 +-
 block/mirror.c               |  4 ++--
 block/stream.c               |  2 +-
 blockjob.c                   |  6 +++---
 7 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index 7da3bea6bc..031a30bba7 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1050,9 +1050,9 @@
   'data': ['top', 'full', 'none', 'incremental'] }
 
 ##
-# @BlockJobType:
+# @JobType:
 #
-# Type of a block job.
+# Type of a background job.
 #
 # @commit: block commit job type, see "block-commit"
 #
@@ -1064,7 +1064,7 @@
 #
 # Since: 1.7
 ##
-{ 'enum': 'BlockJobType',
+{ 'enum': 'JobType',
   'data': ['commit', 'stream', 'mirror', 'backup'] }
 
 ##
@@ -4489,7 +4489,7 @@
 #
 ##
 { 'event': 'BLOCK_JOB_COMPLETED',
-  'data': { 'type'  : 'BlockJobType',
+  'data': { 'type'  : 'JobType',
             'device': 'str',
             'len'   : 'int',
             'offset': 'int',
@@ -4525,7 +4525,7 @@
 #
 ##
 { 'event': 'BLOCK_JOB_CANCELLED',
-  'data': { 'type'  : 'BlockJobType',
+  'data': { 'type'  : 'JobType',
             'device': 'str',
             'len'   : 'int',
             'offset': 'int',
@@ -4590,7 +4590,7 @@
 #
 ##
 { 'event': 'BLOCK_JOB_READY',
-  'data': { 'type'  : 'BlockJobType',
+  'data': { 'type'  : 'JobType',
             'device': 'str',
             'len'   : 'int',
             'offset': 'int',
@@ -4617,7 +4617,7 @@
 #
 ##
 { 'event': 'BLOCK_JOB_PENDING',
-  'data': { 'type'  : 'BlockJobType',
+  'data': { 'type'  : 'JobType',
             'id'    : 'str' } }
 
 ##
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index e8eca44747..8e7e0a2f57 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -39,7 +39,7 @@ struct BlockJobDriver {
     JobDriver job_driver;
 
     /** String describing the operation, part of query-block-jobs QMP API */
-    BlockJobType job_type;
+    JobType job_type;
 
     /** Mandatory: Entrypoint for the Coroutine. */
     CoroutineEntry *start;
diff --git a/block/backup.c b/block/backup.c
index 9e672bbd5e..c49ea92dca 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -526,7 +526,7 @@ static const BlockJobDriver backup_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(BackupBlockJob),
     },
-    .job_type               = BLOCK_JOB_TYPE_BACKUP,
+    .job_type               = JOB_TYPE_BACKUP,
     .start                  = backup_run,
     .commit                 = backup_commit,
     .abort                  = backup_abort,
diff --git a/block/commit.c b/block/commit.c
index 18cbb2f9c4..afa2b2bacf 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -218,7 +218,7 @@ static const BlockJobDriver commit_job_driver = {
     .job_driver = {
         .instance_size = sizeof(CommitBlockJob),
     },
-    .job_type      = BLOCK_JOB_TYPE_COMMIT,
+    .job_type      = JOB_TYPE_COMMIT,
     .start         = commit_run,
 };
 
diff --git a/block/mirror.c b/block/mirror.c
index c75c7100fb..243a58f465 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -989,7 +989,7 @@ static const BlockJobDriver mirror_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
     },
-    .job_type               = BLOCK_JOB_TYPE_MIRROR,
+    .job_type               = JOB_TYPE_MIRROR,
     .start                  = mirror_run,
     .complete               = mirror_complete,
     .pause                  = mirror_pause,
@@ -1001,7 +1001,7 @@ static const BlockJobDriver commit_active_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
     },
-    .job_type               = BLOCK_JOB_TYPE_COMMIT,
+    .job_type               = JOB_TYPE_COMMIT,
     .start                  = mirror_run,
     .complete               = mirror_complete,
     .pause                  = mirror_pause,
diff --git a/block/stream.c b/block/stream.c
index f88fc75141..048bceb5d0 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -212,7 +212,7 @@ static const BlockJobDriver stream_job_driver = {
     .job_driver = {
         .instance_size = sizeof(StreamBlockJob),
     },
-    .job_type      = BLOCK_JOB_TYPE_STREAM,
+    .job_type      = JOB_TYPE_STREAM,
     .start         = stream_run,
 };
 
diff --git a/blockjob.c b/blockjob.c
index 35d604d05a..788b8cf686 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -296,7 +296,7 @@ static char *child_job_get_parent_desc(BdrvChild *c)
 {
     BlockJob *job = c->opaque;
     return g_strdup_printf("%s job '%s'",
-                           BlockJobType_str(job->driver->job_type),
+                           JobType_str(job->driver->job_type),
                            job->job.id);
 }
 
@@ -839,7 +839,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
         return NULL;
     }
     info = g_new0(BlockJobInfo, 1);
-    info->type      = g_strdup(BlockJobType_str(job->driver->job_type));
+    info->type      = g_strdup(JobType_str(job->driver->job_type));
     info->device    = g_strdup(job->job.id);
     info->len       = job->len;
     info->busy      = atomic_read(&job->busy);
@@ -972,7 +972,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
                    block_job_sleep_timer_cb, job);
 
     error_setg(&job->blocker, "block device is in use by block job: %s",
-               BlockJobType_str(driver->job_type));
+               JobType_str(driver->job_type));
     block_job_add_bdrv(job, "main node", bs, 0, BLK_PERM_ALL, &error_abort);
     bs->job = job;
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (8 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-11 22:53   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 11/42] job: Add job_delete() Kevin Wolf
                   ` (32 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves the job_type field from BlockJobDriver to JobDriver.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob_int.h |  3 ---
 include/qemu/job.h           | 11 +++++++++++
 block/backup.c               |  2 +-
 block/commit.c               |  2 +-
 block/mirror.c               |  4 ++--
 block/stream.c               |  2 +-
 blockjob.c                   | 16 +++++++---------
 job.c                        | 10 ++++++++++
 8 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 8e7e0a2f57..1e62d6dd30 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -38,9 +38,6 @@ struct BlockJobDriver {
     /** Generic JobDriver callbacks and settings */
     JobDriver job_driver;
 
-    /** String describing the operation, part of query-block-jobs QMP API */
-    JobType job_type;
-
     /** Mandatory: Entrypoint for the Coroutine. */
     CoroutineEntry *start;
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index b4b49f19e1..c87e951c8a 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -26,6 +26,8 @@
 #ifndef JOB_H
 #define JOB_H
 
+#include "qapi/qapi-types-block-core.h"
+
 typedef struct JobDriver JobDriver;
 
 /**
@@ -45,6 +47,9 @@ typedef struct Job {
 struct JobDriver {
     /** Derived Job struct size */
     size_t instance_size;
+
+    /** Enum describing the operation */
+    JobType job_type;
 };
 
 
@@ -57,4 +62,10 @@ struct JobDriver {
  */
 void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
 
+/** Returns the JobType of a given Job. */
+JobType job_type(Job *job);
+
+/** Returns the enum string for the JobType of a given Job. */
+const char *job_type_str(Job *job);
+
 #endif
diff --git a/block/backup.c b/block/backup.c
index c49ea92dca..baf8d432da 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -525,8 +525,8 @@ static void coroutine_fn backup_run(void *opaque)
 static const BlockJobDriver backup_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(BackupBlockJob),
+        .job_type               = JOB_TYPE_BACKUP,
     },
-    .job_type               = JOB_TYPE_BACKUP,
     .start                  = backup_run,
     .commit                 = backup_commit,
     .abort                  = backup_abort,
diff --git a/block/commit.c b/block/commit.c
index afa2b2bacf..32d29c890e 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -217,8 +217,8 @@ out:
 static const BlockJobDriver commit_job_driver = {
     .job_driver = {
         .instance_size = sizeof(CommitBlockJob),
+        .job_type      = JOB_TYPE_COMMIT,
     },
-    .job_type      = JOB_TYPE_COMMIT,
     .start         = commit_run,
 };
 
diff --git a/block/mirror.c b/block/mirror.c
index 243a58f465..054972bdc1 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -988,8 +988,8 @@ static void mirror_drain(BlockJob *job)
 static const BlockJobDriver mirror_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
+        .job_type               = JOB_TYPE_MIRROR,
     },
-    .job_type               = JOB_TYPE_MIRROR,
     .start                  = mirror_run,
     .complete               = mirror_complete,
     .pause                  = mirror_pause,
@@ -1000,8 +1000,8 @@ static const BlockJobDriver mirror_job_driver = {
 static const BlockJobDriver commit_active_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
+        .job_type               = JOB_TYPE_COMMIT,
     },
-    .job_type               = JOB_TYPE_COMMIT,
     .start                  = mirror_run,
     .complete               = mirror_complete,
     .pause                  = mirror_pause,
diff --git a/block/stream.c b/block/stream.c
index 048bceb5d0..cb723f190a 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -211,8 +211,8 @@ out:
 static const BlockJobDriver stream_job_driver = {
     .job_driver = {
         .instance_size = sizeof(StreamBlockJob),
+        .job_type      = JOB_TYPE_STREAM,
     },
-    .job_type      = JOB_TYPE_STREAM,
     .start         = stream_run,
 };
 
diff --git a/blockjob.c b/blockjob.c
index 788b8cf686..3cf28eb730 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -295,9 +295,7 @@ static void block_job_detach_aio_context(void *opaque)
 static char *child_job_get_parent_desc(BdrvChild *c)
 {
     BlockJob *job = c->opaque;
-    return g_strdup_printf("%s job '%s'",
-                           JobType_str(job->driver->job_type),
-                           job->job.id);
+    return g_strdup_printf("%s job '%s'", job_type_str(&job->job), job->job.id);
 }
 
 static void child_job_drained_begin(BdrvChild *c)
@@ -839,7 +837,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
         return NULL;
     }
     info = g_new0(BlockJobInfo, 1);
-    info->type      = g_strdup(JobType_str(job->driver->job_type));
+    info->type      = g_strdup(job_type_str(&job->job));
     info->device    = g_strdup(job->job.id);
     info->len       = job->len;
     info->busy      = atomic_read(&job->busy);
@@ -870,7 +868,7 @@ static void block_job_event_cancelled(BlockJob *job)
         return;
     }
 
-    qapi_event_send_block_job_cancelled(job->driver->job_type,
+    qapi_event_send_block_job_cancelled(job_type(&job->job),
                                         job->job.id,
                                         job->len,
                                         job->offset,
@@ -884,7 +882,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
         return;
     }
 
-    qapi_event_send_block_job_completed(job->driver->job_type,
+    qapi_event_send_block_job_completed(job_type(&job->job),
                                         job->job.id,
                                         job->len,
                                         job->offset,
@@ -898,7 +896,7 @@ static int block_job_event_pending(BlockJob *job)
 {
     block_job_state_transition(job, BLOCK_JOB_STATUS_PENDING);
     if (!job->auto_finalize && !block_job_is_internal(job)) {
-        qapi_event_send_block_job_pending(job->driver->job_type,
+        qapi_event_send_block_job_pending(job_type(&job->job),
                                           job->job.id,
                                           &error_abort);
     }
@@ -972,7 +970,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
                    block_job_sleep_timer_cb, job);
 
     error_setg(&job->blocker, "block device is in use by block job: %s",
-               JobType_str(driver->job_type));
+               job_type_str(&job->job));
     block_job_add_bdrv(job, "main node", bs, 0, BLK_PERM_ALL, &error_abort);
     bs->job = job;
 
@@ -1176,7 +1174,7 @@ void block_job_event_ready(BlockJob *job)
         return;
     }
 
-    qapi_event_send_block_job_ready(job->driver->job_type,
+    qapi_event_send_block_job_ready(job_type(&job->job),
                                     job->job.id,
                                     job->len,
                                     job->offset,
diff --git a/job.c b/job.c
index 87fd48468c..f00f401502 100644
--- a/job.c
+++ b/job.c
@@ -29,6 +29,16 @@
 #include "qemu/job.h"
 #include "qemu/id.h"
 
+JobType job_type(Job *job)
+{
+    return job->driver->job_type;
+}
+
+const char *job_type_str(Job *job)
+{
+    return JobType_str(job_type(job));
+}
+
 void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
 {
     Job *job;
-- 
2.13.6

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

* [Qemu-devel] [PATCH 11/42] job: Add job_delete()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (9 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-11 22:56   ` Max Reitz
                     ` (2 more replies)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs Kevin Wolf
                   ` (31 subsequent siblings)
  42 siblings, 3 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves freeing the Job object and its fields from block_job_unref()
to job_delete().

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/qemu/job.h | 3 +++
 blockjob.c         | 3 +--
 job.c              | 6 ++++++
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/include/qemu/job.h b/include/qemu/job.h
index c87e951c8a..ee1f5d1ef4 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -62,6 +62,9 @@ struct JobDriver {
  */
 void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
 
+/** Frees the @job object. */
+void job_delete(Job *job);
+
 /** Returns the JobType of a given Job. */
 JobType job_type(Job *job);
 
diff --git a/blockjob.c b/blockjob.c
index 3cf28eb730..f1bff2272e 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -247,9 +247,8 @@ void block_job_unref(BlockJob *job)
                                         block_job_detach_aio_context, job);
         blk_unref(job->blk);
         error_free(job->blocker);
-        g_free(job->job.id);
         assert(!timer_pending(&job->sleep_timer));
-        g_free(job);
+        job_delete(&job->job);
     }
 }
 
diff --git a/job.c b/job.c
index f00f401502..a36425498d 100644
--- a/job.c
+++ b/job.c
@@ -56,3 +56,9 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
 
     return job;
 }
+
+void job_delete(Job *job)
+{
+    g_free(job->id);
+    g_free(job);
+}
-- 
2.13.6

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

* [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (10 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 11/42] job: Add job_delete() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 13:59   ` Max Reitz
                     ` (2 more replies)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job Kevin Wolf
                   ` (30 subsequent siblings)
  42 siblings, 3 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves the job list from BlockJob to Job. Now we can check for
duplicate IDs in job_create().

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h |  3 ---
 include/qemu/job.h       | 19 +++++++++++++++++++
 blockjob.c               | 47 +++++++++++++++++++++++++----------------------
 job.c                    | 31 +++++++++++++++++++++++++++++++
 4 files changed, 75 insertions(+), 25 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 8acc1a236a..8946f118f3 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -105,9 +105,6 @@ typedef struct BlockJob {
      */
     bool deferred_to_main_loop;
 
-    /** Element of the list of block jobs */
-    QLIST_ENTRY(BlockJob) job_list;
-
     /** Status that is published by the query-block-jobs QMP API */
     BlockDeviceIoStatus iostatus;
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index ee1f5d1ef4..6555ab4c36 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -27,6 +27,7 @@
 #define JOB_H
 
 #include "qapi/qapi-types-block-core.h"
+#include "qemu/queue.h"
 
 typedef struct JobDriver JobDriver;
 
@@ -39,6 +40,9 @@ typedef struct Job {
 
     /** The type of this job. */
     const JobDriver *driver;
+
+    /** Element of the list of jobs */
+    QLIST_ENTRY(Job) job_list;
 } Job;
 
 /**
@@ -71,4 +75,19 @@ JobType job_type(Job *job);
 /** Returns the enum string for the JobType of a given Job. */
 const char *job_type_str(Job *job);
 
+/**
+ * Get the next element from the list of block jobs after @job, or the
+ * first one if @job is %NULL.
+ *
+ * Returns the requested job, or %NULL if there are no more jobs left.
+ */
+Job *job_next(Job *job);
+
+/**
+ * Get the job identified by @id (which must not be %NULL).
+ *
+ * Returns the requested job, or %NULL if it doesn't exist.
+ */
+Job *job_get(const char *id);
+
 #endif
diff --git a/blockjob.c b/blockjob.c
index f1bff2272e..29747dfdc0 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -129,8 +129,6 @@ struct BlockJobTxn {
     int refcnt;
 };
 
-static QLIST_HEAD(, BlockJob) block_jobs = QLIST_HEAD_INITIALIZER(block_jobs);
-
 /*
  * The block job API is composed of two categories of functions.
  *
@@ -146,25 +144,35 @@ static QLIST_HEAD(, BlockJob) block_jobs = QLIST_HEAD_INITIALIZER(block_jobs);
  * blockjob_int.h.
  */
 
-BlockJob *block_job_next(BlockJob *job)
+static bool is_block_job(Job *job)
 {
-    if (!job) {
-        return QLIST_FIRST(&block_jobs);
-    }
-    return QLIST_NEXT(job, job_list);
+    return job_type(job) == JOB_TYPE_BACKUP ||
+           job_type(job) == JOB_TYPE_COMMIT ||
+           job_type(job) == JOB_TYPE_MIRROR ||
+           job_type(job) == JOB_TYPE_STREAM;
+}
+
+BlockJob *block_job_next(BlockJob *bjob)
+{
+    Job *job = &bjob->job;
+
+    do {
+        job = job_next(job);
+    } while (job && !is_block_job(job));
+
+
+    return job ? container_of(job, BlockJob, job) : NULL;
 }
 
 BlockJob *block_job_get(const char *id)
 {
-    BlockJob *job;
+    Job *job = job_get(id);
 
-    QLIST_FOREACH(job, &block_jobs, job_list) {
-        if (job->job.id && !strcmp(id, job->job.id)) {
-            return job;
-        }
+    if (job && is_block_job(job)) {
+        return container_of(job, BlockJob, job);
+    } else {
+        return NULL;
     }
-
-    return NULL;
 }
 
 BlockJobTxn *block_job_txn_new(void)
@@ -239,7 +247,6 @@ void block_job_unref(BlockJob *job)
         assert(job->status == BLOCK_JOB_STATUS_NULL);
         assert(!job->txn);
         BlockDriverState *bs = blk_bs(job->blk);
-        QLIST_REMOVE(job, job_list);
         bs->job = NULL;
         block_job_remove_all_bdrv(job);
         blk_remove_aio_context_notifier(job->blk,
@@ -804,7 +811,7 @@ void block_job_cancel_sync_all(void)
     BlockJob *job;
     AioContext *aio_context;
 
-    while ((job = QLIST_FIRST(&block_jobs))) {
+    while ((job = block_job_next(NULL))) {
         aio_context = blk_get_aio_context(job->blk);
         aio_context_acquire(aio_context);
         block_job_cancel_sync(job);
@@ -934,10 +941,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
             error_setg(errp, "Cannot specify job ID for internal block job");
             return NULL;
         }
-        if (block_job_get(job_id)) {
-            error_setg(errp, "Job ID '%s' already in use", job_id);
-            return NULL;
-        }
     }
 
     blk = blk_new(perm, shared_perm);
@@ -953,6 +956,8 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
         return NULL;
     }
 
+    assert(is_block_job(&job->job));
+
     job->driver        = driver;
     job->blk           = blk;
     job->cb            = cb;
@@ -975,8 +980,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
     bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker);
 
-    QLIST_INSERT_HEAD(&block_jobs, job, job_list);
-
     blk_add_aio_context_notifier(blk, block_job_attached_aio_context,
                                  block_job_detach_aio_context, job);
 
diff --git a/job.c b/job.c
index a36425498d..00f7968c50 100644
--- a/job.c
+++ b/job.c
@@ -29,6 +29,8 @@
 #include "qemu/job.h"
 #include "qemu/id.h"
 
+static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
+
 JobType job_type(Job *job)
 {
     return job->driver->job_type;
@@ -39,6 +41,27 @@ const char *job_type_str(Job *job)
     return JobType_str(job_type(job));
 }
 
+Job *job_next(Job *job)
+{
+    if (!job) {
+        return QLIST_FIRST(&jobs);
+    }
+    return QLIST_NEXT(job, job_list);
+}
+
+Job *job_get(const char *id)
+{
+    Job *job;
+
+    QLIST_FOREACH(job, &jobs, job_list) {
+        if (job->id && !strcmp(id, job->id)) {
+            return job;
+        }
+    }
+
+    return NULL;
+}
+
 void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
 {
     Job *job;
@@ -48,17 +71,25 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
             error_setg(errp, "Invalid job ID '%s'", job_id);
             return NULL;
         }
+        if (job_get(job_id)) {
+            error_setg(errp, "Job ID '%s' already in use", job_id);
+            return NULL;
+        }
     }
 
     job = g_malloc0(driver->instance_size);
     job->driver        = driver;
     job->id            = g_strdup(job_id);
 
+    QLIST_INSERT_HEAD(&jobs, job, job_list);
+
     return job;
 }
 
 void job_delete(Job *job)
 {
+    QLIST_REMOVE(job, job_list);
+
     g_free(job->id);
     g_free(job);
 }
-- 
2.13.6

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

* [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (11 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 14:20   ` Max Reitz
                     ` (2 more replies)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 14/42] job: Add reference counting Kevin Wolf
                   ` (29 subsequent siblings)
  42 siblings, 3 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves BlockJob.status and the closely related functions
(block_)job_state_transition() and (block_)job_apply_verb to Job. The
two QAPI enums are renamed to JobStatus and JobVerb.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 qapi/block-core.json     |  14 +++----
 include/block/blockjob.h |   3 --
 include/qemu/job.h       |  13 ++++++
 blockjob.c               | 102 +++++++++++------------------------------------
 job.c                    |  56 ++++++++++++++++++++++++++
 tests/test-blockjob.c    |  39 +++++++++---------
 block/trace-events       |   2 -
 trace-events             |   4 ++
 8 files changed, 122 insertions(+), 111 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index 031a30bba7..f86004fbc3 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1068,9 +1068,9 @@
   'data': ['commit', 'stream', 'mirror', 'backup'] }
 
 ##
-# @BlockJobVerb:
+# @JobVerb:
 #
-# Represents command verbs that can be applied to a blockjob.
+# Represents command verbs that can be applied to a job.
 #
 # @cancel: see @block-job-cancel
 #
@@ -1088,14 +1088,14 @@
 #
 # Since: 2.12
 ##
-{ 'enum': 'BlockJobVerb',
+{ 'enum': 'JobVerb',
   'data': ['cancel', 'pause', 'resume', 'set-speed', 'complete', 'dismiss',
            'finalize' ] }
 
 ##
-# @BlockJobStatus:
+# @JobStatus:
 #
-# Indicates the present state of a given blockjob in its lifetime.
+# Indicates the present state of a given job in its lifetime.
 #
 # @undefined: Erroneous, default state. Should not ever be visible.
 #
@@ -1134,7 +1134,7 @@
 #
 # Since: 2.12
 ##
-{ 'enum': 'BlockJobStatus',
+{ 'enum': 'JobStatus',
   'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby',
            'waiting', 'pending', 'aborting', 'concluded', 'null' ] }
 
@@ -1181,7 +1181,7 @@
   'data': {'type': 'str', 'device': 'str', 'len': 'int',
            'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int',
            'io-status': 'BlockDeviceIoStatus', 'ready': 'bool',
-           'status': 'BlockJobStatus',
+           'status': 'JobStatus',
            'auto-finalize': 'bool', 'auto-dismiss': 'bool',
            '*error': 'str' } }
 
diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 8946f118f3..96e5f67ed7 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -147,9 +147,6 @@ typedef struct BlockJob {
      */
     QEMUTimer sleep_timer;
 
-    /** Current state; See @BlockJobStatus for details. */
-    BlockJobStatus status;
-
     /** True if this job should automatically finalize itself */
     bool auto_finalize;
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 6555ab4c36..48a7dfc431 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -41,6 +41,9 @@ typedef struct Job {
     /** The type of this job. */
     const JobDriver *driver;
 
+    /** Current state; See @JobStatus for details. */
+    JobStatus status;
+
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 } Job;
@@ -90,4 +93,14 @@ Job *job_next(Job *job);
  */
 Job *job_get(const char *id);
 
+/**
+ * Check whether the verb @bv can be applied to @job in its current state.
+ * Returns 0 if the verb can be applied; otherwise errp is set and -EPERM
+ * returned.
+ */
+int job_apply_verb(Job *job, JobVerb bv, Error **errp);
+
+/* TODO To be removed from the public interface */
+void job_state_transition(Job *job, JobStatus s1);
+
 #endif
diff --git a/blockjob.c b/blockjob.c
index 29747dfdc0..408a7159f6 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -41,61 +41,6 @@
  * block_job_enter. */
 static QemuMutex block_job_mutex;
 
-/* BlockJob State Transition Table */
-bool BlockJobSTT[BLOCK_JOB_STATUS__MAX][BLOCK_JOB_STATUS__MAX] = {
-                                          /* U, C, R, P, Y, S, W, D, X, E, N */
-    /* U: */ [BLOCK_JOB_STATUS_UNDEFINED] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-    /* C: */ [BLOCK_JOB_STATUS_CREATED]   = {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1},
-    /* R: */ [BLOCK_JOB_STATUS_RUNNING]   = {0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0},
-    /* P: */ [BLOCK_JOB_STATUS_PAUSED]    = {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
-    /* Y: */ [BLOCK_JOB_STATUS_READY]     = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0},
-    /* S: */ [BLOCK_JOB_STATUS_STANDBY]   = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
-    /* W: */ [BLOCK_JOB_STATUS_WAITING]   = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
-    /* D: */ [BLOCK_JOB_STATUS_PENDING]   = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
-    /* X: */ [BLOCK_JOB_STATUS_ABORTING]  = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
-    /* E: */ [BLOCK_JOB_STATUS_CONCLUDED] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
-    /* N: */ [BLOCK_JOB_STATUS_NULL]      = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-};
-
-bool BlockJobVerbTable[BLOCK_JOB_VERB__MAX][BLOCK_JOB_STATUS__MAX] = {
-                                          /* U, C, R, P, Y, S, W, D, X, E, N */
-    [BLOCK_JOB_VERB_CANCEL]               = {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
-    [BLOCK_JOB_VERB_PAUSE]                = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
-    [BLOCK_JOB_VERB_RESUME]               = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
-    [BLOCK_JOB_VERB_SET_SPEED]            = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
-    [BLOCK_JOB_VERB_COMPLETE]             = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
-    [BLOCK_JOB_VERB_FINALIZE]             = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
-    [BLOCK_JOB_VERB_DISMISS]              = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
-};
-
-static void block_job_state_transition(BlockJob *job, BlockJobStatus s1)
-{
-    BlockJobStatus s0 = job->status;
-    assert(s1 >= 0 && s1 <= BLOCK_JOB_STATUS__MAX);
-    trace_block_job_state_transition(job, job->ret, BlockJobSTT[s0][s1] ?
-                                     "allowed" : "disallowed",
-                                     BlockJobStatus_str(s0),
-                                     BlockJobStatus_str(s1));
-    assert(BlockJobSTT[s0][s1]);
-    job->status = s1;
-}
-
-static int block_job_apply_verb(BlockJob *job, BlockJobVerb bv, Error **errp)
-{
-    assert(bv >= 0 && bv <= BLOCK_JOB_VERB__MAX);
-    trace_block_job_apply_verb(job, BlockJobStatus_str(job->status),
-                               BlockJobVerb_str(bv),
-                               BlockJobVerbTable[bv][job->status] ?
-                               "allowed" : "prohibited");
-    if (BlockJobVerbTable[bv][job->status]) {
-        return 0;
-    }
-    error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'",
-               job->job.id, BlockJobStatus_str(job->status),
-               BlockJobVerb_str(bv));
-    return -EPERM;
-}
-
 static void block_job_lock(void)
 {
     qemu_mutex_lock(&block_job_mutex);
@@ -244,7 +189,7 @@ static void block_job_detach_aio_context(void *opaque);
 void block_job_unref(BlockJob *job)
 {
     if (--job->refcnt == 0) {
-        assert(job->status == BLOCK_JOB_STATUS_NULL);
+        assert(job->job.status == JOB_STATUS_NULL);
         assert(!job->txn);
         BlockDriverState *bs = blk_bs(job->blk);
         bs->job = NULL;
@@ -396,7 +341,7 @@ void block_job_start(BlockJob *job)
     job->pause_count--;
     job->busy = true;
     job->paused = false;
-    block_job_state_transition(job, BLOCK_JOB_STATUS_RUNNING);
+    job_state_transition(&job->job, JOB_STATUS_RUNNING);
     bdrv_coroutine_enter(blk_bs(job->blk), job->co);
 }
 
@@ -408,7 +353,7 @@ static void block_job_decommission(BlockJob *job)
     job->paused = false;
     job->deferred_to_main_loop = true;
     block_job_txn_del_job(job);
-    block_job_state_transition(job, BLOCK_JOB_STATUS_NULL);
+    job_state_transition(&job->job, JOB_STATUS_NULL);
     block_job_unref(job);
 }
 
@@ -419,7 +364,7 @@ static void block_job_do_dismiss(BlockJob *job)
 
 static void block_job_conclude(BlockJob *job)
 {
-    block_job_state_transition(job, BLOCK_JOB_STATUS_CONCLUDED);
+    job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
     if (job->auto_dismiss || !block_job_started(job)) {
         block_job_do_dismiss(job);
     }
@@ -431,7 +376,7 @@ static void block_job_update_rc(BlockJob *job)
         job->ret = -ECANCELED;
     }
     if (job->ret) {
-        block_job_state_transition(job, BLOCK_JOB_STATUS_ABORTING);
+        job_state_transition(&job->job, JOB_STATUS_ABORTING);
     }
 }
 
@@ -639,7 +584,7 @@ static void block_job_completed_txn_success(BlockJob *job)
     BlockJobTxn *txn = job->txn;
     BlockJob *other_job;
 
-    block_job_state_transition(job, BLOCK_JOB_STATUS_WAITING);
+    job_state_transition(&job->job, JOB_STATUS_WAITING);
 
     /*
      * Successful completion, see if there are other running jobs in this
@@ -670,7 +615,7 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
 {
     int64_t old_speed = job->speed;
 
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_SET_SPEED, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_SET_SPEED, errp)) {
         return;
     }
     if (speed < 0) {
@@ -702,7 +647,7 @@ void block_job_complete(BlockJob *job, Error **errp)
 {
     /* Should not be reachable via external interface for internal jobs */
     assert(job->job.id);
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_COMPLETE, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_COMPLETE, errp)) {
         return;
     }
     if (job->pause_count || job->cancelled || !job->driver->complete) {
@@ -717,7 +662,7 @@ void block_job_complete(BlockJob *job, Error **errp)
 void block_job_finalize(BlockJob *job, Error **errp)
 {
     assert(job && job->job.id);
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_FINALIZE, errp)) {
         return;
     }
     block_job_do_finalize(job);
@@ -728,7 +673,7 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
     BlockJob *job = *jobptr;
     /* similarly to _complete, this is QMP-interface only. */
     assert(job->job.id);
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_DISMISS, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_DISMISS, errp)) {
         return;
     }
 
@@ -738,7 +683,7 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
 
 void block_job_user_pause(BlockJob *job, Error **errp)
 {
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_PAUSE, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_PAUSE, errp)) {
         return;
     }
     if (job->user_paused) {
@@ -761,7 +706,7 @@ void block_job_user_resume(BlockJob *job, Error **errp)
         error_setg(errp, "Can't resume a job that was not paused");
         return;
     }
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_RESUME, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_RESUME, errp)) {
         return;
     }
     block_job_iostatus_reset(job);
@@ -771,7 +716,7 @@ void block_job_user_resume(BlockJob *job, Error **errp)
 
 void block_job_cancel(BlockJob *job, bool force)
 {
-    if (job->status == BLOCK_JOB_STATUS_CONCLUDED) {
+    if (job->job.status == JOB_STATUS_CONCLUDED) {
         block_job_do_dismiss(job);
         return;
     }
@@ -787,7 +732,7 @@ void block_job_cancel(BlockJob *job, bool force)
 
 void block_job_user_cancel(BlockJob *job, bool force, Error **errp)
 {
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_CANCEL, errp)) {
+    if (job_apply_verb(&job->job, JOB_VERB_CANCEL, errp)) {
         return;
     }
     block_job_cancel(job, force);
@@ -852,7 +797,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info->speed     = job->speed;
     info->io_status = job->iostatus;
     info->ready     = job->ready;
-    info->status    = job->status;
+    info->status    = job->job.status;
     info->auto_finalize = job->auto_finalize;
     info->auto_dismiss  = job->auto_dismiss;
     info->has_error = job->ret != 0;
@@ -900,7 +845,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
 
 static int block_job_event_pending(BlockJob *job)
 {
-    block_job_state_transition(job, BLOCK_JOB_STATUS_PENDING);
+    job_state_transition(&job->job, JOB_STATUS_PENDING);
     if (!job->auto_finalize && !block_job_is_internal(job)) {
         qapi_event_send_block_job_pending(job_type(&job->job),
                                           job->job.id,
@@ -968,7 +913,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->refcnt        = 1;
     job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
-    block_job_state_transition(job, BLOCK_JOB_STATUS_CREATED);
     aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
                    QEMU_CLOCK_REALTIME, SCALE_NS,
                    block_job_sleep_timer_cb, job);
@@ -1010,7 +954,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
 void block_job_early_fail(BlockJob *job)
 {
-    assert(job->status == BLOCK_JOB_STATUS_CREATED);
+    assert(job->job.status == JOB_STATUS_CREATED);
     block_job_decommission(job);
 }
 
@@ -1070,14 +1014,14 @@ void coroutine_fn block_job_pause_point(BlockJob *job)
     }
 
     if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
-        BlockJobStatus status = job->status;
-        block_job_state_transition(job, status == BLOCK_JOB_STATUS_READY ? \
-                                   BLOCK_JOB_STATUS_STANDBY :           \
-                                   BLOCK_JOB_STATUS_PAUSED);
+        JobStatus status = job->job.status;
+        job_state_transition(&job->job, status == JOB_STATUS_READY
+                                        ? JOB_STATUS_STANDBY
+                                        : JOB_STATUS_PAUSED);
         job->paused = true;
         block_job_do_yield(job, -1);
         job->paused = false;
-        block_job_state_transition(job, status);
+        job_state_transition(&job->job, status);
     }
 
     if (job->driver->resume) {
@@ -1169,7 +1113,7 @@ void block_job_iostatus_reset(BlockJob *job)
 
 void block_job_event_ready(BlockJob *job)
 {
-    block_job_state_transition(job, BLOCK_JOB_STATUS_READY);
+    job_state_transition(&job->job, JOB_STATUS_READY);
     job->ready = true;
 
     if (block_job_is_internal(job)) {
diff --git a/job.c b/job.c
index 00f7968c50..93e846f7e2 100644
--- a/job.c
+++ b/job.c
@@ -28,9 +28,63 @@
 #include "qapi/error.h"
 #include "qemu/job.h"
 #include "qemu/id.h"
+#include "trace-root.h"
 
 static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
 
+/* Job State Transition Table */
+bool JobSTT[JOB_STATUS__MAX][JOB_STATUS__MAX] = {
+                                    /* U, C, R, P, Y, S, W, D, X, E, N */
+    /* U: */ [JOB_STATUS_UNDEFINED] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    /* C: */ [JOB_STATUS_CREATED]   = {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1},
+    /* R: */ [JOB_STATUS_RUNNING]   = {0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0},
+    /* P: */ [JOB_STATUS_PAUSED]    = {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
+    /* Y: */ [JOB_STATUS_READY]     = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0},
+    /* S: */ [JOB_STATUS_STANDBY]   = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
+    /* W: */ [JOB_STATUS_WAITING]   = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
+    /* D: */ [JOB_STATUS_PENDING]   = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
+    /* X: */ [JOB_STATUS_ABORTING]  = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
+    /* E: */ [JOB_STATUS_CONCLUDED] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+    /* N: */ [JOB_STATUS_NULL]      = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+};
+
+bool JobVerbTable[JOB_VERB__MAX][JOB_STATUS__MAX] = {
+                                    /* U, C, R, P, Y, S, W, D, X, E, N */
+    [JOB_VERB_CANCEL]               = {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
+    [JOB_VERB_PAUSE]                = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
+    [JOB_VERB_RESUME]               = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
+    [JOB_VERB_SET_SPEED]            = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
+    [JOB_VERB_COMPLETE]             = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
+    [JOB_VERB_FINALIZE]             = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
+    [JOB_VERB_DISMISS]              = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
+};
+
+/* TODO Make static once the whole state machine is in job.c */
+void job_state_transition(Job *job, JobStatus s1)
+{
+    JobStatus s0 = job->status;
+    assert(s1 >= 0 && s1 <= JOB_STATUS__MAX);
+    trace_job_state_transition(job, /* TODO re-enable: job->ret */ 0,
+                               JobSTT[s0][s1] ? "allowed" : "disallowed",
+                               JobStatus_str(s0), JobStatus_str(s1));
+    assert(JobSTT[s0][s1]);
+    job->status = s1;
+}
+
+int job_apply_verb(Job *job, JobVerb bv, Error **errp)
+{
+    assert(bv >= 0 && bv <= JOB_VERB__MAX);
+    trace_job_apply_verb(job, JobStatus_str(job->status), JobVerb_str(bv),
+                         JobVerbTable[bv][job->status] ?
+                         "allowed" : "prohibited");
+    if (JobVerbTable[bv][job->status]) {
+        return 0;
+    }
+    error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'",
+               job->id, JobStatus_str(job->status), JobVerb_str(bv));
+    return -EPERM;
+}
+
 JobType job_type(Job *job)
 {
     return job->driver->job_type;
@@ -81,6 +135,8 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
     job->driver        = driver;
     job->id            = g_strdup(job_id);
 
+    job_state_transition(job, JOB_STATUS_CREATED);
+
     QLIST_INSERT_HEAD(&jobs, job, job_list);
 
     return job;
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index b82026180a..6ccd585dee 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -211,7 +211,7 @@ static CancelJob *create_common(BlockJob **pjob)
     job = mk_job(blk, "Steve", &test_cancel_driver, true,
                  BLOCK_JOB_MANUAL_FINALIZE | BLOCK_JOB_MANUAL_DISMISS);
     block_job_ref(job);
-    assert(job->status == BLOCK_JOB_STATUS_CREATED);
+    assert(job->job.status == JOB_STATUS_CREATED);
     s = container_of(job, CancelJob, common);
     s->blk = blk;
 
@@ -223,15 +223,14 @@ static void cancel_common(CancelJob *s)
 {
     BlockJob *job = &s->common;
     BlockBackend *blk = s->blk;
-    BlockJobStatus sts = job->status;
+    JobStatus sts = job->job.status;
 
     block_job_cancel_sync(job);
-    if ((sts != BLOCK_JOB_STATUS_CREATED) &&
-        (sts != BLOCK_JOB_STATUS_CONCLUDED)) {
+    if (sts != JOB_STATUS_CREATED && sts != JOB_STATUS_CONCLUDED) {
         BlockJob *dummy = job;
         block_job_dismiss(&dummy, &error_abort);
     }
-    assert(job->status == BLOCK_JOB_STATUS_NULL);
+    assert(job->job.status == JOB_STATUS_NULL);
     block_job_unref(job);
     destroy_blk(blk);
 }
@@ -253,7 +252,7 @@ static void test_cancel_running(void)
     s = create_common(&job);
 
     block_job_start(job);
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
+    assert(job->job.status == JOB_STATUS_RUNNING);
 
     cancel_common(s);
 }
@@ -266,11 +265,11 @@ static void test_cancel_paused(void)
     s = create_common(&job);
 
     block_job_start(job);
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
+    assert(job->job.status == JOB_STATUS_RUNNING);
 
     block_job_user_pause(job, &error_abort);
     block_job_enter(job);
-    assert(job->status == BLOCK_JOB_STATUS_PAUSED);
+    assert(job->job.status == JOB_STATUS_PAUSED);
 
     cancel_common(s);
 }
@@ -283,11 +282,11 @@ static void test_cancel_ready(void)
     s = create_common(&job);
 
     block_job_start(job);
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
+    assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
     block_job_enter(job);
-    assert(job->status == BLOCK_JOB_STATUS_READY);
+    assert(job->job.status == JOB_STATUS_READY);
 
     cancel_common(s);
 }
@@ -300,15 +299,15 @@ static void test_cancel_standby(void)
     s = create_common(&job);
 
     block_job_start(job);
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
+    assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
     block_job_enter(job);
-    assert(job->status == BLOCK_JOB_STATUS_READY);
+    assert(job->job.status == JOB_STATUS_READY);
 
     block_job_user_pause(job, &error_abort);
     block_job_enter(job);
-    assert(job->status == BLOCK_JOB_STATUS_STANDBY);
+    assert(job->job.status == JOB_STATUS_STANDBY);
 
     cancel_common(s);
 }
@@ -321,18 +320,18 @@ static void test_cancel_pending(void)
     s = create_common(&job);
 
     block_job_start(job);
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
+    assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
     block_job_enter(job);
-    assert(job->status == BLOCK_JOB_STATUS_READY);
+    assert(job->job.status == JOB_STATUS_READY);
 
     block_job_complete(job, &error_abort);
     block_job_enter(job);
     while (!s->completed) {
         aio_poll(qemu_get_aio_context(), true);
     }
-    assert(job->status == BLOCK_JOB_STATUS_PENDING);
+    assert(job->job.status == JOB_STATUS_PENDING);
 
     cancel_common(s);
 }
@@ -345,21 +344,21 @@ static void test_cancel_concluded(void)
     s = create_common(&job);
 
     block_job_start(job);
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
+    assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
     block_job_enter(job);
-    assert(job->status == BLOCK_JOB_STATUS_READY);
+    assert(job->job.status == JOB_STATUS_READY);
 
     block_job_complete(job, &error_abort);
     block_job_enter(job);
     while (!s->completed) {
         aio_poll(qemu_get_aio_context(), true);
     }
-    assert(job->status == BLOCK_JOB_STATUS_PENDING);
+    assert(job->job.status == JOB_STATUS_PENDING);
 
     block_job_finalize(job, &error_abort);
-    assert(job->status == BLOCK_JOB_STATUS_CONCLUDED);
+    assert(job->job.status == JOB_STATUS_CONCLUDED);
 
     cancel_common(s);
 }
diff --git a/block/trace-events b/block/trace-events
index f8c50b4063..93b927908a 100644
--- a/block/trace-events
+++ b/block/trace-events
@@ -6,8 +6,6 @@ bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d"
 
 # blockjob.c
 block_job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d"
-block_job_state_transition(void *job,  int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)"
-block_job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
 
 # block/block-backend.c
 blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
diff --git a/trace-events b/trace-events
index ed71f44649..2507e1327d 100644
--- a/trace-events
+++ b/trace-events
@@ -104,6 +104,10 @@ gdbstub_err_invalid_rle(void) "got invalid RLE sequence"
 gdbstub_err_checksum_invalid(uint8_t ch) "got invalid command checksum digit: 0x%02x"
 gdbstub_err_checksum_incorrect(uint8_t expected, uint8_t got) "got command packet with incorrect checksum, expected=0x%02x, received=0x%02x"
 
+# job.c
+job_state_transition(void *job,  int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)"
+job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
+
 ### Guest events, keep at bottom
 
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (12 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 14:29   ` Max Reitz
                     ` (2 more replies)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job Kevin Wolf
                   ` (28 subsequent siblings)
  42 siblings, 3 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves reference counting from BlockJob to Job.

In order to keep calling the BlockJob cleanup code when the job is
deleted via job_unref(), introduce a new JobDriver.free callback. Every
block job must use block_job_free() for this callback, this is asserted
in block_job_create().

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     | 21 -------------------
 include/block/blockjob_int.h |  7 +++++++
 include/qemu/job.h           | 19 ++++++++++++++++--
 block/backup.c               |  1 +
 block/commit.c               |  1 +
 block/mirror.c               |  2 ++
 block/stream.c               |  1 +
 blockjob.c                   | 48 +++++++++++++++++++-------------------------
 job.c                        | 22 ++++++++++++++++----
 qemu-img.c                   |  4 ++--
 tests/test-bdrv-drain.c      |  1 +
 tests/test-blockjob-txn.c    |  1 +
 tests/test-blockjob.c        |  6 ++++--
 13 files changed, 76 insertions(+), 58 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 96e5f67ed7..5e32e719b6 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -132,9 +132,6 @@ typedef struct BlockJob {
     /** The opaque value that is passed to the completion function.  */
     void *opaque;
 
-    /** Reference count of the block job */
-    int refcnt;
-
     /** True when job has reported completion by calling block_job_completed. */
     bool completed;
 
@@ -400,24 +397,6 @@ void block_job_iostatus_reset(BlockJob *job);
 BlockJobTxn *block_job_txn_new(void);
 
 /**
- * block_job_ref:
- *
- * Add a reference to BlockJob refcnt, it will be decreased with
- * block_job_unref, and then be freed if it comes to be the last
- * reference.
- */
-void block_job_ref(BlockJob *job);
-
-/**
- * block_job_unref:
- *
- * Release a reference that was previously acquired with block_job_ref
- * or block_job_create. If it's the last reference to the object, it will be
- * freed.
- */
-void block_job_unref(BlockJob *job);
-
-/**
  * block_job_txn_unref:
  *
  * Release a reference that was previously acquired with block_job_txn_add_job
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 1e62d6dd30..6f0fe3c48d 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -144,6 +144,13 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
                        BlockCompletionFunc *cb, void *opaque, Error **errp);
 
 /**
+ * block_job_free:
+ * Callback to be used for JobDriver.free in all block jobs. Frees block job
+ * specific resources in @job.
+ */
+void block_job_free(Job *job);
+
+/**
  * block_job_sleep_ns:
  * @job: The job that calls the function.
  * @ns: How many nanoseconds to stop for.
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 48a7dfc431..64e988f305 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -41,6 +41,9 @@ typedef struct Job {
     /** The type of this job. */
     const JobDriver *driver;
 
+    /** Reference count of the block job */
+    int refcnt;
+
     /** Current state; See @JobStatus for details. */
     JobStatus status;
 
@@ -57,6 +60,9 @@ struct JobDriver {
 
     /** Enum describing the operation */
     JobType job_type;
+
+    /** Called when the job is freed */
+    void (*free)(Job *job);
 };
 
 
@@ -69,8 +75,17 @@ struct JobDriver {
  */
 void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
 
-/** Frees the @job object. */
-void job_delete(Job *job);
+/**
+ * Add a reference to Job refcnt, it will be decreased with job_unref, and then
+ * be freed if it comes to be the last reference.
+ */
+void job_ref(Job *job);
+
+/**
+ * Release a reference that was previously acquired with job_ref() or
+ * job_create(). If it's the last reference to the object, it will be freed.
+ */
+void job_unref(Job *job);
 
 /** Returns the JobType of a given Job. */
 JobType job_type(Job *job);
diff --git a/block/backup.c b/block/backup.c
index baf8d432da..cfdb89d977 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -526,6 +526,7 @@ static const BlockJobDriver backup_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(BackupBlockJob),
         .job_type               = JOB_TYPE_BACKUP,
+        .free                   = block_job_free,
     },
     .start                  = backup_run,
     .commit                 = backup_commit,
diff --git a/block/commit.c b/block/commit.c
index 32d29c890e..925c96abe7 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -218,6 +218,7 @@ static const BlockJobDriver commit_job_driver = {
     .job_driver = {
         .instance_size = sizeof(CommitBlockJob),
         .job_type      = JOB_TYPE_COMMIT,
+        .free          = block_job_free,
     },
     .start         = commit_run,
 };
diff --git a/block/mirror.c b/block/mirror.c
index 054972bdc1..9b1e5c0849 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -989,6 +989,7 @@ static const BlockJobDriver mirror_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
         .job_type               = JOB_TYPE_MIRROR,
+        .free                   = block_job_free,
     },
     .start                  = mirror_run,
     .complete               = mirror_complete,
@@ -1001,6 +1002,7 @@ static const BlockJobDriver commit_active_job_driver = {
     .job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
         .job_type               = JOB_TYPE_COMMIT,
+        .free                   = block_job_free,
     },
     .start                  = mirror_run,
     .complete               = mirror_complete,
diff --git a/block/stream.c b/block/stream.c
index cb723f190a..7273d2213c 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -212,6 +212,7 @@ static const BlockJobDriver stream_job_driver = {
     .job_driver = {
         .instance_size = sizeof(StreamBlockJob),
         .job_type      = JOB_TYPE_STREAM,
+        .free          = block_job_free,
     },
     .start         = stream_run,
 };
diff --git a/blockjob.c b/blockjob.c
index 408a7159f6..1fa5173bb9 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -177,31 +177,25 @@ static void block_job_resume(BlockJob *job)
     block_job_enter(job);
 }
 
-void block_job_ref(BlockJob *job)
-{
-    ++job->refcnt;
-}
-
 static void block_job_attached_aio_context(AioContext *new_context,
                                            void *opaque);
 static void block_job_detach_aio_context(void *opaque);
 
-void block_job_unref(BlockJob *job)
+void block_job_free(Job *job)
 {
-    if (--job->refcnt == 0) {
-        assert(job->job.status == JOB_STATUS_NULL);
-        assert(!job->txn);
-        BlockDriverState *bs = blk_bs(job->blk);
-        bs->job = NULL;
-        block_job_remove_all_bdrv(job);
-        blk_remove_aio_context_notifier(job->blk,
-                                        block_job_attached_aio_context,
-                                        block_job_detach_aio_context, job);
-        blk_unref(job->blk);
-        error_free(job->blocker);
-        assert(!timer_pending(&job->sleep_timer));
-        job_delete(&job->job);
-    }
+    BlockJob *bjob = container_of(job, BlockJob, job);
+    BlockDriverState *bs = blk_bs(bjob->blk);
+
+    assert(!bjob->txn);
+
+    bs->job = NULL;
+    block_job_remove_all_bdrv(bjob);
+    blk_remove_aio_context_notifier(bjob->blk,
+                                    block_job_attached_aio_context,
+                                    block_job_detach_aio_context, bjob);
+    blk_unref(bjob->blk);
+    error_free(bjob->blocker);
+    assert(!timer_pending(&bjob->sleep_timer));
 }
 
 static void block_job_attached_aio_context(AioContext *new_context,
@@ -232,7 +226,7 @@ static void block_job_detach_aio_context(void *opaque)
     BlockJob *job = opaque;
 
     /* In case the job terminates during aio_poll()... */
-    block_job_ref(job);
+    job_ref(&job->job);
 
     block_job_pause(job);
 
@@ -240,7 +234,7 @@ static void block_job_detach_aio_context(void *opaque)
         block_job_drain(job);
     }
 
-    block_job_unref(job);
+    job_unref(&job->job);
 }
 
 static char *child_job_get_parent_desc(BdrvChild *c)
@@ -354,7 +348,7 @@ static void block_job_decommission(BlockJob *job)
     job->deferred_to_main_loop = true;
     block_job_txn_del_job(job);
     job_state_transition(&job->job, JOB_STATUS_NULL);
-    block_job_unref(job);
+    job_unref(&job->job);
 }
 
 static void block_job_do_dismiss(BlockJob *job)
@@ -493,14 +487,14 @@ static int block_job_finish_sync(BlockJob *job,
 
     assert(blk_bs(job->blk)->job == job);
 
-    block_job_ref(job);
+    job_ref(&job->job);
 
     if (finish) {
         finish(job, &local_err);
     }
     if (local_err) {
         error_propagate(errp, local_err);
-        block_job_unref(job);
+        job_unref(&job->job);
         return -EBUSY;
     }
     /* block_job_drain calls block_job_enter, and it should be enough to
@@ -513,7 +507,7 @@ static int block_job_finish_sync(BlockJob *job,
         aio_poll(qemu_get_aio_context(), true);
     }
     ret = (job->cancelled && job->ret == 0) ? -ECANCELED : job->ret;
-    block_job_unref(job);
+    job_unref(&job->job);
     return ret;
 }
 
@@ -902,6 +896,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     }
 
     assert(is_block_job(&job->job));
+    assert(job->job.driver->free == &block_job_free);
 
     job->driver        = driver;
     job->blk           = blk;
@@ -910,7 +905,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->busy          = false;
     job->paused        = true;
     job->pause_count   = 1;
-    job->refcnt        = 1;
     job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
     aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
diff --git a/job.c b/job.c
index 93e846f7e2..67b09cb54d 100644
--- a/job.c
+++ b/job.c
@@ -134,6 +134,7 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
     job = g_malloc0(driver->instance_size);
     job->driver        = driver;
     job->id            = g_strdup(job_id);
+    job->refcnt        = 1;
 
     job_state_transition(job, JOB_STATUS_CREATED);
 
@@ -142,10 +143,23 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
     return job;
 }
 
-void job_delete(Job *job)
+void job_ref(Job *job)
 {
-    QLIST_REMOVE(job, job_list);
+    ++job->refcnt;
+}
+
+void job_unref(Job *job)
+{
+    if (--job->refcnt == 0) {
+        assert(job->status == JOB_STATUS_NULL);
 
-    g_free(job->id);
-    g_free(job);
+        if (job->driver->free) {
+            job->driver->free(job);
+        }
+
+        QLIST_REMOVE(job, job_list);
+
+        g_free(job->id);
+        g_free(job);
+    }
 }
diff --git a/qemu-img.c b/qemu-img.c
index ea62d2d61e..82f69269ae 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -862,7 +862,7 @@ static void run_block_job(BlockJob *job, Error **errp)
     int ret = 0;
 
     aio_context_acquire(aio_context);
-    block_job_ref(job);
+    job_ref(&job->job);
     do {
         aio_poll(aio_context, true);
         qemu_progress_print(job->len ?
@@ -874,7 +874,7 @@ static void run_block_job(BlockJob *job, Error **errp)
     } else {
         ret = job->ret;
     }
-    block_job_unref(job);
+    job_unref(&job->job);
     aio_context_release(aio_context);
 
     /* publish completion progress only when success */
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index fe9f412b39..f9e37d479c 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -522,6 +522,7 @@ static void test_job_complete(BlockJob *job, Error **errp)
 BlockJobDriver test_job_driver = {
     .job_driver = {
         .instance_size  = sizeof(TestBlockJob),
+        .free           = block_job_free,
     },
     .start          = test_job_start,
     .complete       = test_job_complete,
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 48b12d1744..b49b28ca27 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -76,6 +76,7 @@ static void test_block_job_cb(void *opaque, int ret)
 static const BlockJobDriver test_block_job_driver = {
     .job_driver = {
         .instance_size = sizeof(TestBlockJob),
+        .free          = block_job_free,
     },
     .start = test_block_job_run,
 };
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 6ccd585dee..e24fc3f140 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -19,6 +19,7 @@
 static const BlockJobDriver test_block_job_driver = {
     .job_driver = {
         .instance_size = sizeof(BlockJob),
+        .free          = block_job_free,
     },
 };
 
@@ -196,6 +197,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
 static const BlockJobDriver test_cancel_driver = {
     .job_driver = {
         .instance_size = sizeof(CancelJob),
+        .free          = block_job_free,
     },
     .start         = cancel_job_start,
     .complete      = cancel_job_complete,
@@ -210,7 +212,7 @@ static CancelJob *create_common(BlockJob **pjob)
     blk = create_blk(NULL);
     job = mk_job(blk, "Steve", &test_cancel_driver, true,
                  BLOCK_JOB_MANUAL_FINALIZE | BLOCK_JOB_MANUAL_DISMISS);
-    block_job_ref(job);
+    job_ref(&job->job);
     assert(job->job.status == JOB_STATUS_CREATED);
     s = container_of(job, CancelJob, common);
     s->blk = blk;
@@ -231,7 +233,7 @@ static void cancel_common(CancelJob *s)
         block_job_dismiss(&dummy, &error_abort);
     }
     assert(job->job.status == JOB_STATUS_NULL);
-    block_job_unref(job);
+    job_unref(&job->job);
     destroy_blk(blk);
 }
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (13 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 14/42] job: Add reference counting Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 14:39   ` Max Reitz
  2018-05-14 21:53   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context Kevin Wolf
                   ` (27 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

We cannot yet move the whole logic around job cancelling to Job because
it depends on quite a few other things that are still only in BlockJob,
but we can move the cancelled field at least.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |  8 --------
 include/block/blockjob_int.h |  8 --------
 include/qemu/job.h           | 11 +++++++++++
 block/backup.c               |  6 +++---
 block/commit.c               |  4 ++--
 block/mirror.c               | 20 ++++++++++----------
 block/stream.c               |  4 ++--
 blockjob.c                   | 28 +++++++++++++---------------
 job.c                        |  5 +++++
 tests/test-blockjob-txn.c    |  6 +++---
 tests/test-blockjob.c        |  2 +-
 11 files changed, 50 insertions(+), 52 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 5e32e719b6..04efc94ffc 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -57,14 +57,6 @@ typedef struct BlockJob {
     Coroutine *co;
 
     /**
-     * Set to true if the job should cancel itself.  The flag must
-     * always be tested just before toggling the busy flag from false
-     * to true.  After a job has been cancelled, it should only yield
-     * if #aio_poll will ("sooner or later") reenter the coroutine.
-     */
-    bool cancelled;
-
-    /**
      * Set to true if the job should abort immediately without waiting
      * for data to be in sync.
      */
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 6f0fe3c48d..d64f30e6b0 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -196,14 +196,6 @@ void block_job_early_fail(BlockJob *job);
 void block_job_completed(BlockJob *job, int ret);
 
 /**
- * block_job_is_cancelled:
- * @job: The job being queried.
- *
- * Returns whether the job is scheduled for cancellation.
- */
-bool block_job_is_cancelled(BlockJob *job);
-
-/**
  * block_job_pause_point:
  * @job: The job that is ready to pause.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 64e988f305..14c6288517 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -47,6 +47,14 @@ typedef struct Job {
     /** Current state; See @JobStatus for details. */
     JobStatus status;
 
+    /**
+     * Set to true if the job should cancel itself.  The flag must
+     * always be tested just before toggling the busy flag from false
+     * to true.  After a job has been cancelled, it should only yield
+     * if #aio_poll will ("sooner or later") reenter the coroutine.
+     */
+    bool cancelled;
+
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 } Job;
@@ -93,6 +101,9 @@ JobType job_type(Job *job);
 /** Returns the enum string for the JobType of a given Job. */
 const char *job_type_str(Job *job);
 
+/** Returns whether the job is scheduled for cancellation. */
+bool job_is_cancelled(Job *job);
+
 /**
  * Get the next element from the list of block jobs after @job, or the
  * first one if @job is %NULL.
diff --git a/block/backup.c b/block/backup.c
index cfdb89d977..ef0aa0e24e 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -329,7 +329,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
 {
     uint64_t delay_ns;
 
-    if (block_job_is_cancelled(&job->common)) {
+    if (job_is_cancelled(&job->common.job)) {
         return true;
     }
 
@@ -339,7 +339,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
     job->bytes_read = 0;
     block_job_sleep_ns(&job->common, delay_ns);
 
-    if (block_job_is_cancelled(&job->common)) {
+    if (job_is_cancelled(&job->common.job)) {
         return true;
     }
 
@@ -441,7 +441,7 @@ static void coroutine_fn backup_run(void *opaque)
     if (job->sync_mode == MIRROR_SYNC_MODE_NONE) {
         /* All bits are set in copy_bitmap to allow any cluster to be copied.
          * This does not actually require them to be copied. */
-        while (!block_job_is_cancelled(&job->common)) {
+        while (!job_is_cancelled(&job->common.job)) {
             /* Yield until the job is cancelled.  We just let our before_write
              * notify callback service CoW requests. */
             block_job_yield(&job->common);
diff --git a/block/commit.c b/block/commit.c
index 925c96abe7..85baea8f92 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -90,7 +90,7 @@ static void commit_complete(BlockJob *job, void *opaque)
      * the normal backing chain can be restored. */
     blk_unref(s->base);
 
-    if (!block_job_is_cancelled(&s->common) && ret == 0) {
+    if (!job_is_cancelled(&s->common.job) && ret == 0) {
         /* success */
         ret = bdrv_drop_intermediate(s->commit_top_bs, base,
                                      s->backing_file_str);
@@ -172,7 +172,7 @@ static void coroutine_fn commit_run(void *opaque)
          * with no pending I/O here so that bdrv_drain_all() returns.
          */
         block_job_sleep_ns(&s->common, delay_ns);
-        if (block_job_is_cancelled(&s->common)) {
+        if (job_is_cancelled(&s->common.job)) {
             break;
         }
         /* Copy if allocated above the base */
diff --git a/block/mirror.c b/block/mirror.c
index 9b1e5c0849..163d83e34a 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -622,7 +622,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s)
 
             mirror_throttle(s);
 
-            if (block_job_is_cancelled(&s->common)) {
+            if (job_is_cancelled(&s->common.job)) {
                 s->initial_zeroing_ongoing = false;
                 return 0;
             }
@@ -650,7 +650,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s)
 
         mirror_throttle(s);
 
-        if (block_job_is_cancelled(&s->common)) {
+        if (job_is_cancelled(&s->common.job)) {
             return 0;
         }
 
@@ -695,7 +695,7 @@ static void coroutine_fn mirror_run(void *opaque)
                                  checking for a NULL string */
     int ret = 0;
 
-    if (block_job_is_cancelled(&s->common)) {
+    if (job_is_cancelled(&s->common.job)) {
         goto immediate_exit;
     }
 
@@ -729,10 +729,10 @@ static void coroutine_fn mirror_run(void *opaque)
         /* Report BLOCK_JOB_READY and wait for complete. */
         block_job_event_ready(&s->common);
         s->synced = true;
-        while (!block_job_is_cancelled(&s->common) && !s->should_complete) {
+        while (!job_is_cancelled(&s->common.job) && !s->should_complete) {
             block_job_yield(&s->common);
         }
-        s->common.cancelled = false;
+        s->common.job.cancelled = false;
         goto immediate_exit;
     }
 
@@ -768,7 +768,7 @@ static void coroutine_fn mirror_run(void *opaque)
     s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
     if (!s->is_none_mode) {
         ret = mirror_dirty_init(s);
-        if (ret < 0 || block_job_is_cancelled(&s->common)) {
+        if (ret < 0 || job_is_cancelled(&s->common.job)) {
             goto immediate_exit;
         }
     }
@@ -829,7 +829,7 @@ static void coroutine_fn mirror_run(void *opaque)
             }
 
             should_complete = s->should_complete ||
-                block_job_is_cancelled(&s->common);
+                job_is_cancelled(&s->common.job);
             cnt = bdrv_get_dirty_count(s->dirty_bitmap);
         }
 
@@ -857,7 +857,7 @@ static void coroutine_fn mirror_run(void *opaque)
              * completion.
              */
             assert(QLIST_EMPTY(&bs->tracked_requests));
-            s->common.cancelled = false;
+            s->common.job.cancelled = false;
             need_drain = false;
             break;
         }
@@ -869,7 +869,7 @@ static void coroutine_fn mirror_run(void *opaque)
         }
         trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
         block_job_sleep_ns(&s->common, delay_ns);
-        if (block_job_is_cancelled(&s->common) &&
+        if (job_is_cancelled(&s->common.job) &&
             (!s->synced || s->common.force))
         {
             break;
@@ -884,7 +884,7 @@ immediate_exit:
          * the target is a copy of the source.
          */
         assert(ret < 0 || ((s->common.force || !s->synced) &&
-               block_job_is_cancelled(&s->common)));
+               job_is_cancelled(&s->common.job)));
         assert(need_drain);
         mirror_wait_for_all_io(s);
     }
diff --git a/block/stream.c b/block/stream.c
index 7273d2213c..22c71ae100 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -66,7 +66,7 @@ static void stream_complete(BlockJob *job, void *opaque)
     BlockDriverState *base = s->base;
     Error *local_err = NULL;
 
-    if (!block_job_is_cancelled(&s->common) && bs->backing &&
+    if (!job_is_cancelled(&s->common.job) && bs->backing &&
         data->ret == 0) {
         const char *base_id = NULL, *base_fmt = NULL;
         if (base) {
@@ -141,7 +141,7 @@ static void coroutine_fn stream_run(void *opaque)
          * with no pending I/O here so that bdrv_drain_all() returns.
          */
         block_job_sleep_ns(&s->common, delay_ns);
-        if (block_job_is_cancelled(&s->common)) {
+        if (job_is_cancelled(&s->common.job)) {
             break;
         }
 
diff --git a/blockjob.c b/blockjob.c
index 1fa5173bb9..b33c1df37d 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -366,7 +366,7 @@ static void block_job_conclude(BlockJob *job)
 
 static void block_job_update_rc(BlockJob *job)
 {
-    if (!job->ret && block_job_is_cancelled(job)) {
+    if (!job->ret && job_is_cancelled(&job->job)) {
         job->ret = -ECANCELED;
     }
     if (job->ret) {
@@ -425,7 +425,7 @@ static int block_job_finalize_single(BlockJob *job)
 
     /* Emit events only if we actually started */
     if (block_job_started(job)) {
-        if (block_job_is_cancelled(job)) {
+        if (job_is_cancelled(&job->job)) {
             block_job_event_cancelled(job);
         } else {
             const char *msg = NULL;
@@ -451,7 +451,7 @@ static void block_job_cancel_async(BlockJob *job, bool force)
         job->user_paused = false;
         job->pause_count--;
     }
-    job->cancelled = true;
+    job->job.cancelled = true;
     /* To prevent 'force == false' overriding a previous 'force == true' */
     job->force |= force;
 }
@@ -506,7 +506,8 @@ static int block_job_finish_sync(BlockJob *job,
     while (!job->completed) {
         aio_poll(qemu_get_aio_context(), true);
     }
-    ret = (job->cancelled && job->ret == 0) ? -ECANCELED : job->ret;
+    ret = (job_is_cancelled(&job->job) && job->ret == 0)
+          ? -ECANCELED : job->ret;
     job_unref(&job->job);
     return ret;
 }
@@ -544,7 +545,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
         other_job = QLIST_FIRST(&txn->jobs);
         ctx = blk_get_aio_context(other_job->blk);
         if (!other_job->completed) {
-            assert(other_job->cancelled);
+            assert(job_is_cancelled(&other_job->job));
             block_job_finish_sync(other_job, NULL, NULL);
         }
         block_job_finalize_single(other_job);
@@ -644,7 +645,9 @@ void block_job_complete(BlockJob *job, Error **errp)
     if (job_apply_verb(&job->job, JOB_VERB_COMPLETE, errp)) {
         return;
     }
-    if (job->pause_count || job->cancelled || !job->driver->complete) {
+    if (job->pause_count || job_is_cancelled(&job->job) ||
+        !job->driver->complete)
+    {
         error_setg(errp, "The active block job '%s' cannot be completed",
                    job->job.id);
         return;
@@ -999,7 +1002,7 @@ void coroutine_fn block_job_pause_point(BlockJob *job)
     if (!block_job_should_pause(job)) {
         return;
     }
-    if (block_job_is_cancelled(job)) {
+    if (job_is_cancelled(&job->job)) {
         return;
     }
 
@@ -1007,7 +1010,7 @@ void coroutine_fn block_job_pause_point(BlockJob *job)
         job->driver->pause(job);
     }
 
-    if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
+    if (block_job_should_pause(job) && !job_is_cancelled(&job->job)) {
         JobStatus status = job->job.status;
         job_state_transition(&job->job, status == JOB_STATUS_READY
                                         ? JOB_STATUS_STANDBY
@@ -1059,17 +1062,12 @@ void block_job_enter(BlockJob *job)
     block_job_enter_cond(job, NULL);
 }
 
-bool block_job_is_cancelled(BlockJob *job)
-{
-    return job->cancelled;
-}
-
 void block_job_sleep_ns(BlockJob *job, int64_t ns)
 {
     assert(job->busy);
 
     /* Check cancellation *before* setting busy = false, too!  */
-    if (block_job_is_cancelled(job)) {
+    if (job_is_cancelled(&job->job)) {
         return;
     }
 
@@ -1085,7 +1083,7 @@ void block_job_yield(BlockJob *job)
     assert(job->busy);
 
     /* Check cancellation *before* setting busy = false, too!  */
-    if (block_job_is_cancelled(job)) {
+    if (job_is_cancelled(&job->job)) {
         return;
     }
 
diff --git a/job.c b/job.c
index 67b09cb54d..090d250559 100644
--- a/job.c
+++ b/job.c
@@ -95,6 +95,11 @@ const char *job_type_str(Job *job)
     return JobType_str(job_type(job));
 }
 
+bool job_is_cancelled(Job *job)
+{
+    return job->cancelled;
+}
+
 Job *job_next(Job *job)
 {
     if (!job) {
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index b49b28ca27..26b4bbb230 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -29,7 +29,7 @@ static void test_block_job_complete(BlockJob *job, void *opaque)
     BlockDriverState *bs = blk_bs(job->blk);
     int rc = (intptr_t)opaque;
 
-    if (block_job_is_cancelled(job)) {
+    if (job_is_cancelled(&job->job)) {
         rc = -ECANCELED;
     }
 
@@ -49,7 +49,7 @@ static void coroutine_fn test_block_job_run(void *opaque)
             block_job_yield(job);
         }
 
-        if (block_job_is_cancelled(job)) {
+        if (job_is_cancelled(&job->job)) {
             break;
         }
     }
@@ -66,7 +66,7 @@ typedef struct {
 static void test_block_job_cb(void *opaque, int ret)
 {
     TestBlockJobCBData *data = opaque;
-    if (!ret && block_job_is_cancelled(&data->job->common)) {
+    if (!ret && job_is_cancelled(&data->job->common.job)) {
         ret = -ECANCELED;
     }
     *data->result = ret;
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index e24fc3f140..fa31481537 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -179,7 +179,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
     CancelJob *s = opaque;
 
     while (!s->should_complete) {
-        if (block_job_is_cancelled(&s->common)) {
+        if (job_is_cancelled(&s->common.job)) {
             goto defer;
         }
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (14 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 15:20   ` Max Reitz
  2018-05-14 22:02   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job Kevin Wolf
                   ` (26 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

When block jobs need an AioContext, they just take it from their main
block node. Generic jobs don't have a main block node, so we need to
assign them an AioContext explicitly.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/qemu/job.h | 7 ++++++-
 blockjob.c         | 5 ++++-
 job.c              | 4 +++-
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/include/qemu/job.h b/include/qemu/job.h
index 14c6288517..1821f9ebd7 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -47,6 +47,9 @@ typedef struct Job {
     /** Current state; See @JobStatus for details. */
     JobStatus status;
 
+    /** AioContext to run the job coroutine in */
+    AioContext *aio_context;
+
     /**
      * Set to true if the job should cancel itself.  The flag must
      * always be tested just before toggling the busy flag from false
@@ -79,9 +82,11 @@ struct JobDriver {
  *
  * @job_id: The id of the newly-created job, or %NULL for internal jobs
  * @driver: The class object for the newly-created job.
+ * @ctx: The AioContext to run the job coroutine in.
  * @errp: Error object.
  */
-void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
+void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
+                 Error **errp);
 
 /**
  * Add a reference to Job refcnt, it will be decreased with job_unref, and then
diff --git a/blockjob.c b/blockjob.c
index b33c1df37d..d44f5c2e50 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -203,6 +203,7 @@ static void block_job_attached_aio_context(AioContext *new_context,
 {
     BlockJob *job = opaque;
 
+    job->job.aio_context = new_context;
     if (job->driver->attached_aio_context) {
         job->driver->attached_aio_context(job, new_context);
     }
@@ -234,6 +235,7 @@ static void block_job_detach_aio_context(void *opaque)
         block_job_drain(job);
     }
 
+    job->job.aio_context = NULL;
     job_unref(&job->job);
 }
 
@@ -892,7 +894,8 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
         return NULL;
     }
 
-    job = job_create(job_id, &driver->job_driver, errp);
+    job = job_create(job_id, &driver->job_driver, blk_get_aio_context(blk),
+                     errp);
     if (job == NULL) {
         blk_unref(blk);
         return NULL;
diff --git a/job.c b/job.c
index 090d250559..6f97a4317e 100644
--- a/job.c
+++ b/job.c
@@ -121,7 +121,8 @@ Job *job_get(const char *id)
     return NULL;
 }
 
-void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
+void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
+                 Error **errp)
 {
     Job *job;
 
@@ -140,6 +141,7 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
     job->driver        = driver;
     job->id            = g_strdup(job_id);
     job->refcnt        = 1;
+    job->aio_context   = ctx;
 
     job_state_transition(job, JOB_STATUS_CREATED);
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (15 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 15:52   ` Max Reitz
                     ` (2 more replies)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code " Kevin Wolf
                   ` (25 subsequent siblings)
  42 siblings, 3 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |  5 ----
 include/block/blockjob_int.h | 19 ---------------
 include/qemu/job.h           | 20 ++++++++++++++++
 block/backup.c               |  7 +++---
 block/commit.c               | 11 +++++----
 block/mirror.c               | 15 ++++++------
 block/stream.c               | 14 +++++------
 blockjob.c                   | 57 ++++----------------------------------------
 job.c                        | 33 +++++++++++++++++++++++++
 tests/test-bdrv-drain.c      |  7 +++---
 tests/test-blockjob-txn.c    | 13 +++++-----
 tests/test-blockjob.c        |  7 +++---
 12 files changed, 98 insertions(+), 110 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 04efc94ffc..90942826f5 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -92,11 +92,6 @@ typedef struct BlockJob {
      */
     bool ready;
 
-    /**
-     * Set to true when the job has deferred work to the main loop.
-     */
-    bool deferred_to_main_loop;
-
     /** Status that is published by the query-block-jobs QMP API */
     BlockDeviceIoStatus iostatus;
 
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index d64f30e6b0..0c2f8de381 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -233,23 +233,4 @@ void block_job_event_ready(BlockJob *job);
 BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
                                         int is_read, int error);
 
-typedef void BlockJobDeferToMainLoopFn(BlockJob *job, void *opaque);
-
-/**
- * block_job_defer_to_main_loop:
- * @job: The job
- * @fn: The function to run in the main loop
- * @opaque: The opaque value that is passed to @fn
- *
- * This function must be called by the main job coroutine just before it
- * returns.  @fn is executed in the main loop with the BlockDriverState
- * AioContext acquired.  Block jobs must call bdrv_unref(), bdrv_close(), and
- * anything that uses bdrv_drain_all() in the main loop.
- *
- * The @job AioContext is held while @fn executes.
- */
-void block_job_defer_to_main_loop(BlockJob *job,
-                                  BlockJobDeferToMainLoopFn *fn,
-                                  void *opaque);
-
 #endif
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 1821f9ebd7..1a20534235 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -58,6 +58,9 @@ typedef struct Job {
      */
     bool cancelled;
 
+    /** Set to true when the job has deferred work to the main loop. */
+    bool deferred_to_main_loop;
+
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 } Job;
@@ -131,6 +134,23 @@ Job *job_get(const char *id);
  */
 int job_apply_verb(Job *job, JobVerb bv, Error **errp);
 
+typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
+
+/**
+ * @job: The job
+ * @fn: The function to run in the main loop
+ * @opaque: The opaque value that is passed to @fn
+ *
+ * This function must be called by the main job coroutine just before it
+ * returns.  @fn is executed in the main loop with the job AioContext acquired.
+ *
+ * Block jobs must call bdrv_unref(), bdrv_close(), and anything that uses
+ * bdrv_drain_all() in the main loop.
+ *
+ * The @job AioContext is held while @fn executes.
+ */
+void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
+
 /* TODO To be removed from the public interface */
 void job_state_transition(Job *job, JobStatus s1);
 
diff --git a/block/backup.c b/block/backup.c
index ef0aa0e24e..22dd368c90 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -317,11 +317,12 @@ typedef struct {
     int ret;
 } BackupCompleteData;
 
-static void backup_complete(BlockJob *job, void *opaque)
+static void backup_complete(Job *job, void *opaque)
 {
+    BlockJob *bjob = container_of(job, BlockJob, job);
     BackupCompleteData *data = opaque;
 
-    block_job_completed(job, data->ret);
+    block_job_completed(bjob, data->ret);
     g_free(data);
 }
 
@@ -519,7 +520,7 @@ static void coroutine_fn backup_run(void *opaque)
 
     data = g_malloc(sizeof(*data));
     data->ret = ret;
-    block_job_defer_to_main_loop(&job->common, backup_complete, data);
+    job_defer_to_main_loop(&job->common.job, backup_complete, data);
 }
 
 static const BlockJobDriver backup_job_driver = {
diff --git a/block/commit.c b/block/commit.c
index 85baea8f92..d326766e4d 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -72,9 +72,10 @@ typedef struct {
     int ret;
 } CommitCompleteData;
 
-static void commit_complete(BlockJob *job, void *opaque)
+static void commit_complete(Job *job, void *opaque)
 {
-    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
+    CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
+    BlockJob *bjob = &s->common;
     CommitCompleteData *data = opaque;
     BlockDriverState *top = blk_bs(s->top);
     BlockDriverState *base = blk_bs(s->base);
@@ -90,7 +91,7 @@ static void commit_complete(BlockJob *job, void *opaque)
      * the normal backing chain can be restored. */
     blk_unref(s->base);
 
-    if (!job_is_cancelled(&s->common.job) && ret == 0) {
+    if (!job_is_cancelled(job) && ret == 0) {
         /* success */
         ret = bdrv_drop_intermediate(s->commit_top_bs, base,
                                      s->backing_file_str);
@@ -114,7 +115,7 @@ static void commit_complete(BlockJob *job, void *opaque)
      * block_job_finish_sync()), block_job_completed() won't free it and
      * therefore the blockers on the intermediate nodes remain. This would
      * cause bdrv_set_backing_hd() to fail. */
-    block_job_remove_all_bdrv(job);
+    block_job_remove_all_bdrv(bjob);
 
     block_job_completed(&s->common, ret);
     g_free(data);
@@ -211,7 +212,7 @@ out:
 
     data = g_malloc(sizeof(*data));
     data->ret = ret;
-    block_job_defer_to_main_loop(&s->common, commit_complete, data);
+    job_defer_to_main_loop(&s->common.job, commit_complete, data);
 }
 
 static const BlockJobDriver commit_job_driver = {
diff --git a/block/mirror.c b/block/mirror.c
index 163d83e34a..4929191b81 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -484,9 +484,10 @@ typedef struct {
     int ret;
 } MirrorExitData;
 
-static void mirror_exit(BlockJob *job, void *opaque)
+static void mirror_exit(Job *job, void *opaque)
 {
-    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
+    BlockJob *bjob = &s->common;
     MirrorExitData *data = opaque;
     AioContext *replace_aio_context = NULL;
     BlockDriverState *src = s->source;
@@ -568,7 +569,7 @@ static void mirror_exit(BlockJob *job, void *opaque)
      * the blockers on the intermediate nodes so that the resulting state is
      * valid. Also give up permissions on mirror_top_bs->backing, which might
      * block the removal. */
-    block_job_remove_all_bdrv(job);
+    block_job_remove_all_bdrv(bjob);
     bdrv_child_try_set_perm(mirror_top_bs->backing, 0, BLK_PERM_ALL,
                             &error_abort);
     bdrv_replace_node(mirror_top_bs, backing_bs(mirror_top_bs), &error_abort);
@@ -576,9 +577,9 @@ static void mirror_exit(BlockJob *job, void *opaque)
     /* We just changed the BDS the job BB refers to (with either or both of the
      * bdrv_replace_node() calls), so switch the BB back so the cleanup does
      * the right thing. We don't need any permissions any more now. */
-    blk_remove_bs(job->blk);
-    blk_set_perm(job->blk, 0, BLK_PERM_ALL, &error_abort);
-    blk_insert_bs(job->blk, mirror_top_bs, &error_abort);
+    blk_remove_bs(bjob->blk);
+    blk_set_perm(bjob->blk, 0, BLK_PERM_ALL, &error_abort);
+    blk_insert_bs(bjob->blk, mirror_top_bs, &error_abort);
 
     block_job_completed(&s->common, data->ret);
 
@@ -901,7 +902,7 @@ immediate_exit:
     if (need_drain) {
         bdrv_drained_begin(bs);
     }
-    block_job_defer_to_main_loop(&s->common, mirror_exit, data);
+    job_defer_to_main_loop(&s->common.job, mirror_exit, data);
 }
 
 static void mirror_complete(BlockJob *job, Error **errp)
diff --git a/block/stream.c b/block/stream.c
index 22c71ae100..0bba81678c 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -58,16 +58,16 @@ typedef struct {
     int ret;
 } StreamCompleteData;
 
-static void stream_complete(BlockJob *job, void *opaque)
+static void stream_complete(Job *job, void *opaque)
 {
-    StreamBlockJob *s = container_of(job, StreamBlockJob, common);
+    StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
+    BlockJob *bjob = &s->common;
     StreamCompleteData *data = opaque;
-    BlockDriverState *bs = blk_bs(job->blk);
+    BlockDriverState *bs = blk_bs(bjob->blk);
     BlockDriverState *base = s->base;
     Error *local_err = NULL;
 
-    if (!job_is_cancelled(&s->common.job) && bs->backing &&
-        data->ret == 0) {
+    if (!job_is_cancelled(job) && bs->backing && data->ret == 0) {
         const char *base_id = NULL, *base_fmt = NULL;
         if (base) {
             base_id = s->backing_file_str;
@@ -88,7 +88,7 @@ out:
     /* Reopen the image back in read-only mode if necessary */
     if (s->bs_flags != bdrv_get_flags(bs)) {
         /* Give up write permissions before making it read-only */
-        blk_set_perm(job->blk, 0, BLK_PERM_ALL, &error_abort);
+        blk_set_perm(bjob->blk, 0, BLK_PERM_ALL, &error_abort);
         bdrv_reopen(bs, s->bs_flags, NULL);
     }
 
@@ -205,7 +205,7 @@ out:
     /* Modify backing chain and close BDSes in main loop */
     data = g_malloc(sizeof(*data));
     data->ret = ret;
-    block_job_defer_to_main_loop(&s->common, stream_complete, data);
+    job_defer_to_main_loop(&s->common.job, stream_complete, data);
 }
 
 static const BlockJobDriver stream_job_driver = {
diff --git a/blockjob.c b/blockjob.c
index d44f5c2e50..6021d885be 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -347,7 +347,7 @@ static void block_job_decommission(BlockJob *job)
     job->completed = true;
     job->busy = false;
     job->paused = false;
-    job->deferred_to_main_loop = true;
+    job->job.deferred_to_main_loop = true;
     block_job_txn_del_job(job);
     job_state_transition(&job->job, JOB_STATUS_NULL);
     job_unref(&job->job);
@@ -502,7 +502,7 @@ static int block_job_finish_sync(BlockJob *job,
     /* block_job_drain calls block_job_enter, and it should be enough to
      * induce progress until the job completes or moves to the main thread.
     */
-    while (!job->deferred_to_main_loop && !job->completed) {
+    while (!job->job.deferred_to_main_loop && !job->completed) {
         block_job_drain(job);
     }
     while (!job->completed) {
@@ -722,7 +722,7 @@ void block_job_cancel(BlockJob *job, bool force)
     block_job_cancel_async(job, force);
     if (!block_job_started(job)) {
         block_job_completed(job, -ECANCELED);
-    } else if (job->deferred_to_main_loop) {
+    } else if (job->job.deferred_to_main_loop) {
         block_job_completed_txn_abort(job);
     } else {
         block_job_enter(job);
@@ -1038,7 +1038,7 @@ static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job))
     if (!block_job_started(job)) {
         return;
     }
-    if (job->deferred_to_main_loop) {
+    if (job->job.deferred_to_main_loop) {
         return;
     }
 
@@ -1053,7 +1053,7 @@ static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job))
         return;
     }
 
-    assert(!job->deferred_to_main_loop);
+    assert(!job->job.deferred_to_main_loop);
     timer_del(&job->sleep_timer);
     job->busy = true;
     block_job_unlock();
@@ -1159,50 +1159,3 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
     }
     return action;
 }
-
-typedef struct {
-    BlockJob *job;
-    AioContext *aio_context;
-    BlockJobDeferToMainLoopFn *fn;
-    void *opaque;
-} BlockJobDeferToMainLoopData;
-
-static void block_job_defer_to_main_loop_bh(void *opaque)
-{
-    BlockJobDeferToMainLoopData *data = opaque;
-    AioContext *aio_context;
-
-    /* Prevent race with block_job_defer_to_main_loop() */
-    aio_context_acquire(data->aio_context);
-
-    /* Fetch BDS AioContext again, in case it has changed */
-    aio_context = blk_get_aio_context(data->job->blk);
-    if (aio_context != data->aio_context) {
-        aio_context_acquire(aio_context);
-    }
-
-    data->fn(data->job, data->opaque);
-
-    if (aio_context != data->aio_context) {
-        aio_context_release(aio_context);
-    }
-
-    aio_context_release(data->aio_context);
-
-    g_free(data);
-}
-
-void block_job_defer_to_main_loop(BlockJob *job,
-                                  BlockJobDeferToMainLoopFn *fn,
-                                  void *opaque)
-{
-    BlockJobDeferToMainLoopData *data = g_malloc(sizeof(*data));
-    data->job = job;
-    data->aio_context = blk_get_aio_context(job->blk);
-    data->fn = fn;
-    data->opaque = opaque;
-    job->deferred_to_main_loop = true;
-
-    aio_bh_schedule_oneshot(qemu_get_aio_context(),
-                            block_job_defer_to_main_loop_bh, data);
-}
diff --git a/job.c b/job.c
index 6f97a4317e..b074b3ffd7 100644
--- a/job.c
+++ b/job.c
@@ -28,6 +28,7 @@
 #include "qapi/error.h"
 #include "qemu/job.h"
 #include "qemu/id.h"
+#include "qemu/main-loop.h"
 #include "trace-root.h"
 
 static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
@@ -170,3 +171,35 @@ void job_unref(Job *job)
         g_free(job);
     }
 }
+
+typedef struct {
+    Job *job;
+    JobDeferToMainLoopFn *fn;
+    void *opaque;
+} JobDeferToMainLoopData;
+
+static void job_defer_to_main_loop_bh(void *opaque)
+{
+    JobDeferToMainLoopData *data = opaque;
+    Job *job = data->job;
+    AioContext *aio_context = job->aio_context;
+
+    /* Prevent race with job_defer_to_main_loop() */
+    aio_context_acquire(aio_context);
+    data->fn(data->job, data->opaque);
+    aio_context_release(aio_context);
+
+    g_free(data);
+}
+
+void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque)
+{
+    JobDeferToMainLoopData *data = g_malloc(sizeof(*data));
+    data->job = job;
+    data->fn = fn;
+    data->opaque = opaque;
+    job->deferred_to_main_loop = true;
+
+    aio_bh_schedule_oneshot(qemu_get_aio_context(),
+                            job_defer_to_main_loop_bh, data);
+}
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index f9e37d479c..4f8cba8377 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -496,9 +496,10 @@ typedef struct TestBlockJob {
     bool should_complete;
 } TestBlockJob;
 
-static void test_job_completed(BlockJob *job, void *opaque)
+static void test_job_completed(Job *job, void *opaque)
 {
-    block_job_completed(job, 0);
+    BlockJob *bjob = container_of(job, BlockJob, job);
+    block_job_completed(bjob, 0);
 }
 
 static void coroutine_fn test_job_start(void *opaque)
@@ -510,7 +511,7 @@ static void coroutine_fn test_job_start(void *opaque)
         block_job_sleep_ns(&s->common, 100000);
     }
 
-    block_job_defer_to_main_loop(&s->common, test_job_completed, NULL);
+    job_defer_to_main_loop(&s->common.job, test_job_completed, NULL);
 }
 
 static void test_job_complete(BlockJob *job, Error **errp)
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 26b4bbb230..c03f9662d8 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -24,16 +24,17 @@ typedef struct {
     int *result;
 } TestBlockJob;
 
-static void test_block_job_complete(BlockJob *job, void *opaque)
+static void test_block_job_complete(Job *job, void *opaque)
 {
-    BlockDriverState *bs = blk_bs(job->blk);
+    BlockJob *bjob = container_of(job, BlockJob, job);
+    BlockDriverState *bs = blk_bs(bjob->blk);
     int rc = (intptr_t)opaque;
 
-    if (job_is_cancelled(&job->job)) {
+    if (job_is_cancelled(job)) {
         rc = -ECANCELED;
     }
 
-    block_job_completed(job, rc);
+    block_job_completed(bjob, rc);
     bdrv_unref(bs);
 }
 
@@ -54,8 +55,8 @@ static void coroutine_fn test_block_job_run(void *opaque)
         }
     }
 
-    block_job_defer_to_main_loop(job, test_block_job_complete,
-                                 (void *)(intptr_t)s->rc);
+    job_defer_to_main_loop(&job->job, test_block_job_complete,
+                           (void *)(intptr_t)s->rc);
 }
 
 typedef struct {
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index fa31481537..5f43bd72a4 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -161,11 +161,12 @@ typedef struct CancelJob {
     bool completed;
 } CancelJob;
 
-static void cancel_job_completed(BlockJob *job, void *opaque)
+static void cancel_job_completed(Job *job, void *opaque)
 {
+    BlockJob *bjob = container_of(job, BlockJob, job);
     CancelJob *s = opaque;
     s->completed = true;
-    block_job_completed(job, 0);
+    block_job_completed(bjob, 0);
 }
 
 static void cancel_job_complete(BlockJob *job, Error **errp)
@@ -191,7 +192,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
     }
 
  defer:
-    block_job_defer_to_main_loop(&s->common, cancel_job_completed, s);
+    job_defer_to_main_loop(&s->common.job, cancel_job_completed, s);
 }
 
 static const BlockJobDriver test_cancel_driver = {
-- 
2.13.6

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

* [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (16 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 16:47   ` Max Reitz
  2018-05-14 23:02   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns() Kevin Wolf
                   ` (24 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This commit moves some core functions for dealing with the job coroutine
from BlockJob to Job. This includes primarily entering the coroutine
(both for the first and reentering) and yielding explicitly and at pause
points.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |  40 ---------
 include/block/blockjob_int.h |  26 ------
 include/qemu/job.h           |  76 ++++++++++++++++
 block/backup.c               |   2 +-
 block/commit.c               |   4 +-
 block/mirror.c               |  22 ++---
 block/replication.c          |   2 +-
 block/stream.c               |   4 +-
 blockdev.c                   |   8 +-
 blockjob.c                   | 201 +++++++------------------------------------
 job.c                        | 137 +++++++++++++++++++++++++++++
 tests/test-bdrv-drain.c      |  38 ++++----
 tests/test-blockjob-txn.c    |  12 +--
 tests/test-blockjob.c        |  14 +--
 14 files changed, 296 insertions(+), 290 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 90942826f5..c0448206e2 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -51,43 +51,18 @@ typedef struct BlockJob {
     BlockBackend *blk;
 
     /**
-     * The coroutine that executes the job.  If not NULL, it is
-     * reentered when busy is false and the job is cancelled.
-     */
-    Coroutine *co;
-
-    /**
      * Set to true if the job should abort immediately without waiting
      * for data to be in sync.
      */
     bool force;
 
     /**
-     * Counter for pause request. If non-zero, the block job is either paused,
-     * or if busy == true will pause itself as soon as possible.
-     */
-    int pause_count;
-
-    /**
      * Set to true if the job is paused by user.  Can be unpaused with the
      * block-job-resume QMP command.
      */
     bool user_paused;
 
     /**
-     * Set to false by the job while the coroutine has yielded and may be
-     * re-entered by block_job_enter().  There may still be I/O or event loop
-     * activity pending.  Accessed under block_job_mutex (in blockjob.c).
-     */
-    bool busy;
-
-    /**
-     * Set to true by the job while it is in a quiescent state, where
-     * no I/O or event loop activity is pending.
-     */
-    bool paused;
-
-    /**
      * Set to true when the job is ready to be completed.
      */
     bool ready;
@@ -125,12 +100,6 @@ typedef struct BlockJob {
     /** ret code passed to block_job_completed. */
     int ret;
 
-    /**
-     * Timer that is used by @block_job_sleep_ns. Accessed under
-     * block_job_mutex (in blockjob.c).
-     */
-    QEMUTimer sleep_timer;
-
     /** True if this job should automatically finalize itself */
     bool auto_finalize;
 
@@ -208,15 +177,6 @@ void block_job_remove_all_bdrv(BlockJob *job);
 void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 
 /**
- * block_job_start:
- * @job: A job that has not yet been started.
- *
- * Begins execution of a block job.
- * Takes ownership of one reference to the job object.
- */
-void block_job_start(BlockJob *job);
-
-/**
  * block_job_cancel:
  * @job: The job to be canceled.
  * @force: Quit a job without waiting for data to be in sync.
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 0c2f8de381..0a614a89b8 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -38,9 +38,6 @@ struct BlockJobDriver {
     /** Generic JobDriver callbacks and settings */
     JobDriver job_driver;
 
-    /** Mandatory: Entrypoint for the Coroutine. */
-    CoroutineEntry *start;
-
     /**
      * Optional callback for job types whose completion must be triggered
      * manually.
@@ -85,20 +82,6 @@ struct BlockJobDriver {
      */
     void (*clean)(BlockJob *job);
 
-    /**
-     * If the callback is not NULL, it will be invoked when the job transitions
-     * into the paused state.  Paused jobs must not perform any asynchronous
-     * I/O or event loop activity.  This callback is used to quiesce jobs.
-     */
-    void coroutine_fn (*pause)(BlockJob *job);
-
-    /**
-     * If the callback is not NULL, it will be invoked when the job transitions
-     * out of the paused state.  Any asynchronous I/O or event loop activity
-     * should be restarted from this callback.
-     */
-    void coroutine_fn (*resume)(BlockJob *job);
-
     /*
      * If the callback is not NULL, it will be invoked before the job is
      * resumed in a new AioContext.  This is the place to move any resources
@@ -196,15 +179,6 @@ void block_job_early_fail(BlockJob *job);
 void block_job_completed(BlockJob *job, int ret);
 
 /**
- * block_job_pause_point:
- * @job: The job that is ready to pause.
- *
- * Pause now if block_job_pause() has been called.  Block jobs that perform
- * lots of I/O must call this between requests so that the job can be paused.
- */
-void coroutine_fn block_job_pause_point(BlockJob *job);
-
-/**
  * block_job_enter:
  * @job: The job to enter.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 1a20534235..00c7cda9a3 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -28,6 +28,7 @@
 
 #include "qapi/qapi-types-block-core.h"
 #include "qemu/queue.h"
+#include "qemu/coroutine.h"
 
 typedef struct JobDriver JobDriver;
 
@@ -51,6 +52,37 @@ typedef struct Job {
     AioContext *aio_context;
 
     /**
+     * The coroutine that executes the job.  If not NULL, it is reentered when
+     * busy is false and the job is cancelled.
+     */
+    Coroutine *co;
+
+    /**
+     * Timer that is used by @block_job_sleep_ns. Accessed under job_mutex (in
+     * job.c).
+     */
+    QEMUTimer sleep_timer;
+
+    /**
+     * Counter for pause request. If non-zero, the block job is either paused,
+     * or if busy == true will pause itself as soon as possible.
+     */
+    int pause_count;
+
+    /**
+     * Set to false by the job while the coroutine has yielded and may be
+     * re-entered by block_job_enter().  There may still be I/O or event loop
+     * activity pending.  Accessed under block_job_mutex (in blockjob.c).
+     */
+    bool busy;
+
+    /**
+     * Set to true by the job while it is in a quiescent state, where
+     * no I/O or event loop activity is pending.
+     */
+    bool paused;
+
+    /**
      * Set to true if the job should cancel itself.  The flag must
      * always be tested just before toggling the busy flag from false
      * to true.  After a job has been cancelled, it should only yield
@@ -75,6 +107,23 @@ struct JobDriver {
     /** Enum describing the operation */
     JobType job_type;
 
+    /** Mandatory: Entrypoint for the Coroutine. */
+    CoroutineEntry *start;
+
+    /**
+     * If the callback is not NULL, it will be invoked when the job transitions
+     * into the paused state.  Paused jobs must not perform any asynchronous
+     * I/O or event loop activity.  This callback is used to quiesce jobs.
+     */
+    void coroutine_fn (*pause)(Job *job);
+
+    /**
+     * If the callback is not NULL, it will be invoked when the job transitions
+     * out of the paused state.  Any asynchronous I/O or event loop activity
+     * should be restarted from this callback.
+     */
+    void coroutine_fn (*resume)(Job *job);
+
     /** Called when the job is freed */
     void (*free)(Job *job);
 };
@@ -103,6 +152,30 @@ void job_ref(Job *job);
  */
 void job_unref(Job *job);
 
+/**
+ * Conditionally enter the job coroutine if the job is ready to run, not
+ * already busy and fn() returns true. fn() is called while under the job_lock
+ * critical section.
+ */
+void job_enter_cond(Job *job, bool(*fn)(Job *job));
+
+/**
+ * @job: A job that has not yet been started.
+ *
+ * Begins execution of a job.
+ * Takes ownership of one reference to the job object.
+ */
+void job_start(Job *job);
+
+/**
+ * @job: The job that is ready to pause.
+ *
+ * Pause now if job_pause() has been called. Jobs that perform lots of I/O
+ * must call this between requests so that the job can be paused.
+ */
+void coroutine_fn job_pause_point(Job *job);
+
+
 /** Returns the JobType of a given Job. */
 JobType job_type(Job *job);
 
@@ -153,5 +226,8 @@ void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
 
 /* TODO To be removed from the public interface */
 void job_state_transition(Job *job, JobStatus s1);
+void coroutine_fn job_do_yield(Job *job, uint64_t ns);
+bool job_should_pause(Job *job);
+bool job_started(Job *job);
 
 #endif
diff --git a/block/backup.c b/block/backup.c
index 22dd368c90..7d9aad9749 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -528,8 +528,8 @@ static const BlockJobDriver backup_job_driver = {
         .instance_size          = sizeof(BackupBlockJob),
         .job_type               = JOB_TYPE_BACKUP,
         .free                   = block_job_free,
+        .start                  = backup_run,
     },
-    .start                  = backup_run,
     .commit                 = backup_commit,
     .abort                  = backup_abort,
     .clean                  = backup_clean,
diff --git a/block/commit.c b/block/commit.c
index d326766e4d..2fbc31077a 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -220,8 +220,8 @@ static const BlockJobDriver commit_job_driver = {
         .instance_size = sizeof(CommitBlockJob),
         .job_type      = JOB_TYPE_COMMIT,
         .free          = block_job_free,
+        .start         = commit_run,
     },
-    .start         = commit_run,
 };
 
 static int coroutine_fn bdrv_commit_top_preadv(BlockDriverState *bs,
@@ -371,7 +371,7 @@ void commit_start(const char *job_id, BlockDriverState *bs,
     s->on_error = on_error;
 
     trace_commit_start(bs, base, top, s);
-    block_job_start(&s->common);
+    job_start(&s->common.job);
     return;
 
 fail:
diff --git a/block/mirror.c b/block/mirror.c
index 4929191b81..5e8e0869be 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -126,7 +126,7 @@ static void mirror_iteration_done(MirrorOp *op, int ret)
     g_free(op);
 
     if (s->waiting_for_io) {
-        qemu_coroutine_enter(s->common.co);
+        qemu_coroutine_enter(s->common.job.co);
     }
 }
 
@@ -345,7 +345,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
         mirror_wait_for_io(s);
     }
 
-    block_job_pause_point(&s->common);
+    job_pause_point(&s->common.job);
 
     /* Find the number of consective dirty chunks following the first dirty
      * one, and wait for in flight requests in them. */
@@ -597,7 +597,7 @@ static void mirror_throttle(MirrorBlockJob *s)
         s->last_pause_ns = now;
         block_job_sleep_ns(&s->common, 0);
     } else {
-        block_job_pause_point(&s->common);
+        job_pause_point(&s->common.job);
     }
 }
 
@@ -786,7 +786,7 @@ static void coroutine_fn mirror_run(void *opaque)
             goto immediate_exit;
         }
 
-        block_job_pause_point(&s->common);
+        job_pause_point(&s->common.job);
 
         cnt = bdrv_get_dirty_count(s->dirty_bitmap);
         /* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
@@ -957,9 +957,9 @@ static void mirror_complete(BlockJob *job, Error **errp)
     block_job_enter(&s->common);
 }
 
-static void mirror_pause(BlockJob *job)
+static void mirror_pause(Job *job)
 {
-    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
 
     mirror_wait_for_all_io(s);
 }
@@ -991,10 +991,10 @@ static const BlockJobDriver mirror_job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
         .job_type               = JOB_TYPE_MIRROR,
         .free                   = block_job_free,
+        .start                  = mirror_run,
+        .pause                  = mirror_pause,
     },
-    .start                  = mirror_run,
     .complete               = mirror_complete,
-    .pause                  = mirror_pause,
     .attached_aio_context   = mirror_attached_aio_context,
     .drain                  = mirror_drain,
 };
@@ -1004,10 +1004,10 @@ static const BlockJobDriver commit_active_job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
         .job_type               = JOB_TYPE_COMMIT,
         .free                   = block_job_free,
+        .start                  = mirror_run,
+        .pause                  = mirror_pause,
     },
-    .start                  = mirror_run,
     .complete               = mirror_complete,
-    .pause                  = mirror_pause,
     .attached_aio_context   = mirror_attached_aio_context,
     .drain                  = mirror_drain,
 };
@@ -1242,7 +1242,7 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs,
     }
 
     trace_mirror_start(bs, s, opaque);
-    block_job_start(&s->common);
+    job_start(&s->common.job);
     return;
 
 fail:
diff --git a/block/replication.c b/block/replication.c
index 48148b884a..9ed6e0fb04 100644
--- a/block/replication.c
+++ b/block/replication.c
@@ -576,7 +576,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
             aio_context_release(aio_context);
             return;
         }
-        block_job_start(job);
+        job_start(&job->job);
         break;
     default:
         aio_context_release(aio_context);
diff --git a/block/stream.c b/block/stream.c
index 0bba81678c..6d8b7b6eee 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -213,8 +213,8 @@ static const BlockJobDriver stream_job_driver = {
         .instance_size = sizeof(StreamBlockJob),
         .job_type      = JOB_TYPE_STREAM,
         .free          = block_job_free,
+        .start         = stream_run,
     },
-    .start         = stream_run,
 };
 
 void stream_start(const char *job_id, BlockDriverState *bs,
@@ -262,7 +262,7 @@ void stream_start(const char *job_id, BlockDriverState *bs,
 
     s->on_error = on_error;
     trace_stream_start(bs, base, s);
-    block_job_start(&s->common);
+    job_start(&s->common.job);
     return;
 
 fail:
diff --git a/blockdev.c b/blockdev.c
index 3808b1fc00..c551fdf39a 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1910,7 +1910,7 @@ static void drive_backup_commit(BlkActionState *common)
     aio_context_acquire(aio_context);
 
     assert(state->job);
-    block_job_start(state->job);
+    job_start(&state->job->job);
 
     aio_context_release(aio_context);
 }
@@ -2008,7 +2008,7 @@ static void blockdev_backup_commit(BlkActionState *common)
     aio_context_acquire(aio_context);
 
     assert(state->job);
-    block_job_start(state->job);
+    job_start(&state->job->job);
 
     aio_context_release(aio_context);
 }
@@ -3425,7 +3425,7 @@ void qmp_drive_backup(DriveBackup *arg, Error **errp)
     BlockJob *job;
     job = do_drive_backup(arg, NULL, errp);
     if (job) {
-        block_job_start(job);
+        job_start(&job->job);
     }
 }
 
@@ -3513,7 +3513,7 @@ void qmp_blockdev_backup(BlockdevBackup *arg, Error **errp)
     BlockJob *job;
     job = do_blockdev_backup(arg, NULL, errp);
     if (job) {
-        block_job_start(job);
+        job_start(&job->job);
     }
 }
 
diff --git a/blockjob.c b/blockjob.c
index 6021d885be..323dad96a3 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -36,30 +36,9 @@
 #include "qemu/coroutine.h"
 #include "qemu/timer.h"
 
-/* Right now, this mutex is only needed to synchronize accesses to job->busy
- * and job->sleep_timer, such as concurrent calls to block_job_do_yield and
- * block_job_enter. */
-static QemuMutex block_job_mutex;
-
-static void block_job_lock(void)
-{
-    qemu_mutex_lock(&block_job_mutex);
-}
-
-static void block_job_unlock(void)
-{
-    qemu_mutex_unlock(&block_job_mutex);
-}
-
-static void __attribute__((__constructor__)) block_job_init(void)
-{
-    qemu_mutex_init(&block_job_mutex);
-}
-
 static void block_job_event_cancelled(BlockJob *job);
 static void block_job_event_completed(BlockJob *job, const char *msg);
 static int block_job_event_pending(BlockJob *job);
-static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job));
 
 /* Transactional group of block jobs */
 struct BlockJobTxn {
@@ -164,14 +143,14 @@ static void block_job_txn_del_job(BlockJob *job)
 
 static void block_job_pause(BlockJob *job)
 {
-    job->pause_count++;
+    job->job.pause_count++;
 }
 
 static void block_job_resume(BlockJob *job)
 {
-    assert(job->pause_count > 0);
-    job->pause_count--;
-    if (job->pause_count) {
+    assert(job->job.pause_count > 0);
+    job->job.pause_count--;
+    if (job->job.pause_count) {
         return;
     }
     block_job_enter(job);
@@ -195,7 +174,7 @@ void block_job_free(Job *job)
                                     block_job_detach_aio_context, bjob);
     blk_unref(bjob->blk);
     error_free(bjob->blocker);
-    assert(!timer_pending(&bjob->sleep_timer));
+    assert(!timer_pending(&bjob->job.sleep_timer));
 }
 
 static void block_job_attached_aio_context(AioContext *new_context,
@@ -213,7 +192,7 @@ static void block_job_attached_aio_context(AioContext *new_context,
 
 static void block_job_drain(BlockJob *job)
 {
-    /* If job is !job->busy this kicks it into the next pause point. */
+    /* If job is !job->job.busy this kicks it into the next pause point. */
     block_job_enter(job);
 
     blk_drain(job->blk);
@@ -231,7 +210,7 @@ static void block_job_detach_aio_context(void *opaque)
 
     block_job_pause(job);
 
-    while (!job->paused && !job->completed) {
+    while (!job->job.paused && !job->completed) {
         block_job_drain(job);
     }
 
@@ -299,29 +278,11 @@ bool block_job_is_internal(BlockJob *job)
     return (job->job.id == NULL);
 }
 
-static bool block_job_started(BlockJob *job)
-{
-    return job->co;
-}
-
 const BlockJobDriver *block_job_driver(BlockJob *job)
 {
     return job->driver;
 }
 
-/**
- * All jobs must allow a pause point before entering their job proper. This
- * ensures that jobs can be paused prior to being started, then resumed later.
- */
-static void coroutine_fn block_job_co_entry(void *opaque)
-{
-    BlockJob *job = opaque;
-
-    assert(job && job->driver && job->driver->start);
-    block_job_pause_point(job);
-    job->driver->start(job);
-}
-
 static void block_job_sleep_timer_cb(void *opaque)
 {
     BlockJob *job = opaque;
@@ -329,24 +290,12 @@ static void block_job_sleep_timer_cb(void *opaque)
     block_job_enter(job);
 }
 
-void block_job_start(BlockJob *job)
-{
-    assert(job && !block_job_started(job) && job->paused &&
-           job->driver && job->driver->start);
-    job->co = qemu_coroutine_create(block_job_co_entry, job);
-    job->pause_count--;
-    job->busy = true;
-    job->paused = false;
-    job_state_transition(&job->job, JOB_STATUS_RUNNING);
-    bdrv_coroutine_enter(blk_bs(job->blk), job->co);
-}
-
 static void block_job_decommission(BlockJob *job)
 {
     assert(job);
     job->completed = true;
-    job->busy = false;
-    job->paused = false;
+    job->job.busy = false;
+    job->job.paused = false;
     job->job.deferred_to_main_loop = true;
     block_job_txn_del_job(job);
     job_state_transition(&job->job, JOB_STATUS_NULL);
@@ -361,7 +310,7 @@ static void block_job_do_dismiss(BlockJob *job)
 static void block_job_conclude(BlockJob *job)
 {
     job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
-    if (job->auto_dismiss || !block_job_started(job)) {
+    if (job->auto_dismiss || !job_started(&job->job)) {
         block_job_do_dismiss(job);
     }
 }
@@ -426,7 +375,7 @@ static int block_job_finalize_single(BlockJob *job)
     }
 
     /* Emit events only if we actually started */
-    if (block_job_started(job)) {
+    if (job_started(&job->job)) {
         if (job_is_cancelled(&job->job)) {
             block_job_event_cancelled(job);
         } else {
@@ -451,7 +400,7 @@ static void block_job_cancel_async(BlockJob *job, bool force)
     if (job->user_paused) {
         /* Do not call block_job_enter here, the caller will handle it.  */
         job->user_paused = false;
-        job->pause_count--;
+        job->job.pause_count--;
     }
     job->job.cancelled = true;
     /* To prevent 'force == false' overriding a previous 'force == true' */
@@ -603,7 +552,7 @@ static void block_job_completed_txn_success(BlockJob *job)
 }
 
 /* Assumes the block_job_mutex is held */
-static bool block_job_timer_pending(BlockJob *job)
+static bool job_timer_pending(Job *job)
 {
     return timer_pending(&job->sleep_timer);
 }
@@ -628,7 +577,7 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
     }
 
     /* kick only if a timer is pending */
-    block_job_enter_cond(job, block_job_timer_pending);
+    job_enter_cond(&job->job, job_timer_pending);
 }
 
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
@@ -647,7 +596,7 @@ void block_job_complete(BlockJob *job, Error **errp)
     if (job_apply_verb(&job->job, JOB_VERB_COMPLETE, errp)) {
         return;
     }
-    if (job->pause_count || job_is_cancelled(&job->job) ||
+    if (job->job.pause_count || job_is_cancelled(&job->job) ||
         !job->driver->complete)
     {
         error_setg(errp, "The active block job '%s' cannot be completed",
@@ -701,7 +650,7 @@ bool block_job_user_paused(BlockJob *job)
 void block_job_user_resume(BlockJob *job, Error **errp)
 {
     assert(job);
-    if (!job->user_paused || job->pause_count <= 0) {
+    if (!job->user_paused || job->job.pause_count <= 0) {
         error_setg(errp, "Can't resume a job that was not paused");
         return;
     }
@@ -720,7 +669,7 @@ void block_job_cancel(BlockJob *job, bool force)
         return;
     }
     block_job_cancel_async(job, force);
-    if (!block_job_started(job)) {
+    if (!job_started(&job->job)) {
         block_job_completed(job, -ECANCELED);
     } else if (job->job.deferred_to_main_loop) {
         block_job_completed_txn_abort(job);
@@ -790,8 +739,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info->type      = g_strdup(job_type_str(&job->job));
     info->device    = g_strdup(job->job.id);
     info->len       = job->len;
-    info->busy      = atomic_read(&job->busy);
-    info->paused    = job->pause_count > 0;
+    info->busy      = atomic_read(&job->job.busy);
+    info->paused    = job->job.pause_count > 0;
     info->offset    = job->offset;
     info->speed     = job->speed;
     info->io_status = job->iostatus;
@@ -908,12 +857,9 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->blk           = blk;
     job->cb            = cb;
     job->opaque        = opaque;
-    job->busy          = false;
-    job->paused        = true;
-    job->pause_count   = 1;
     job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
-    aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
+    aio_timer_init(qemu_get_aio_context(), &job->job.sleep_timer,
                    QEMU_CLOCK_REALTIME, SCALE_NS,
                    block_job_sleep_timer_cb, job);
 
@@ -973,128 +919,41 @@ void block_job_completed(BlockJob *job, int ret)
     }
 }
 
-static bool block_job_should_pause(BlockJob *job)
-{
-    return job->pause_count > 0;
-}
-
-/* Yield, and schedule a timer to reenter the coroutine after @ns nanoseconds.
- * Reentering the job coroutine with block_job_enter() before the timer has
- * expired is allowed and cancels the timer.
- *
- * If @ns is (uint64_t) -1, no timer is scheduled and block_job_enter() must be
- * called explicitly. */
-static void block_job_do_yield(BlockJob *job, uint64_t ns)
-{
-    block_job_lock();
-    if (ns != -1) {
-        timer_mod(&job->sleep_timer, ns);
-    }
-    job->busy = false;
-    block_job_unlock();
-    qemu_coroutine_yield();
-
-    /* Set by block_job_enter before re-entering the coroutine.  */
-    assert(job->busy);
-}
-
-void coroutine_fn block_job_pause_point(BlockJob *job)
-{
-    assert(job && block_job_started(job));
-
-    if (!block_job_should_pause(job)) {
-        return;
-    }
-    if (job_is_cancelled(&job->job)) {
-        return;
-    }
-
-    if (job->driver->pause) {
-        job->driver->pause(job);
-    }
-
-    if (block_job_should_pause(job) && !job_is_cancelled(&job->job)) {
-        JobStatus status = job->job.status;
-        job_state_transition(&job->job, status == JOB_STATUS_READY
-                                        ? JOB_STATUS_STANDBY
-                                        : JOB_STATUS_PAUSED);
-        job->paused = true;
-        block_job_do_yield(job, -1);
-        job->paused = false;
-        job_state_transition(&job->job, status);
-    }
-
-    if (job->driver->resume) {
-        job->driver->resume(job);
-    }
-}
-
-/*
- * Conditionally enter a block_job pending a call to fn() while
- * under the block_job_lock critical section.
- */
-static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job))
-{
-    if (!block_job_started(job)) {
-        return;
-    }
-    if (job->job.deferred_to_main_loop) {
-        return;
-    }
-
-    block_job_lock();
-    if (job->busy) {
-        block_job_unlock();
-        return;
-    }
-
-    if (fn && !fn(job)) {
-        block_job_unlock();
-        return;
-    }
-
-    assert(!job->job.deferred_to_main_loop);
-    timer_del(&job->sleep_timer);
-    job->busy = true;
-    block_job_unlock();
-    aio_co_wake(job->co);
-}
-
 void block_job_enter(BlockJob *job)
 {
-    block_job_enter_cond(job, NULL);
+    job_enter_cond(&job->job, NULL);
 }
 
 void block_job_sleep_ns(BlockJob *job, int64_t ns)
 {
-    assert(job->busy);
+    assert(job->job.busy);
 
     /* Check cancellation *before* setting busy = false, too!  */
     if (job_is_cancelled(&job->job)) {
         return;
     }
 
-    if (!block_job_should_pause(job)) {
-        block_job_do_yield(job, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + ns);
+    if (!job_should_pause(&job->job)) {
+        job_do_yield(&job->job, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + ns);
     }
 
-    block_job_pause_point(job);
+    job_pause_point(&job->job);
 }
 
 void block_job_yield(BlockJob *job)
 {
-    assert(job->busy);
+    assert(job->job.busy);
 
     /* Check cancellation *before* setting busy = false, too!  */
     if (job_is_cancelled(&job->job)) {
         return;
     }
 
-    if (!block_job_should_pause(job)) {
-        block_job_do_yield(job, -1);
+    if (!job_should_pause(&job->job)) {
+        job_do_yield(&job->job, -1);
     }
 
-    block_job_pause_point(job);
+    job_pause_point(&job->job);
 }
 
 void block_job_iostatus_reset(BlockJob *job)
@@ -1102,7 +961,7 @@ void block_job_iostatus_reset(BlockJob *job)
     if (job->iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
         return;
     }
-    assert(job->user_paused && job->pause_count > 0);
+    assert(job->user_paused && job->job.pause_count > 0);
     job->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
 }
 
diff --git a/job.c b/job.c
index b074b3ffd7..b4cece1a82 100644
--- a/job.c
+++ b/job.c
@@ -60,6 +60,26 @@ bool JobVerbTable[JOB_VERB__MAX][JOB_STATUS__MAX] = {
     [JOB_VERB_DISMISS]              = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
 };
 
+/* Right now, this mutex is only needed to synchronize accesses to job->busy
+ * and job->sleep_timer, such as concurrent calls to job_do_yield and
+ * job_enter. */
+static QemuMutex job_mutex;
+
+static void job_lock(void)
+{
+    qemu_mutex_lock(&job_mutex);
+}
+
+static void job_unlock(void)
+{
+    qemu_mutex_unlock(&job_mutex);
+}
+
+static void __attribute__((__constructor__)) job_init(void)
+{
+    qemu_mutex_init(&job_mutex);
+}
+
 /* TODO Make static once the whole state machine is in job.c */
 void job_state_transition(Job *job, JobStatus s1)
 {
@@ -101,6 +121,16 @@ bool job_is_cancelled(Job *job)
     return job->cancelled;
 }
 
+bool job_started(Job *job)
+{
+    return job->co;
+}
+
+bool job_should_pause(Job *job)
+{
+    return job->pause_count > 0;
+}
+
 Job *job_next(Job *job)
 {
     if (!job) {
@@ -143,6 +173,9 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
     job->id            = g_strdup(job_id);
     job->refcnt        = 1;
     job->aio_context   = ctx;
+    job->busy          = false;
+    job->paused        = true;
+    job->pause_count   = 1;
 
     job_state_transition(job, JOB_STATUS_CREATED);
 
@@ -172,6 +205,110 @@ void job_unref(Job *job)
     }
 }
 
+void job_enter_cond(Job *job, bool(*fn)(Job *job))
+{
+    if (!job_started(job)) {
+        return;
+    }
+    if (job->deferred_to_main_loop) {
+        return;
+    }
+
+    job_lock();
+    if (job->busy) {
+        job_unlock();
+        return;
+    }
+
+    if (fn && !fn(job)) {
+        job_unlock();
+        return;
+    }
+
+    assert(!job->deferred_to_main_loop);
+    timer_del(&job->sleep_timer);
+    job->busy = true;
+    job_unlock();
+    aio_co_wake(job->co);
+}
+
+/* Yield, and schedule a timer to reenter the coroutine after @ns nanoseconds.
+ * Reentering the job coroutine with block_job_enter() before the timer has
+ * expired is allowed and cancels the timer.
+ *
+ * If @ns is (uint64_t) -1, no timer is scheduled and block_job_enter() must be
+ * called explicitly. */
+void coroutine_fn job_do_yield(Job *job, uint64_t ns)
+{
+    job_lock();
+    if (ns != -1) {
+        timer_mod(&job->sleep_timer, ns);
+    }
+    job->busy = false;
+    job_unlock();
+    qemu_coroutine_yield();
+
+    /* Set by job_enter before re-entering the coroutine.  */
+    assert(job->busy);
+}
+
+void coroutine_fn job_pause_point(Job *job)
+{
+    assert(job && job_started(job));
+
+    if (!job_should_pause(job)) {
+        return;
+    }
+    if (job_is_cancelled(job)) {
+        return;
+    }
+
+    if (job->driver->pause) {
+        job->driver->pause(job);
+    }
+
+    if (job_should_pause(job) && !job_is_cancelled(job)) {
+        JobStatus status = job->status;
+        job_state_transition(job, status == JOB_STATUS_READY
+                                  ? JOB_STATUS_STANDBY
+                                  : JOB_STATUS_PAUSED);
+        job->paused = true;
+        job_do_yield(job, -1);
+        job->paused = false;
+        job_state_transition(job, status);
+    }
+
+    if (job->driver->resume) {
+        job->driver->resume(job);
+    }
+}
+
+/**
+ * All jobs must allow a pause point before entering their job proper. This
+ * ensures that jobs can be paused prior to being started, then resumed later.
+ */
+static void coroutine_fn job_co_entry(void *opaque)
+{
+    Job *job = opaque;
+
+    assert(job && job->driver && job->driver->start);
+    job_pause_point(job);
+    job->driver->start(job);
+}
+
+
+void job_start(Job *job)
+{
+    assert(job && !job_started(job) && job->paused &&
+           job->driver && job->driver->start);
+    job->co = qemu_coroutine_create(job_co_entry, job);
+    job->pause_count--;
+    job->busy = true;
+    job->paused = false;
+    job_state_transition(job, JOB_STATUS_RUNNING);
+    aio_co_enter(job->aio_context, job->co);
+}
+
 typedef struct {
     Job *job;
     JobDeferToMainLoopFn *fn;
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index 4f8cba8377..c9f2f9b183 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -524,8 +524,8 @@ BlockJobDriver test_job_driver = {
     .job_driver = {
         .instance_size  = sizeof(TestBlockJob),
         .free           = block_job_free,
+        .start          = test_job_start,
     },
-    .start          = test_job_start,
     .complete       = test_job_complete,
 };
 
@@ -549,47 +549,47 @@ static void test_blockjob_common(enum drain_type drain_type)
     job = block_job_create("job0", &test_job_driver, NULL, src, 0, BLK_PERM_ALL,
                            0, 0, NULL, NULL, &error_abort);
     block_job_add_bdrv(job, "target", target, 0, BLK_PERM_ALL, &error_abort);
-    block_job_start(job);
+    job_start(&job->job);
 
-    g_assert_cmpint(job->pause_count, ==, 0);
-    g_assert_false(job->paused);
-    g_assert_false(job->busy); /* We're in block_job_sleep_ns() */
+    g_assert_cmpint(job->job.pause_count, ==, 0);
+    g_assert_false(job->job.paused);
+    g_assert_false(job->job.busy); /* We're in block_job_sleep_ns() */
 
     do_drain_begin(drain_type, src);
 
     if (drain_type == BDRV_DRAIN_ALL) {
         /* bdrv_drain_all() drains both src and target */
-        g_assert_cmpint(job->pause_count, ==, 2);
+        g_assert_cmpint(job->job.pause_count, ==, 2);
     } else {
-        g_assert_cmpint(job->pause_count, ==, 1);
+        g_assert_cmpint(job->job.pause_count, ==, 1);
     }
     /* XXX We don't wait until the job is actually paused. Is this okay? */
-    /* g_assert_true(job->paused); */
-    g_assert_false(job->busy); /* The job is paused */
+    /* g_assert_true(job->job.paused); */
+    g_assert_false(job->job.busy); /* The job is paused */
 
     do_drain_end(drain_type, src);
 
-    g_assert_cmpint(job->pause_count, ==, 0);
-    g_assert_false(job->paused);
-    g_assert_false(job->busy); /* We're in block_job_sleep_ns() */
+    g_assert_cmpint(job->job.pause_count, ==, 0);
+    g_assert_false(job->job.paused);
+    g_assert_false(job->job.busy); /* We're in block_job_sleep_ns() */
 
     do_drain_begin(drain_type, target);
 
     if (drain_type == BDRV_DRAIN_ALL) {
         /* bdrv_drain_all() drains both src and target */
-        g_assert_cmpint(job->pause_count, ==, 2);
+        g_assert_cmpint(job->job.pause_count, ==, 2);
     } else {
-        g_assert_cmpint(job->pause_count, ==, 1);
+        g_assert_cmpint(job->job.pause_count, ==, 1);
     }
     /* XXX We don't wait until the job is actually paused. Is this okay? */
-    /* g_assert_true(job->paused); */
-    g_assert_false(job->busy); /* The job is paused */
+    /* g_assert_true(job->job.paused); */
+    g_assert_false(job->job.busy); /* The job is paused */
 
     do_drain_end(drain_type, target);
 
-    g_assert_cmpint(job->pause_count, ==, 0);
-    g_assert_false(job->paused);
-    g_assert_false(job->busy); /* We're in block_job_sleep_ns() */
+    g_assert_cmpint(job->job.pause_count, ==, 0);
+    g_assert_false(job->job.paused);
+    g_assert_false(job->job.busy); /* We're in block_job_sleep_ns() */
 
     ret = block_job_complete_sync(job, &error_abort);
     g_assert_cmpint(ret, ==, 0);
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index c03f9662d8..323e154a00 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -78,8 +78,8 @@ static const BlockJobDriver test_block_job_driver = {
     .job_driver = {
         .instance_size = sizeof(TestBlockJob),
         .free          = block_job_free,
+        .start         = test_block_job_run,
     },
-    .start = test_block_job_run,
 };
 
 /* Create a block job that completes with a given return code after a given
@@ -125,7 +125,7 @@ static void test_single_job(int expected)
 
     txn = block_job_txn_new();
     job = test_block_job_start(1, true, expected, &result, txn);
-    block_job_start(job);
+    job_start(&job->job);
 
     if (expected == -ECANCELED) {
         block_job_cancel(job, false);
@@ -165,8 +165,8 @@ static void test_pair_jobs(int expected1, int expected2)
     txn = block_job_txn_new();
     job1 = test_block_job_start(1, true, expected1, &result1, txn);
     job2 = test_block_job_start(2, true, expected2, &result2, txn);
-    block_job_start(job1);
-    block_job_start(job2);
+    job_start(&job1->job);
+    job_start(&job2->job);
 
     /* Release our reference now to trigger as many nice
      * use-after-free bugs as possible.
@@ -227,8 +227,8 @@ static void test_pair_jobs_fail_cancel_race(void)
     txn = block_job_txn_new();
     job1 = test_block_job_start(1, true, -ECANCELED, &result1, txn);
     job2 = test_block_job_start(2, false, 0, &result2, txn);
-    block_job_start(job1);
-    block_job_start(job2);
+    job_start(&job1->job);
+    job_start(&job2->job);
 
     block_job_cancel(job1, false);
 
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 5f43bd72a4..1d18325feb 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -199,8 +199,8 @@ static const BlockJobDriver test_cancel_driver = {
     .job_driver = {
         .instance_size = sizeof(CancelJob),
         .free          = block_job_free,
+        .start         = cancel_job_start,
     },
-    .start         = cancel_job_start,
     .complete      = cancel_job_complete,
 };
 
@@ -254,7 +254,7 @@ static void test_cancel_running(void)
 
     s = create_common(&job);
 
-    block_job_start(job);
+    job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     cancel_common(s);
@@ -267,7 +267,7 @@ static void test_cancel_paused(void)
 
     s = create_common(&job);
 
-    block_job_start(job);
+    job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     block_job_user_pause(job, &error_abort);
@@ -284,7 +284,7 @@ static void test_cancel_ready(void)
 
     s = create_common(&job);
 
-    block_job_start(job);
+    job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
@@ -301,7 +301,7 @@ static void test_cancel_standby(void)
 
     s = create_common(&job);
 
-    block_job_start(job);
+    job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
@@ -322,7 +322,7 @@ static void test_cancel_pending(void)
 
     s = create_common(&job);
 
-    block_job_start(job);
+    job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
@@ -346,7 +346,7 @@ static void test_cancel_concluded(void)
 
     s = create_common(&job);
 
-    block_job_start(job);
+    job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
-- 
2.13.6

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

* [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (17 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code " Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 16:57   ` Max Reitz
  2018-05-14 23:10   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job Kevin Wolf
                   ` (23 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

There is nothing block layer specific about block_job_sleep_ns(), so
move the function to Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob_int.h | 11 -----------
 include/qemu/job.h           | 17 +++++++++++++++++
 block/backup.c               |  2 +-
 block/commit.c               |  2 +-
 block/mirror.c               |  4 ++--
 block/stream.c               |  2 +-
 blockjob.c                   | 27 ---------------------------
 job.c                        | 32 ++++++++++++++++++++++++++++++++
 tests/test-bdrv-drain.c      |  8 ++++----
 tests/test-blockjob-txn.c    |  2 +-
 tests/test-blockjob.c        |  2 +-
 11 files changed, 60 insertions(+), 49 deletions(-)

diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 0a614a89b8..8937f5b163 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -134,17 +134,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 void block_job_free(Job *job);
 
 /**
- * block_job_sleep_ns:
- * @job: The job that calls the function.
- * @ns: How many nanoseconds to stop for.
- *
- * Put the job to sleep (assuming that it wasn't canceled) for @ns
- * %QEMU_CLOCK_REALTIME nanoseconds.  Canceling the job will immediately
- * interrupt the wait.
- */
-void block_job_sleep_ns(BlockJob *job, int64_t ns);
-
-/**
  * block_job_yield:
  * @job: The job that calls the function.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 00c7cda9a3..ddfa824315 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -168,6 +168,13 @@ void job_enter_cond(Job *job, bool(*fn)(Job *job));
 void job_start(Job *job);
 
 /**
+ * @job: The job to enter.
+ *
+ * Continue the specified job by entering the coroutine.
+ */
+void job_enter(Job *job);
+
+/**
  * @job: The job that is ready to pause.
  *
  * Pause now if job_pause() has been called. Jobs that perform lots of I/O
@@ -175,6 +182,16 @@ void job_start(Job *job);
  */
 void coroutine_fn job_pause_point(Job *job);
 
+/**
+ * @job: The job that calls the function.
+ * @ns: How many nanoseconds to stop for.
+ *
+ * Put the job to sleep (assuming that it wasn't canceled) for @ns
+ * %QEMU_CLOCK_REALTIME nanoseconds.  Canceling the job will immediately
+ * interrupt the wait.
+ */
+void coroutine_fn job_sleep_ns(Job *job, int64_t ns);
+
 
 /** Returns the JobType of a given Job. */
 JobType job_type(Job *job);
diff --git a/block/backup.c b/block/backup.c
index 7d9aad9749..f3a4f7c898 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -338,7 +338,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
      * return. Without a yield, the VM would not reboot. */
     delay_ns = block_job_ratelimit_get_delay(&job->common, job->bytes_read);
     job->bytes_read = 0;
-    block_job_sleep_ns(&job->common, delay_ns);
+    job_sleep_ns(&job->common.job, delay_ns);
 
     if (job_is_cancelled(&job->common.job)) {
         return true;
diff --git a/block/commit.c b/block/commit.c
index 2fbc31077a..1c6cb6c298 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -172,7 +172,7 @@ static void coroutine_fn commit_run(void *opaque)
         /* Note that even when no rate limit is applied we need to yield
          * with no pending I/O here so that bdrv_drain_all() returns.
          */
-        block_job_sleep_ns(&s->common, delay_ns);
+        job_sleep_ns(&s->common.job, delay_ns);
         if (job_is_cancelled(&s->common.job)) {
             break;
         }
diff --git a/block/mirror.c b/block/mirror.c
index 5e8e0869be..6d54729557 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -595,7 +595,7 @@ static void mirror_throttle(MirrorBlockJob *s)
 
     if (now - s->last_pause_ns > SLICE_TIME) {
         s->last_pause_ns = now;
-        block_job_sleep_ns(&s->common, 0);
+        job_sleep_ns(&s->common.job, 0);
     } else {
         job_pause_point(&s->common.job);
     }
@@ -869,7 +869,7 @@ static void coroutine_fn mirror_run(void *opaque)
             delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
         }
         trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
-        block_job_sleep_ns(&s->common, delay_ns);
+        job_sleep_ns(&s->common.job, delay_ns);
         if (job_is_cancelled(&s->common.job) &&
             (!s->synced || s->common.force))
         {
diff --git a/block/stream.c b/block/stream.c
index 6d8b7b6eee..1faab02086 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -140,7 +140,7 @@ static void coroutine_fn stream_run(void *opaque)
         /* Note that even when no rate limit is applied we need to yield
          * with no pending I/O here so that bdrv_drain_all() returns.
          */
-        block_job_sleep_ns(&s->common, delay_ns);
+        job_sleep_ns(&s->common.job, delay_ns);
         if (job_is_cancelled(&s->common.job)) {
             break;
         }
diff --git a/blockjob.c b/blockjob.c
index 323dad96a3..342ec74f66 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -174,7 +174,6 @@ void block_job_free(Job *job)
                                     block_job_detach_aio_context, bjob);
     blk_unref(bjob->blk);
     error_free(bjob->blocker);
-    assert(!timer_pending(&bjob->job.sleep_timer));
 }
 
 static void block_job_attached_aio_context(AioContext *new_context,
@@ -283,13 +282,6 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
     return job->driver;
 }
 
-static void block_job_sleep_timer_cb(void *opaque)
-{
-    BlockJob *job = opaque;
-
-    block_job_enter(job);
-}
-
 static void block_job_decommission(BlockJob *job)
 {
     assert(job);
@@ -859,9 +851,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->opaque        = opaque;
     job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
-    aio_timer_init(qemu_get_aio_context(), &job->job.sleep_timer,
-                   QEMU_CLOCK_REALTIME, SCALE_NS,
-                   block_job_sleep_timer_cb, job);
 
     error_setg(&job->blocker, "block device is in use by block job: %s",
                job_type_str(&job->job));
@@ -924,22 +913,6 @@ void block_job_enter(BlockJob *job)
     job_enter_cond(&job->job, NULL);
 }
 
-void block_job_sleep_ns(BlockJob *job, int64_t ns)
-{
-    assert(job->job.busy);
-
-    /* Check cancellation *before* setting busy = false, too!  */
-    if (job_is_cancelled(&job->job)) {
-        return;
-    }
-
-    if (!job_should_pause(&job->job)) {
-        job_do_yield(&job->job, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + ns);
-    }
-
-    job_pause_point(&job->job);
-}
-
 void block_job_yield(BlockJob *job)
 {
     assert(job->job.busy);
diff --git a/job.c b/job.c
index b4cece1a82..932ccea105 100644
--- a/job.c
+++ b/job.c
@@ -152,6 +152,13 @@ Job *job_get(const char *id)
     return NULL;
 }
 
+static void job_sleep_timer_cb(void *opaque)
+{
+    Job *job = opaque;
+
+    job_enter(job);
+}
+
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
                  Error **errp)
 {
@@ -178,6 +185,9 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
     job->pause_count   = 1;
 
     job_state_transition(job, JOB_STATUS_CREATED);
+    aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
+                   QEMU_CLOCK_REALTIME, SCALE_NS,
+                   job_sleep_timer_cb, job);
 
     QLIST_INSERT_HEAD(&jobs, job, job_list);
 
@@ -193,6 +203,7 @@ void job_unref(Job *job)
 {
     if (--job->refcnt == 0) {
         assert(job->status == JOB_STATUS_NULL);
+        assert(!timer_pending(&job->sleep_timer));
 
         if (job->driver->free) {
             job->driver->free(job);
@@ -232,6 +243,11 @@ void job_enter_cond(Job *job, bool(*fn)(Job *job))
     aio_co_wake(job->co);
 }
 
+void job_enter(Job *job)
+{
+    job_enter_cond(job, NULL);
+}
+
 /* Yield, and schedule a timer to reenter the coroutine after @ns nanoseconds.
  * Reentering the job coroutine with block_job_enter() before the timer has
  * expired is allowed and cancels the timer.
@@ -283,6 +299,22 @@ void coroutine_fn job_pause_point(Job *job)
     }
 }
 
+void coroutine_fn job_sleep_ns(Job *job, int64_t ns)
+{
+    assert(job->busy);
+
+    /* Check cancellation *before* setting busy = false, too!  */
+    if (job_is_cancelled(job)) {
+        return;
+    }
+
+    if (!job_should_pause(job)) {
+        job_do_yield(job, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + ns);
+    }
+
+    job_pause_point(job);
+}
+
 /**
  * All jobs must allow a pause point before entering their job proper. This
  * ensures that jobs can be paused prior to being started, then resumed later.
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index c9f2f9b183..50232f5eaf 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -508,7 +508,7 @@ static void coroutine_fn test_job_start(void *opaque)
 
     block_job_event_ready(&s->common);
     while (!s->should_complete) {
-        block_job_sleep_ns(&s->common, 100000);
+        job_sleep_ns(&s->common.job, 100000);
     }
 
     job_defer_to_main_loop(&s->common.job, test_job_completed, NULL);
@@ -553,7 +553,7 @@ static void test_blockjob_common(enum drain_type drain_type)
 
     g_assert_cmpint(job->job.pause_count, ==, 0);
     g_assert_false(job->job.paused);
-    g_assert_false(job->job.busy); /* We're in block_job_sleep_ns() */
+    g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
 
     do_drain_begin(drain_type, src);
 
@@ -571,7 +571,7 @@ static void test_blockjob_common(enum drain_type drain_type)
 
     g_assert_cmpint(job->job.pause_count, ==, 0);
     g_assert_false(job->job.paused);
-    g_assert_false(job->job.busy); /* We're in block_job_sleep_ns() */
+    g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
 
     do_drain_begin(drain_type, target);
 
@@ -589,7 +589,7 @@ static void test_blockjob_common(enum drain_type drain_type)
 
     g_assert_cmpint(job->job.pause_count, ==, 0);
     g_assert_false(job->job.paused);
-    g_assert_false(job->job.busy); /* We're in block_job_sleep_ns() */
+    g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
 
     ret = block_job_complete_sync(job, &error_abort);
     g_assert_cmpint(ret, ==, 0);
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 323e154a00..0e6162bc71 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -45,7 +45,7 @@ static void coroutine_fn test_block_job_run(void *opaque)
 
     while (s->iterations--) {
         if (s->use_timer) {
-            block_job_sleep_ns(job, 0);
+            job_sleep_ns(&job->job, 0);
         } else {
             block_job_yield(job);
         }
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 1d18325feb..b329bd5274 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -188,7 +188,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
             block_job_event_ready(&s->common);
         }
 
-        block_job_sleep_ns(&s->common, 100000);
+        job_sleep_ns(&s->common.job, 100000);
     }
 
  defer:
-- 
2.13.6

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

* [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (18 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 17:13   ` Max Reitz
  2018-05-14 23:23   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed() Kevin Wolf
                   ` (22 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

While we already moved the state related to job pausing to Job, the
functions to do were still BlockJob only. This commit moves them over to
Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     | 32 -------------------
 include/block/blockjob_int.h |  7 +++++
 include/qemu/job.h           | 37 ++++++++++++++++++++++
 block/backup.c               |  1 +
 block/commit.c               |  1 +
 block/mirror.c               |  2 ++
 block/stream.c               |  1 +
 blockdev.c                   |  6 ++--
 blockjob.c                   | 73 ++++++++++----------------------------------
 job.c                        | 51 +++++++++++++++++++++++++++++++
 tests/test-bdrv-drain.c      |  1 +
 tests/test-blockjob-txn.c    |  1 +
 tests/test-blockjob.c        |  6 ++--
 13 files changed, 125 insertions(+), 94 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index c0448206e2..ce136ff2ac 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -57,12 +57,6 @@ typedef struct BlockJob {
     bool force;
 
     /**
-     * Set to true if the job is paused by user.  Can be unpaused with the
-     * block-job-resume QMP command.
-     */
-    bool user_paused;
-
-    /**
      * Set to true when the job is ready to be completed.
      */
     bool ready;
@@ -248,32 +242,6 @@ void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);
 BlockJobInfo *block_job_query(BlockJob *job, Error **errp);
 
 /**
- * block_job_user_pause:
- * @job: The job to be paused.
- *
- * Asynchronously pause the specified job.
- * Do not allow a resume until a matching call to block_job_user_resume.
- */
-void block_job_user_pause(BlockJob *job, Error **errp);
-
-/**
- * block_job_paused:
- * @job: The job to query.
- *
- * Returns true if the job is user-paused.
- */
-bool block_job_user_paused(BlockJob *job);
-
-/**
- * block_job_user_resume:
- * @job: The job to be resumed.
- *
- * Resume the specified job.
- * Must be paired with a preceding block_job_user_pause.
- */
-void block_job_user_resume(BlockJob *job, Error **errp);
-
-/**
  * block_job_user_cancel:
  * @job: The job to be cancelled.
  * @force: Quit a job without waiting for data to be in sync.
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 8937f5b163..7e705ae0e9 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -134,6 +134,13 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 void block_job_free(Job *job);
 
 /**
+ * block_job_user_resume:
+ * Callback to be used for JobDriver.user_resume in all block jobs. Resets the
+ * iostatus when the user resumes @job.
+ */
+void block_job_user_resume(Job *job);
+
+/**
  * block_job_yield:
  * @job: The job that calls the function.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index ddfa824315..0ba6cb3161 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -83,6 +83,12 @@ typedef struct Job {
     bool paused;
 
     /**
+     * Set to true if the job is paused by user.  Can be unpaused with the
+     * block-job-resume QMP command.
+     */
+    bool user_paused;
+
+    /**
      * Set to true if the job should cancel itself.  The flag must
      * always be tested just before toggling the busy flag from false
      * to true.  After a job has been cancelled, it should only yield
@@ -124,6 +130,12 @@ struct JobDriver {
      */
     void coroutine_fn (*resume)(Job *job);
 
+    /**
+     * Called when the job is resumed by the user (i.e. user_paused becomes
+     * false). .user_resume is called before .resume.
+     */
+    void (*user_resume)(Job *job);
+
     /** Called when the job is freed */
     void (*free)(Job *job);
 };
@@ -203,6 +215,31 @@ const char *job_type_str(Job *job);
 bool job_is_cancelled(Job *job);
 
 /**
+ * Request @job to pause at the next pause point. Must be paired with
+ * job_resume(). If the job is supposed to be resumed by user action, call
+ * job_user_pause() instead.
+ */
+void job_pause(Job *job);
+
+/** Resumes a @job paused with job_pause. */
+void job_resume(Job *job);
+
+/**
+ * Asynchronously pause the specified @job.
+ * Do not allow a resume until a matching call to job_user_resume.
+ */
+void job_user_pause(Job *job, Error **errp);
+
+/** Returns true if the job is user-paused. */
+bool job_user_paused(Job *job);
+
+/**
+ * Resume the specified @job.
+ * Must be paired with a preceding job_user_pause.
+ */
+void job_user_resume(Job *job, Error **errp);
+
+/**
  * Get the next element from the list of block jobs after @job, or the
  * first one if @job is %NULL.
  *
diff --git a/block/backup.c b/block/backup.c
index f3a4f7c898..4d011d5a5c 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -528,6 +528,7 @@ static const BlockJobDriver backup_job_driver = {
         .instance_size          = sizeof(BackupBlockJob),
         .job_type               = JOB_TYPE_BACKUP,
         .free                   = block_job_free,
+        .user_resume            = block_job_user_resume,
         .start                  = backup_run,
     },
     .commit                 = backup_commit,
diff --git a/block/commit.c b/block/commit.c
index 1c6cb6c298..c4a98e5804 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -220,6 +220,7 @@ static const BlockJobDriver commit_job_driver = {
         .instance_size = sizeof(CommitBlockJob),
         .job_type      = JOB_TYPE_COMMIT,
         .free          = block_job_free,
+        .user_resume   = block_job_user_resume,
         .start         = commit_run,
     },
 };
diff --git a/block/mirror.c b/block/mirror.c
index 6d54729557..80f068ca75 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -991,6 +991,7 @@ static const BlockJobDriver mirror_job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
         .job_type               = JOB_TYPE_MIRROR,
         .free                   = block_job_free,
+        .user_resume            = block_job_user_resume,
         .start                  = mirror_run,
         .pause                  = mirror_pause,
     },
@@ -1004,6 +1005,7 @@ static const BlockJobDriver commit_active_job_driver = {
         .instance_size          = sizeof(MirrorBlockJob),
         .job_type               = JOB_TYPE_COMMIT,
         .free                   = block_job_free,
+        .user_resume            = block_job_user_resume,
         .start                  = mirror_run,
         .pause                  = mirror_pause,
     },
diff --git a/block/stream.c b/block/stream.c
index 1faab02086..e81b488a22 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -214,6 +214,7 @@ static const BlockJobDriver stream_job_driver = {
         .job_type      = JOB_TYPE_STREAM,
         .free          = block_job_free,
         .start         = stream_run,
+        .user_resume   = block_job_user_resume,
     },
 };
 
diff --git a/blockdev.c b/blockdev.c
index c551fdf39a..3533c0dd6a 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3844,7 +3844,7 @@ void qmp_block_job_cancel(const char *device,
         force = false;
     }
 
-    if (block_job_user_paused(job) && !force) {
+    if (job_user_paused(&job->job) && !force) {
         error_setg(errp, "The block job for device '%s' is currently paused",
                    device);
         goto out;
@@ -3866,7 +3866,7 @@ void qmp_block_job_pause(const char *device, Error **errp)
     }
 
     trace_qmp_block_job_pause(job);
-    block_job_user_pause(job, errp);
+    job_user_pause(&job->job, errp);
     aio_context_release(aio_context);
 }
 
@@ -3880,7 +3880,7 @@ void qmp_block_job_resume(const char *device, Error **errp)
     }
 
     trace_qmp_block_job_resume(job);
-    block_job_user_resume(job, errp);
+    job_user_resume(&job->job, errp);
     aio_context_release(aio_context);
 }
 
diff --git a/blockjob.c b/blockjob.c
index 342ec74f66..a2b6bfc975 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -141,21 +141,6 @@ static void block_job_txn_del_job(BlockJob *job)
     }
 }
 
-static void block_job_pause(BlockJob *job)
-{
-    job->job.pause_count++;
-}
-
-static void block_job_resume(BlockJob *job)
-{
-    assert(job->job.pause_count > 0);
-    job->job.pause_count--;
-    if (job->job.pause_count) {
-        return;
-    }
-    block_job_enter(job);
-}
-
 static void block_job_attached_aio_context(AioContext *new_context,
                                            void *opaque);
 static void block_job_detach_aio_context(void *opaque);
@@ -186,7 +171,7 @@ static void block_job_attached_aio_context(AioContext *new_context,
         job->driver->attached_aio_context(job, new_context);
     }
 
-    block_job_resume(job);
+    job_resume(&job->job);
 }
 
 static void block_job_drain(BlockJob *job)
@@ -207,7 +192,7 @@ static void block_job_detach_aio_context(void *opaque)
     /* In case the job terminates during aio_poll()... */
     job_ref(&job->job);
 
-    block_job_pause(job);
+    job_pause(&job->job);
 
     while (!job->job.paused && !job->completed) {
         block_job_drain(job);
@@ -226,13 +211,13 @@ static char *child_job_get_parent_desc(BdrvChild *c)
 static void child_job_drained_begin(BdrvChild *c)
 {
     BlockJob *job = c->opaque;
-    block_job_pause(job);
+    job_pause(&job->job);
 }
 
 static void child_job_drained_end(BdrvChild *c)
 {
     BlockJob *job = c->opaque;
-    block_job_resume(job);
+    job_resume(&job->job);
 }
 
 static const BdrvChildRole child_job = {
@@ -389,9 +374,9 @@ static void block_job_cancel_async(BlockJob *job, bool force)
     if (job->iostatus != BLOCK_DEVICE_IO_STATUS_OK) {
         block_job_iostatus_reset(job);
     }
-    if (job->user_paused) {
+    if (job->job.user_paused) {
         /* Do not call block_job_enter here, the caller will handle it.  */
-        job->user_paused = false;
+        job->job.user_paused = false;
         job->job.pause_count--;
     }
     job->job.cancelled = true;
@@ -621,39 +606,6 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
     *jobptr = NULL;
 }
 
-void block_job_user_pause(BlockJob *job, Error **errp)
-{
-    if (job_apply_verb(&job->job, JOB_VERB_PAUSE, errp)) {
-        return;
-    }
-    if (job->user_paused) {
-        error_setg(errp, "Job is already paused");
-        return;
-    }
-    job->user_paused = true;
-    block_job_pause(job);
-}
-
-bool block_job_user_paused(BlockJob *job)
-{
-    return job->user_paused;
-}
-
-void block_job_user_resume(BlockJob *job, Error **errp)
-{
-    assert(job);
-    if (!job->user_paused || job->job.pause_count <= 0) {
-        error_setg(errp, "Can't resume a job that was not paused");
-        return;
-    }
-    if (job_apply_verb(&job->job, JOB_VERB_RESUME, errp)) {
-        return;
-    }
-    block_job_iostatus_reset(job);
-    job->user_paused = false;
-    block_job_resume(job);
-}
-
 void block_job_cancel(BlockJob *job, bool force)
 {
     if (job->job.status == JOB_STATUS_CONCLUDED) {
@@ -844,6 +796,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
     assert(is_block_job(&job->job));
     assert(job->job.driver->free == &block_job_free);
+    assert(job->job.driver->user_resume == &block_job_user_resume);
 
     job->driver        = driver;
     job->blk           = blk;
@@ -934,10 +887,16 @@ void block_job_iostatus_reset(BlockJob *job)
     if (job->iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
         return;
     }
-    assert(job->user_paused && job->job.pause_count > 0);
+    assert(job->job.user_paused && job->job.pause_count > 0);
     job->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
 }
 
+void block_job_user_resume(Job *job)
+{
+    BlockJob *bjob = container_of(job, BlockJob, job);
+    block_job_iostatus_reset(bjob);
+}
+
 void block_job_event_ready(BlockJob *job)
 {
     job_state_transition(&job->job, JOB_STATUS_READY);
@@ -984,9 +943,9 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
                                         action, &error_abort);
     }
     if (action == BLOCK_ERROR_ACTION_STOP) {
-        block_job_pause(job);
+        job_pause(&job->job);
         /* make the pause user visible, which will be resumed from QMP. */
-        job->user_paused = true;
+        job->job.user_paused = true;
         block_job_iostatus_set_err(job, error);
     }
     return action;
diff --git a/job.c b/job.c
index 932ccea105..94ad01a51a 100644
--- a/job.c
+++ b/job.c
@@ -341,6 +341,57 @@ void job_start(Job *job)
     aio_co_enter(job->aio_context, job->co);
 }
 
+void job_pause(Job *job)
+{
+    job->pause_count++;
+}
+
+void job_resume(Job *job)
+{
+    assert(job->pause_count > 0);
+    job->pause_count--;
+    if (job->pause_count) {
+        return;
+    }
+    job_enter(job);
+}
+
+void job_user_pause(Job *job, Error **errp)
+{
+    if (job_apply_verb(job, JOB_VERB_PAUSE, errp)) {
+        return;
+    }
+    if (job->user_paused) {
+        error_setg(errp, "Job is already paused");
+        return;
+    }
+    job->user_paused = true;
+    job_pause(job);
+}
+
+bool job_user_paused(Job *job)
+{
+    return job->user_paused;
+}
+
+void job_user_resume(Job *job, Error **errp)
+{
+    assert(job);
+    if (!job->user_paused || job->pause_count <= 0) {
+        error_setg(errp, "Can't resume a job that was not paused");
+        return;
+    }
+    if (job_apply_verb(job, JOB_VERB_RESUME, errp)) {
+        return;
+    }
+    if (job->driver->user_resume) {
+        job->driver->user_resume(job);
+    }
+    job->user_paused = false;
+    job_resume(job);
+}
+
+
 typedef struct {
     Job *job;
     JobDeferToMainLoopFn *fn;
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index 50232f5eaf..c993512f66 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -524,6 +524,7 @@ BlockJobDriver test_job_driver = {
     .job_driver = {
         .instance_size  = sizeof(TestBlockJob),
         .free           = block_job_free,
+        .user_resume    = block_job_user_resume,
         .start          = test_job_start,
     },
     .complete       = test_job_complete,
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 0e6162bc71..93d1ff0859 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -78,6 +78,7 @@ static const BlockJobDriver test_block_job_driver = {
     .job_driver = {
         .instance_size = sizeof(TestBlockJob),
         .free          = block_job_free,
+        .user_resume   = block_job_user_resume,
         .start         = test_block_job_run,
     },
 };
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index b329bd5274..ceb59600ed 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -20,6 +20,7 @@ static const BlockJobDriver test_block_job_driver = {
     .job_driver = {
         .instance_size = sizeof(BlockJob),
         .free          = block_job_free,
+        .user_resume   = block_job_user_resume,
     },
 };
 
@@ -199,6 +200,7 @@ static const BlockJobDriver test_cancel_driver = {
     .job_driver = {
         .instance_size = sizeof(CancelJob),
         .free          = block_job_free,
+        .user_resume   = block_job_user_resume,
         .start         = cancel_job_start,
     },
     .complete      = cancel_job_complete,
@@ -270,7 +272,7 @@ static void test_cancel_paused(void)
     job_start(&job->job);
     assert(job->job.status == JOB_STATUS_RUNNING);
 
-    block_job_user_pause(job, &error_abort);
+    job_user_pause(&job->job, &error_abort);
     block_job_enter(job);
     assert(job->job.status == JOB_STATUS_PAUSED);
 
@@ -308,7 +310,7 @@ static void test_cancel_standby(void)
     block_job_enter(job);
     assert(job->job.status == JOB_STATUS_READY);
 
-    block_job_user_pause(job, &error_abort);
+    job_user_pause(&job->job, &error_abort);
     block_job_enter(job);
     assert(job->job.status == JOB_STATUS_STANDBY);
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (19 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 17:33   ` Max Reitz
  2018-05-14 23:43   ` John Snow
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job Kevin Wolf
                   ` (21 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Since we introduced an explicit status to block job, BlockJob.completed
is redundant because it can be derived from the status. Remove the field
from BlockJob and add a function to derive it from the status at the Job
level.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h |  3 ---
 include/qemu/job.h       |  3 +++
 blockjob.c               | 16 +++++++---------
 job.c                    | 22 ++++++++++++++++++++++
 qemu-img.c               |  4 ++--
 5 files changed, 34 insertions(+), 14 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index ce136ff2ac..a2d16a700d 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -88,9 +88,6 @@ typedef struct BlockJob {
     /** The opaque value that is passed to the completion function.  */
     void *opaque;
 
-    /** True when job has reported completion by calling block_job_completed. */
-    bool completed;
-
     /** ret code passed to block_job_completed. */
     int ret;
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 0ba6cb3161..87b0500795 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -214,6 +214,9 @@ const char *job_type_str(Job *job);
 /** Returns whether the job is scheduled for cancellation. */
 bool job_is_cancelled(Job *job);
 
+/** Returns whether the job is in a completed state. */
+bool job_is_completed(Job *job);
+
 /**
  * Request @job to pause at the next pause point. Must be paired with
  * job_resume(). If the job is supposed to be resumed by user action, call
diff --git a/blockjob.c b/blockjob.c
index a2b6bfc975..e0a51cfb3e 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -194,7 +194,7 @@ static void block_job_detach_aio_context(void *opaque)
 
     job_pause(&job->job);
 
-    while (!job->job.paused && !job->completed) {
+    while (!job->job.paused && !job_is_completed(&job->job)) {
         block_job_drain(job);
     }
 
@@ -270,7 +270,6 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
 static void block_job_decommission(BlockJob *job)
 {
     assert(job);
-    job->completed = true;
     job->job.busy = false;
     job->job.paused = false;
     job->job.deferred_to_main_loop = true;
@@ -335,7 +334,7 @@ static void block_job_clean(BlockJob *job)
 
 static int block_job_finalize_single(BlockJob *job)
 {
-    assert(job->completed);
+    assert(job_is_completed(&job->job));
 
     /* Ensure abort is called for late-transactional failures */
     block_job_update_rc(job);
@@ -428,10 +427,10 @@ static int block_job_finish_sync(BlockJob *job,
     /* block_job_drain calls block_job_enter, and it should be enough to
      * induce progress until the job completes or moves to the main thread.
     */
-    while (!job->job.deferred_to_main_loop && !job->completed) {
+    while (!job->job.deferred_to_main_loop && !job_is_completed(&job->job)) {
         block_job_drain(job);
     }
-    while (!job->completed) {
+    while (!job_is_completed(&job->job)) {
         aio_poll(qemu_get_aio_context(), true);
     }
     ret = (job_is_cancelled(&job->job) && job->ret == 0)
@@ -472,7 +471,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
     while (!QLIST_EMPTY(&txn->jobs)) {
         other_job = QLIST_FIRST(&txn->jobs);
         ctx = blk_get_aio_context(other_job->blk);
-        if (!other_job->completed) {
+        if (!job_is_completed(&other_job->job)) {
             assert(job_is_cancelled(&other_job->job));
             block_job_finish_sync(other_job, NULL, NULL);
         }
@@ -514,7 +513,7 @@ static void block_job_completed_txn_success(BlockJob *job)
      * txn.
      */
     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        if (!other_job->completed) {
+        if (!job_is_completed(&other_job->job)) {
             return;
         }
         assert(other_job->ret == 0);
@@ -848,9 +847,8 @@ void block_job_early_fail(BlockJob *job)
 
 void block_job_completed(BlockJob *job, int ret)
 {
-    assert(job && job->txn && !job->completed);
+    assert(job && job->txn && !job_is_completed(&job->job));
     assert(blk_bs(job->blk)->job == job);
-    job->completed = true;
     job->ret = ret;
     block_job_update_rc(job);
     trace_block_job_completed(job, ret, job->ret);
diff --git a/job.c b/job.c
index 94ad01a51a..60ccb0640b 100644
--- a/job.c
+++ b/job.c
@@ -121,6 +121,28 @@ bool job_is_cancelled(Job *job)
     return job->cancelled;
 }
 
+bool job_is_completed(Job *job)
+{
+    switch (job->status) {
+    case JOB_STATUS_UNDEFINED:
+    case JOB_STATUS_CREATED:
+    case JOB_STATUS_RUNNING:
+    case JOB_STATUS_PAUSED:
+    case JOB_STATUS_READY:
+    case JOB_STATUS_STANDBY:
+        return false;
+    case JOB_STATUS_WAITING:
+    case JOB_STATUS_PENDING:
+    case JOB_STATUS_ABORTING:
+    case JOB_STATUS_CONCLUDED:
+    case JOB_STATUS_NULL:
+        return true;
+    default:
+        g_assert_not_reached();
+    }
+    return false;
+}
+
 bool job_started(Job *job)
 {
     return job->co;
diff --git a/qemu-img.c b/qemu-img.c
index 82f69269ae..6a2431a653 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -867,9 +867,9 @@ static void run_block_job(BlockJob *job, Error **errp)
         aio_poll(aio_context, true);
         qemu_progress_print(job->len ?
                             ((float)job->offset / job->len * 100.f) : 0.0f, 0);
-    } while (!job->ready && !job->completed);
+    } while (!job->ready && !job_is_completed(&job->job));
 
-    if (!job->completed) {
+    if (!job_is_completed(&job->job)) {
         ret = block_job_complete_sync(job, errp);
     } else {
         ret = job->ret;
-- 
2.13.6

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

* [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (20 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 17:50   ` Max Reitz
  2018-05-16 19:13   ` Eric Blake
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending() Kevin Wolf
                   ` (20 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This renames the BlockJobCreateFlags constants, moves a few JOB_INTERNAL
checks to job_create() and the auto_{finalize,dismiss} fields from
BlockJob to Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     | 17 -----------------
 include/block/blockjob_int.h |  3 +--
 include/qemu/job.h           | 20 +++++++++++++++++++-
 block/commit.c               |  2 +-
 block/mirror.c               |  2 +-
 block/replication.c          |  4 ++--
 block/stream.c               |  2 +-
 blockdev.c                   | 14 +++++++-------
 blockjob.c                   | 27 +++++++--------------------
 job.c                        | 11 ++++++++++-
 qemu-img.c                   |  2 +-
 tests/test-blockjob-txn.c    |  2 +-
 tests/test-blockjob.c        |  4 ++--
 13 files changed, 53 insertions(+), 57 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index a2d16a700d..a74d5d02fd 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -91,27 +91,10 @@ typedef struct BlockJob {
     /** ret code passed to block_job_completed. */
     int ret;
 
-    /** True if this job should automatically finalize itself */
-    bool auto_finalize;
-
-    /** True if this job should automatically dismiss itself */
-    bool auto_dismiss;
-
     BlockJobTxn *txn;
     QLIST_ENTRY(BlockJob) txn_list;
 } BlockJob;
 
-typedef enum BlockJobCreateFlags {
-    /* Default behavior */
-    BLOCK_JOB_DEFAULT = 0x00,
-    /* BlockJob is not QMP-created and should not send QMP events */
-    BLOCK_JOB_INTERNAL = 0x01,
-    /* BlockJob requires manual finalize step */
-    BLOCK_JOB_MANUAL_FINALIZE = 0x02,
-    /* BlockJob requires manual dismiss step */
-    BLOCK_JOB_MANUAL_DISMISS = 0x04,
-} BlockJobCreateFlags;
-
 /**
  * block_job_next:
  * @job: A block job, or %NULL.
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 7e705ae0e9..88639f7efc 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -106,8 +106,7 @@ struct BlockJobDriver {
  * @bs: The block
  * @perm, @shared_perm: Permissions to request for @bs
  * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
- * @flags: Creation flags for the Block Job.
- *         See @BlockJobCreateFlags
+ * @flags: Creation flags for the Block Job. See @JobCreateFlags.
  * @cb: Completion function for the job.
  * @opaque: Opaque pointer value passed to @cb.
  * @errp: Error object.
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 87b0500795..9ad93ee862 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -99,6 +99,12 @@ typedef struct Job {
     /** Set to true when the job has deferred work to the main loop. */
     bool deferred_to_main_loop;
 
+    /** True if this job should automatically finalize itself */
+    bool auto_finalize;
+
+    /** True if this job should automatically dismiss itself */
+    bool auto_dismiss;
+
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 } Job;
@@ -140,6 +146,17 @@ struct JobDriver {
     void (*free)(Job *job);
 };
 
+typedef enum JobCreateFlags {
+    /* Default behavior */
+    JOB_DEFAULT = 0x00,
+    /* Job is not QMP-created and should not send QMP events */
+    JOB_INTERNAL = 0x01,
+    /* Job requires manual finalize step */
+    JOB_MANUAL_FINALIZE = 0x02,
+    /* Job requires manual dismiss step */
+    JOB_MANUAL_DISMISS = 0x04,
+} JobCreateFlags;
+
 
 /**
  * Create a new long-running job and return it.
@@ -147,10 +164,11 @@ struct JobDriver {
  * @job_id: The id of the newly-created job, or %NULL for internal jobs
  * @driver: The class object for the newly-created job.
  * @ctx: The AioContext to run the job coroutine in.
+ * @flags: Creation flags for the job. See @JobCreateFlags.
  * @errp: Error object.
  */
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 Error **errp);
+                 int flags, Error **errp);
 
 /**
  * Add a reference to Job refcnt, it will be decreased with job_unref, and then
diff --git a/block/commit.c b/block/commit.c
index c4a98e5804..7a6ae59d42 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -282,7 +282,7 @@ void commit_start(const char *job_id, BlockDriverState *bs,
     }
 
     s = block_job_create(job_id, &commit_job_driver, NULL, bs, 0, BLK_PERM_ALL,
-                         speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp);
+                         speed, JOB_DEFAULT, NULL, NULL, errp);
     if (!s) {
         return;
     }
diff --git a/block/mirror.c b/block/mirror.c
index 80f068ca75..df636298f1 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1282,7 +1282,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
     }
     is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
     base = mode == MIRROR_SYNC_MODE_TOP ? backing_bs(bs) : NULL;
-    mirror_start_job(job_id, bs, BLOCK_JOB_DEFAULT, target, replaces,
+    mirror_start_job(job_id, bs, JOB_DEFAULT, target, replaces,
                      speed, granularity, buf_size, backing_mode,
                      on_source_error, on_target_error, unmap, NULL, NULL,
                      &mirror_job_driver, is_none_mode, base, false,
diff --git a/block/replication.c b/block/replication.c
index 9ed6e0fb04..ff2e2028af 100644
--- a/block/replication.c
+++ b/block/replication.c
@@ -568,7 +568,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
         job = backup_job_create(NULL, s->secondary_disk->bs, s->hidden_disk->bs,
                                 0, MIRROR_SYNC_MODE_NONE, NULL, false,
                                 BLOCKDEV_ON_ERROR_REPORT,
-                                BLOCKDEV_ON_ERROR_REPORT, BLOCK_JOB_INTERNAL,
+                                BLOCKDEV_ON_ERROR_REPORT, JOB_INTERNAL,
                                 backup_job_completed, bs, NULL, &local_err);
         if (local_err) {
             error_propagate(errp, local_err);
@@ -693,7 +693,7 @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp)
 
         s->stage = BLOCK_REPLICATION_FAILOVER;
         commit_active_start(NULL, s->active_disk->bs, s->secondary_disk->bs,
-                            BLOCK_JOB_INTERNAL, 0, BLOCKDEV_ON_ERROR_REPORT,
+                            JOB_INTERNAL, 0, BLOCKDEV_ON_ERROR_REPORT,
                             NULL, replication_done, bs, true, errp);
         break;
     default:
diff --git a/block/stream.c b/block/stream.c
index e81b488a22..eee02538ed 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -242,7 +242,7 @@ void stream_start(const char *job_id, BlockDriverState *bs,
                          BLK_PERM_GRAPH_MOD,
                          BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED |
                          BLK_PERM_WRITE,
-                         speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp);
+                         speed, JOB_DEFAULT, NULL, NULL, errp);
     if (!s) {
         goto fail;
     }
diff --git a/blockdev.c b/blockdev.c
index 3533c0dd6a..278b92ce03 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3244,7 +3244,7 @@ void qmp_block_commit(bool has_job_id, const char *job_id, const char *device,
             goto out;
         }
         commit_active_start(has_job_id ? job_id : NULL, bs, base_bs,
-                            BLOCK_JOB_DEFAULT, speed, on_error,
+                            JOB_DEFAULT, speed, on_error,
                             filter_node_name, NULL, NULL, false, &local_err);
     } else {
         BlockDriverState *overlay_bs = bdrv_find_overlay(bs, top_bs);
@@ -3275,7 +3275,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
     AioContext *aio_context;
     QDict *options = NULL;
     Error *local_err = NULL;
-    int flags, job_flags = BLOCK_JOB_DEFAULT;
+    int flags, job_flags = JOB_DEFAULT;
     int64_t size;
     bool set_backing_hd = false;
 
@@ -3398,10 +3398,10 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
         }
     }
     if (!backup->auto_finalize) {
-        job_flags |= BLOCK_JOB_MANUAL_FINALIZE;
+        job_flags |= JOB_MANUAL_FINALIZE;
     }
     if (!backup->auto_dismiss) {
-        job_flags |= BLOCK_JOB_MANUAL_DISMISS;
+        job_flags |= JOB_MANUAL_DISMISS;
     }
 
     job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
@@ -3442,7 +3442,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
     Error *local_err = NULL;
     AioContext *aio_context;
     BlockJob *job = NULL;
-    int job_flags = BLOCK_JOB_DEFAULT;
+    int job_flags = JOB_DEFAULT;
 
     if (!backup->has_speed) {
         backup->speed = 0;
@@ -3491,10 +3491,10 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
         }
     }
     if (!backup->auto_finalize) {
-        job_flags |= BLOCK_JOB_MANUAL_FINALIZE;
+        job_flags |= JOB_MANUAL_FINALIZE;
     }
     if (!backup->auto_dismiss) {
-        job_flags |= BLOCK_JOB_MANUAL_DISMISS;
+        job_flags |= JOB_MANUAL_DISMISS;
     }
     job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
                             backup->sync, NULL, backup->compress,
diff --git a/blockjob.c b/blockjob.c
index e0a51cfb3e..4cfedbe791 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -286,7 +286,7 @@ static void block_job_do_dismiss(BlockJob *job)
 static void block_job_conclude(BlockJob *job)
 {
     job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
-    if (job->auto_dismiss || !job_started(&job->job)) {
+    if (job->job.auto_dismiss || !job_started(&job->job)) {
         block_job_do_dismiss(job);
     }
 }
@@ -484,7 +484,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
 
 static int block_job_needs_finalize(BlockJob *job)
 {
-    return !job->auto_finalize;
+    return !job->job.auto_finalize;
 }
 
 static void block_job_do_finalize(BlockJob *job)
@@ -689,8 +689,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info->io_status = job->iostatus;
     info->ready     = job->ready;
     info->status    = job->job.status;
-    info->auto_finalize = job->auto_finalize;
-    info->auto_dismiss  = job->auto_dismiss;
+    info->auto_finalize = job->job.auto_finalize;
+    info->auto_dismiss  = job->job.auto_dismiss;
     info->has_error = job->ret != 0;
     info->error     = job->ret ? g_strdup(strerror(-job->ret)) : NULL;
     return info;
@@ -737,7 +737,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
 static int block_job_event_pending(BlockJob *job)
 {
     job_state_transition(&job->job, JOB_STATUS_PENDING);
-    if (!job->auto_finalize && !block_job_is_internal(job)) {
+    if (!job->job.auto_finalize && !block_job_is_internal(job)) {
         qapi_event_send_block_job_pending(job_type(&job->job),
                                           job->job.id,
                                           &error_abort);
@@ -764,19 +764,8 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
         return NULL;
     }
 
-    if (job_id == NULL && !(flags & BLOCK_JOB_INTERNAL)) {
+    if (job_id == NULL && !(flags & JOB_INTERNAL)) {
         job_id = bdrv_get_device_name(bs);
-        if (!*job_id) {
-            error_setg(errp, "An explicit job ID is required for this node");
-            return NULL;
-        }
-    }
-
-    if (job_id) {
-        if (flags & BLOCK_JOB_INTERNAL) {
-            error_setg(errp, "Cannot specify job ID for internal block job");
-            return NULL;
-        }
     }
 
     blk = blk_new(perm, shared_perm);
@@ -787,7 +776,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     }
 
     job = job_create(job_id, &driver->job_driver, blk_get_aio_context(blk),
-                     errp);
+                     flags, errp);
     if (job == NULL) {
         blk_unref(blk);
         return NULL;
@@ -801,8 +790,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->blk           = blk;
     job->cb            = cb;
     job->opaque        = opaque;
-    job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
-    job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
 
     error_setg(&job->blocker, "block device is in use by block job: %s",
                job_type_str(&job->job));
diff --git a/job.c b/job.c
index 60ccb0640b..bcd335280a 100644
--- a/job.c
+++ b/job.c
@@ -182,11 +182,15 @@ static void job_sleep_timer_cb(void *opaque)
 }
 
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 Error **errp)
+                 int flags, Error **errp)
 {
     Job *job;
 
     if (job_id) {
+        if (flags & JOB_INTERNAL) {
+            error_setg(errp, "Cannot specify job ID for internal job");
+            return NULL;
+        }
         if (!id_wellformed(job_id)) {
             error_setg(errp, "Invalid job ID '%s'", job_id);
             return NULL;
@@ -195,6 +199,9 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
             error_setg(errp, "Job ID '%s' already in use", job_id);
             return NULL;
         }
+    } else if (!(flags & JOB_INTERNAL)) {
+        error_setg(errp, "An explicit job ID is required");
+        return NULL;
     }
 
     job = g_malloc0(driver->instance_size);
@@ -205,6 +212,8 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
     job->busy          = false;
     job->paused        = true;
     job->pause_count   = 1;
+    job->auto_finalize = !(flags & JOB_MANUAL_FINALIZE);
+    job->auto_dismiss  = !(flags & JOB_MANUAL_DISMISS);
 
     job_state_transition(job, JOB_STATUS_CREATED);
     aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
diff --git a/qemu-img.c b/qemu-img.c
index 6a2431a653..94115c0330 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -1015,7 +1015,7 @@ static int img_commit(int argc, char **argv)
 
     aio_context = bdrv_get_aio_context(bs);
     aio_context_acquire(aio_context);
-    commit_active_start("commit", bs, base_bs, BLOCK_JOB_DEFAULT, 0,
+    commit_active_start("commit", bs, base_bs, JOB_DEFAULT, 0,
                         BLOCKDEV_ON_ERROR_REPORT, NULL, common_block_job_cb,
                         &cbi, false, &local_err);
     aio_context_release(aio_context);
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 93d1ff0859..60e9fa2298 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -107,7 +107,7 @@ static BlockJob *test_block_job_start(unsigned int iterations,
 
     snprintf(job_id, sizeof(job_id), "job%u", counter++);
     s = block_job_create(job_id, &test_block_job_driver, txn, bs,
-                         0, BLK_PERM_ALL, 0, BLOCK_JOB_DEFAULT,
+                         0, BLK_PERM_ALL, 0, JOB_DEFAULT,
                          test_block_job_cb, data, &error_abort);
     s->iterations = iterations;
     s->use_timer = use_timer;
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index ceb59600ed..8bb0aa8f85 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -59,7 +59,7 @@ static BlockJob *do_test_id(BlockBackend *blk, const char *id,
                             bool should_succeed)
 {
     return mk_job(blk, id, &test_block_job_driver,
-                  should_succeed, BLOCK_JOB_DEFAULT);
+                  should_succeed, JOB_DEFAULT);
 }
 
 /* This creates a BlockBackend (optionally with a name) with a
@@ -214,7 +214,7 @@ static CancelJob *create_common(BlockJob **pjob)
 
     blk = create_blk(NULL);
     job = mk_job(blk, "Steve", &test_cancel_driver, true,
-                 BLOCK_JOB_MANUAL_FINALIZE | BLOCK_JOB_MANUAL_DISMISS);
+                 JOB_MANUAL_FINALIZE | JOB_MANUAL_DISMISS);
     job_ref(&job->job);
     assert(job->job.status == JOB_STATUS_CREATED);
     s = container_of(job, CancelJob, common);
-- 
2.13.6

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

* [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (21 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 17:56   ` Max Reitz
  2018-05-16 19:15   ` Eric Blake
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 24/42] job: Add job_event_*() Kevin Wolf
                   ` (19 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

block_job_event_pending() doesn't only send a QMP event, but it also
transitions to the PENDING state. Split the function so that we get one
part only sending the event (like other block_job_event_* functions) and
another part than does the state transition.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 blockjob.c | 27 ++++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/blockjob.c b/blockjob.c
index 4cfedbe791..de9a789e29 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -38,7 +38,7 @@
 
 static void block_job_event_cancelled(BlockJob *job);
 static void block_job_event_completed(BlockJob *job, const char *msg);
-static int block_job_event_pending(BlockJob *job);
+static void block_job_event_pending(BlockJob *job);
 
 /* Transactional group of block jobs */
 struct BlockJobTxn {
@@ -501,6 +501,15 @@ static void block_job_do_finalize(BlockJob *job)
     }
 }
 
+static int block_job_transition_to_pending(BlockJob *job)
+{
+    job_state_transition(&job->job, JOB_STATUS_PENDING);
+    if (!job->job.auto_finalize) {
+        block_job_event_pending(job);
+    }
+    return 0;
+}
+
 static void block_job_completed_txn_success(BlockJob *job)
 {
     BlockJobTxn *txn = job->txn;
@@ -519,7 +528,7 @@ static void block_job_completed_txn_success(BlockJob *job)
         assert(other_job->ret == 0);
     }
 
-    block_job_txn_apply(txn, block_job_event_pending, false);
+    block_job_txn_apply(txn, block_job_transition_to_pending, false);
 
     /* If no jobs need manual finalization, automatically do so */
     if (block_job_txn_apply(txn, block_job_needs_finalize, false) == 0) {
@@ -734,15 +743,15 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
                                         &error_abort);
 }
 
-static int block_job_event_pending(BlockJob *job)
+static void block_job_event_pending(BlockJob *job)
 {
-    job_state_transition(&job->job, JOB_STATUS_PENDING);
-    if (!job->job.auto_finalize && !block_job_is_internal(job)) {
-        qapi_event_send_block_job_pending(job_type(&job->job),
-                                          job->job.id,
-                                          &error_abort);
+    if (block_job_is_internal(job)) {
+        return;
     }
-    return 0;
+
+    qapi_event_send_block_job_pending(job_type(&job->job),
+                                      job->job.id,
+                                      &error_abort);
 }
 
 /*
-- 
2.13.6

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

* [Qemu-devel] [PATCH 24/42] job: Add job_event_*()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (22 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 18:09   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 25/42] job: Move single job finalisation to Job Kevin Wolf
                   ` (18 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Go through the Job layer in order to send QMP events. For the moment,
these functions only call a notifier in the BlockJob layer that sends
the existing commands.

This uses notifiers rather than JobDriver callbacks because internal
users of jobs won't receive QMP events, but might still be interested
in getting notified for the events.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h |  9 +++++++++
 include/qemu/job.h       | 18 ++++++++++++++++++
 blockjob.c               | 42 ++++++++++++++++++++++++++++--------------
 job.c                    | 19 +++++++++++++++++++
 4 files changed, 74 insertions(+), 14 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index a74d5d02fd..b5bcb52219 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -82,6 +82,15 @@ typedef struct BlockJob {
     /** Block other operations when block job is running */
     Error *blocker;
 
+    /** Called when a cancelled job is finalised. */
+    Notifier finalize_cancelled_notifier;
+
+    /** Called when a successfully completed job is finalised. */
+    Notifier finalize_completed_notifier;
+
+    /** Called when the job transitions to PENDING */
+    Notifier pending_notifier;
+
     /** BlockDriverStates that are involved in this block job */
     GSList *nodes;
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 9ad93ee862..1b4397f9a1 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -105,6 +105,15 @@ typedef struct Job {
     /** True if this job should automatically dismiss itself */
     bool auto_dismiss;
 
+    /** Notifiers called when a cancelled job is finalised */
+    NotifierList on_finalize_cancelled;
+
+    /** Notifiers called when a successfully completed job is finalised */
+    NotifierList on_finalize_completed;
+
+    /** Notifiers called when the job transitions to PENDING */
+    NotifierList on_pending;
+
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 } Job;
@@ -182,6 +191,15 @@ void job_ref(Job *job);
  */
 void job_unref(Job *job);
 
+/** To be called when a cancelled job is finalised. */
+void job_event_cancelled(Job *job);
+
+/** To be called when a successfully completed job is finalised. */
+void job_event_completed(Job *job);
+
+/** To be called when the job transitions to PENDING */
+void job_event_pending(Job *job);
+
 /**
  * Conditionally enter the job coroutine if the job is ready to run, not
  * already busy and fn() returns true. fn() is called while under the job_lock
diff --git a/blockjob.c b/blockjob.c
index de9a789e29..35376a60f0 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -36,10 +36,6 @@
 #include "qemu/coroutine.h"
 #include "qemu/timer.h"
 
-static void block_job_event_cancelled(BlockJob *job);
-static void block_job_event_completed(BlockJob *job, const char *msg);
-static void block_job_event_pending(BlockJob *job);
-
 /* Transactional group of block jobs */
 struct BlockJobTxn {
 
@@ -353,13 +349,9 @@ static int block_job_finalize_single(BlockJob *job)
     /* Emit events only if we actually started */
     if (job_started(&job->job)) {
         if (job_is_cancelled(&job->job)) {
-            block_job_event_cancelled(job);
+            job_event_cancelled(&job->job);
         } else {
-            const char *msg = NULL;
-            if (job->ret < 0) {
-                msg = strerror(-job->ret);
-            }
-            block_job_event_completed(job, msg);
+            job_event_completed(&job->job);
         }
     }
 
@@ -505,7 +497,7 @@ static int block_job_transition_to_pending(BlockJob *job)
 {
     job_state_transition(&job->job, JOB_STATUS_PENDING);
     if (!job->job.auto_finalize) {
-        block_job_event_pending(job);
+        job_event_pending(&job->job);
     }
     return 0;
 }
@@ -713,8 +705,10 @@ static void block_job_iostatus_set_err(BlockJob *job, int error)
     }
 }
 
-static void block_job_event_cancelled(BlockJob *job)
+static void block_job_event_cancelled(Notifier *n, void *opaque)
 {
+    BlockJob *job = opaque;
+
     if (block_job_is_internal(job)) {
         return;
     }
@@ -727,12 +721,19 @@ static void block_job_event_cancelled(BlockJob *job)
                                         &error_abort);
 }
 
-static void block_job_event_completed(BlockJob *job, const char *msg)
+static void block_job_event_completed(Notifier *n, void *opaque)
 {
+    BlockJob *job = opaque;
+    const char *msg = NULL;
+
     if (block_job_is_internal(job)) {
         return;
     }
 
+    if (job->ret < 0) {
+        msg = strerror(-job->ret);
+    }
+
     qapi_event_send_block_job_completed(job_type(&job->job),
                                         job->job.id,
                                         job->len,
@@ -743,8 +744,10 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
                                         &error_abort);
 }
 
-static void block_job_event_pending(BlockJob *job)
+static void block_job_event_pending(Notifier *n, void *opaque)
 {
+    BlockJob *job = opaque;
+
     if (block_job_is_internal(job)) {
         return;
     }
@@ -800,6 +803,17 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->cb            = cb;
     job->opaque        = opaque;
 
+    job->finalize_cancelled_notifier.notify = block_job_event_cancelled;
+    job->finalize_completed_notifier.notify = block_job_event_completed;
+    job->pending_notifier.notify = block_job_event_pending;
+
+    notifier_list_add(&job->job.on_finalize_cancelled,
+                      &job->finalize_cancelled_notifier);
+    notifier_list_add(&job->job.on_finalize_completed,
+                      &job->finalize_completed_notifier);
+    notifier_list_add(&job->job.on_pending,
+                      &job->pending_notifier);
+
     error_setg(&job->blocker, "block device is in use by block job: %s",
                job_type_str(&job->job));
     block_job_add_bdrv(job, "main node", bs, 0, BLK_PERM_ALL, &error_abort);
diff --git a/job.c b/job.c
index bcd335280a..8a27d4f760 100644
--- a/job.c
+++ b/job.c
@@ -215,6 +215,10 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
     job->auto_finalize = !(flags & JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & JOB_MANUAL_DISMISS);
 
+    notifier_list_init(&job->on_finalize_cancelled);
+    notifier_list_init(&job->on_finalize_completed);
+    notifier_list_init(&job->on_pending);
+
     job_state_transition(job, JOB_STATUS_CREATED);
     aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
                    QEMU_CLOCK_REALTIME, SCALE_NS,
@@ -247,6 +251,21 @@ void job_unref(Job *job)
     }
 }
 
+void job_event_cancelled(Job *job)
+{
+    notifier_list_notify(&job->on_finalize_cancelled, job);
+}
+
+void job_event_completed(Job *job)
+{
+    notifier_list_notify(&job->on_finalize_completed, job);
+}
+
+void job_event_pending(Job *job)
+{
+    notifier_list_notify(&job->on_pending, job);
+}
+
 void job_enter_cond(Job *job, bool(*fn)(Job *job))
 {
     if (!job_started(job)) {
-- 
2.13.6

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

* [Qemu-devel] [PATCH 25/42] job: Move single job finalisation to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (23 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 24/42] job: Add job_event_*() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 18:46   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 26/42] job: Convert block_job_cancel_async() " Kevin Wolf
                   ` (17 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves the finalisation of a single job from BlockJob to Job.

Some part of this code depends on job transactions, and job transactions
call this code, we introduce some temporary calls from Job functions to
BlockJob ones. This will be fixed once transactions move to Job, too.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |   9 ---
 include/block/blockjob_int.h |  36 -----------
 include/qemu/job.h           |  53 +++++++++++++++-
 block/backup.c               |  22 +++----
 block/commit.c               |   2 +-
 block/mirror.c               |   2 +-
 blockjob.c                   | 142 ++++++++-----------------------------------
 job.c                        | 100 +++++++++++++++++++++++++++++-
 qemu-img.c                   |   2 +-
 tests/test-blockjob.c        |  10 +--
 10 files changed, 194 insertions(+), 184 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index b5bcb52219..e5af02f751 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -76,9 +76,6 @@ typedef struct BlockJob {
     /** Rate limiting data structure for implementing @speed. */
     RateLimit limit;
 
-    /** The completion function that will be called when the job completes.  */
-    BlockCompletionFunc *cb;
-
     /** Block other operations when block job is running */
     Error *blocker;
 
@@ -94,12 +91,6 @@ typedef struct BlockJob {
     /** BlockDriverStates that are involved in this block job */
     GSList *nodes;
 
-    /** The opaque value that is passed to the completion function.  */
-    void *opaque;
-
-    /** ret code passed to block_job_completed. */
-    int ret;
-
     BlockJobTxn *txn;
     QLIST_ENTRY(BlockJob) txn_list;
 } BlockJob;
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 88639f7efc..bf2b762808 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -54,34 +54,6 @@ struct BlockJobDriver {
      */
     int (*prepare)(BlockJob *job);
 
-    /**
-     * 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);
-
-    /**
-     * If the callback is not NULL, it will be invoked after a call to either
-     * .commit() or .abort(). Regardless of which callback is invoked after
-     * completion, .clean() will always be called, even if the job does not
-     * belong to a transaction group.
-     */
-    void (*clean)(BlockJob *job);
-
     /*
      * If the callback is not NULL, it will be invoked before the job is
      * resumed in a new AioContext.  This is the place to move any resources
@@ -156,14 +128,6 @@ void block_job_yield(BlockJob *job);
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
 
 /**
- * block_job_early_fail:
- * @bs: The block device.
- *
- * The block job could not be started, free it.
- */
-void block_job_early_fail(BlockJob *job);
-
-/**
  * block_job_completed:
  * @job: The job being completed.
  * @ret: The status code.
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 1b4397f9a1..12edb822d8 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -29,6 +29,7 @@
 #include "qapi/qapi-types-block-core.h"
 #include "qemu/queue.h"
 #include "qemu/coroutine.h"
+#include "block/aio.h"
 
 typedef struct JobDriver JobDriver;
 
@@ -105,6 +106,15 @@ typedef struct Job {
     /** True if this job should automatically dismiss itself */
     bool auto_dismiss;
 
+    /** ret code passed to block_job_completed. */
+    int ret;
+
+    /** The completion function that will be called when the job completes.  */
+    BlockCompletionFunc *cb;
+
+    /** The opaque value that is passed to the completion function.  */
+    void *opaque;
+
     /** Notifiers called when a cancelled job is finalised */
     NotifierList on_finalize_cancelled;
 
@@ -151,6 +161,35 @@ struct JobDriver {
      */
     void (*user_resume)(Job *job);
 
+    /**
+     * 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)(Job *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)(Job *job);
+
+    /**
+     * If the callback is not NULL, it will be invoked after a call to either
+     * .commit() or .abort(). Regardless of which callback is invoked after
+     * completion, .clean() will always be called, even if the job does not
+     * belong to a transaction group.
+     */
+    void (*clean)(Job *job);
+
+
     /** Called when the job is freed */
     void (*free)(Job *job);
 };
@@ -174,10 +213,12 @@ typedef enum JobCreateFlags {
  * @driver: The class object for the newly-created job.
  * @ctx: The AioContext to run the job coroutine in.
  * @flags: Creation flags for the job. See @JobCreateFlags.
+ * @cb: Completion function for the job.
+ * @opaque: Opaque pointer value passed to @cb.
  * @errp: Error object.
  */
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 int flags, Error **errp);
+                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp);
 
 /**
  * Add a reference to Job refcnt, it will be decreased with job_unref, and then
@@ -300,6 +341,10 @@ Job *job_get(const char *id);
  */
 int job_apply_verb(Job *job, JobVerb bv, Error **errp);
 
+/** The @job could not be started, free it. */
+void job_early_fail(Job *job);
+
+
 typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
 
 /**
@@ -322,5 +367,11 @@ void job_state_transition(Job *job, JobStatus s1);
 void coroutine_fn job_do_yield(Job *job, uint64_t ns);
 bool job_should_pause(Job *job);
 bool job_started(Job *job);
+void job_do_dismiss(Job *job);
+int job_finalize_single(Job *job);
+void job_update_rc(Job *job);
+
+typedef struct BlockJob BlockJob;
+void block_job_txn_del_job(BlockJob *job);
 
 #endif
diff --git a/block/backup.c b/block/backup.c
index 4d011d5a5c..bd31282924 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -207,25 +207,25 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
     }
 }
 
-static void backup_commit(BlockJob *job)
+static void backup_commit(Job *job)
 {
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
     if (s->sync_bitmap) {
         backup_cleanup_sync_bitmap(s, 0);
     }
 }
 
-static void backup_abort(BlockJob *job)
+static void backup_abort(Job *job)
 {
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
     if (s->sync_bitmap) {
         backup_cleanup_sync_bitmap(s, -1);
     }
 }
 
-static void backup_clean(BlockJob *job)
+static void backup_clean(Job *job)
 {
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
     assert(s->target);
     blk_unref(s->target);
     s->target = NULL;
@@ -530,10 +530,10 @@ static const BlockJobDriver backup_job_driver = {
         .free                   = block_job_free,
         .user_resume            = block_job_user_resume,
         .start                  = backup_run,
+        .commit                 = backup_commit,
+        .abort                  = backup_abort,
+        .clean                  = backup_clean,
     },
-    .commit                 = backup_commit,
-    .abort                  = backup_abort,
-    .clean                  = backup_clean,
     .attached_aio_context   = backup_attached_aio_context,
     .drain                  = backup_drain,
 };
@@ -678,8 +678,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
         bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
     }
     if (job) {
-        backup_clean(&job->common);
-        block_job_early_fail(&job->common);
+        backup_clean(&job->common.job);
+        job_early_fail(&job->common.job);
     }
 
     return NULL;
diff --git a/block/commit.c b/block/commit.c
index 7a6ae59d42..e53b2d7d55 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -385,7 +385,7 @@ fail:
     if (commit_top_bs) {
         bdrv_replace_node(commit_top_bs, top, &error_abort);
     }
-    block_job_early_fail(&s->common);
+    job_early_fail(&s->common.job);
 }
 
 
diff --git a/block/mirror.c b/block/mirror.c
index df636298f1..dcf0856c97 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1255,7 +1255,7 @@ fail:
 
         g_free(s->replaces);
         blk_unref(s->target);
-        block_job_early_fail(&s->common);
+        job_early_fail(&s->common.job);
     }
 
     bdrv_child_try_set_perm(mirror_top_bs->backing, 0, BLK_PERM_ALL,
diff --git a/blockjob.c b/blockjob.c
index 35376a60f0..9280e92062 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -128,7 +128,7 @@ void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job)
     block_job_txn_ref(txn);
 }
 
-static void block_job_txn_del_job(BlockJob *job)
+void block_job_txn_del_job(BlockJob *job)
 {
     if (job->txn) {
         QLIST_REMOVE(job, txn_list);
@@ -263,101 +263,12 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
     return job->driver;
 }
 
-static void block_job_decommission(BlockJob *job)
-{
-    assert(job);
-    job->job.busy = false;
-    job->job.paused = false;
-    job->job.deferred_to_main_loop = true;
-    block_job_txn_del_job(job);
-    job_state_transition(&job->job, JOB_STATUS_NULL);
-    job_unref(&job->job);
-}
-
-static void block_job_do_dismiss(BlockJob *job)
-{
-    block_job_decommission(job);
-}
-
-static void block_job_conclude(BlockJob *job)
-{
-    job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
-    if (job->job.auto_dismiss || !job_started(&job->job)) {
-        block_job_do_dismiss(job);
-    }
-}
-
-static void block_job_update_rc(BlockJob *job)
-{
-    if (!job->ret && job_is_cancelled(&job->job)) {
-        job->ret = -ECANCELED;
-    }
-    if (job->ret) {
-        job_state_transition(&job->job, JOB_STATUS_ABORTING);
-    }
-}
-
 static int block_job_prepare(BlockJob *job)
 {
-    if (job->ret == 0 && job->driver->prepare) {
-        job->ret = job->driver->prepare(job);
-    }
-    return job->ret;
-}
-
-static void block_job_commit(BlockJob *job)
-{
-    assert(!job->ret);
-    if (job->driver->commit) {
-        job->driver->commit(job);
-    }
-}
-
-static void block_job_abort(BlockJob *job)
-{
-    assert(job->ret);
-    if (job->driver->abort) {
-        job->driver->abort(job);
-    }
-}
-
-static void block_job_clean(BlockJob *job)
-{
-    if (job->driver->clean) {
-        job->driver->clean(job);
+    if (job->job.ret == 0 && job->driver->prepare) {
+        job->job.ret = job->driver->prepare(job);
     }
-}
-
-static int block_job_finalize_single(BlockJob *job)
-{
-    assert(job_is_completed(&job->job));
-
-    /* Ensure abort is called for late-transactional failures */
-    block_job_update_rc(job);
-
-    if (!job->ret) {
-        block_job_commit(job);
-    } else {
-        block_job_abort(job);
-    }
-    block_job_clean(job);
-
-    if (job->cb) {
-        job->cb(job->opaque, job->ret);
-    }
-
-    /* Emit events only if we actually started */
-    if (job_started(&job->job)) {
-        if (job_is_cancelled(&job->job)) {
-            job_event_cancelled(&job->job);
-        } else {
-            job_event_completed(&job->job);
-        }
-    }
-
-    block_job_txn_del_job(job);
-    block_job_conclude(job);
-    return 0;
+    return job->job.ret;
 }
 
 static void block_job_cancel_async(BlockJob *job, bool force)
@@ -425,8 +336,8 @@ static int block_job_finish_sync(BlockJob *job,
     while (!job_is_completed(&job->job)) {
         aio_poll(qemu_get_aio_context(), true);
     }
-    ret = (job_is_cancelled(&job->job) && job->ret == 0)
-          ? -ECANCELED : job->ret;
+    ret = (job_is_cancelled(&job->job) && job->job.ret == 0)
+          ? -ECANCELED : job->job.ret;
     job_unref(&job->job);
     return ret;
 }
@@ -467,7 +378,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
             assert(job_is_cancelled(&other_job->job));
             block_job_finish_sync(other_job, NULL, NULL);
         }
-        block_job_finalize_single(other_job);
+        job_finalize_single(&other_job->job);
         aio_context_release(ctx);
     }
 
@@ -479,6 +390,11 @@ static int block_job_needs_finalize(BlockJob *job)
     return !job->job.auto_finalize;
 }
 
+static int block_job_finalize_single(BlockJob *job)
+{
+    return job_finalize_single(&job->job);
+}
+
 static void block_job_do_finalize(BlockJob *job)
 {
     int rc;
@@ -517,7 +433,7 @@ static void block_job_completed_txn_success(BlockJob *job)
         if (!job_is_completed(&other_job->job)) {
             return;
         }
-        assert(other_job->ret == 0);
+        assert(other_job->job.ret == 0);
     }
 
     block_job_txn_apply(txn, block_job_transition_to_pending, false);
@@ -602,14 +518,14 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
         return;
     }
 
-    block_job_do_dismiss(job);
+    job_do_dismiss(&job->job);
     *jobptr = NULL;
 }
 
 void block_job_cancel(BlockJob *job, bool force)
 {
     if (job->job.status == JOB_STATUS_CONCLUDED) {
-        block_job_do_dismiss(job);
+        job_do_dismiss(&job->job);
         return;
     }
     block_job_cancel_async(job, force);
@@ -692,8 +608,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info->status    = job->job.status;
     info->auto_finalize = job->job.auto_finalize;
     info->auto_dismiss  = job->job.auto_dismiss;
-    info->has_error = job->ret != 0;
-    info->error     = job->ret ? g_strdup(strerror(-job->ret)) : NULL;
+    info->has_error = job->job.ret != 0;
+    info->error     = job->job.ret ? g_strdup(strerror(-job->job.ret)) : NULL;
     return info;
 }
 
@@ -730,8 +646,8 @@ static void block_job_event_completed(Notifier *n, void *opaque)
         return;
     }
 
-    if (job->ret < 0) {
-        msg = strerror(-job->ret);
+    if (job->job.ret < 0) {
+        msg = strerror(-job->job.ret);
     }
 
     qapi_event_send_block_job_completed(job_type(&job->job),
@@ -788,7 +704,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     }
 
     job = job_create(job_id, &driver->job_driver, blk_get_aio_context(blk),
-                     flags, errp);
+                     flags, cb, opaque, errp);
     if (job == NULL) {
         blk_unref(blk);
         return NULL;
@@ -800,8 +716,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
     job->driver        = driver;
     job->blk           = blk;
-    job->cb            = cb;
-    job->opaque        = opaque;
 
     job->finalize_cancelled_notifier.notify = block_job_event_cancelled;
     job->finalize_completed_notifier.notify = block_job_event_completed;
@@ -830,7 +744,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
         block_job_set_speed(job, speed, &local_err);
         if (local_err) {
-            block_job_early_fail(job);
+            job_early_fail(&job->job);
             error_propagate(errp, local_err);
             return NULL;
         }
@@ -849,20 +763,14 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     return job;
 }
 
-void block_job_early_fail(BlockJob *job)
-{
-    assert(job->job.status == JOB_STATUS_CREATED);
-    block_job_decommission(job);
-}
-
 void block_job_completed(BlockJob *job, int ret)
 {
     assert(job && job->txn && !job_is_completed(&job->job));
     assert(blk_bs(job->blk)->job == job);
-    job->ret = ret;
-    block_job_update_rc(job);
-    trace_block_job_completed(job, ret, job->ret);
-    if (job->ret) {
+    job->job.ret = ret;
+    job_update_rc(&job->job);
+    trace_block_job_completed(job, ret, job->job.ret);
+    if (job->job.ret) {
         block_job_completed_txn_abort(job);
     } else {
         block_job_completed_txn_success(job);
diff --git a/job.c b/job.c
index 8a27d4f760..4591e09e92 100644
--- a/job.c
+++ b/job.c
@@ -85,7 +85,7 @@ void job_state_transition(Job *job, JobStatus s1)
 {
     JobStatus s0 = job->status;
     assert(s1 >= 0 && s1 <= JOB_STATUS__MAX);
-    trace_job_state_transition(job, /* TODO re-enable: job->ret */ 0,
+    trace_job_state_transition(job, job->ret,
                                JobSTT[s0][s1] ? "allowed" : "disallowed",
                                JobStatus_str(s0), JobStatus_str(s1));
     assert(JobSTT[s0][s1]);
@@ -182,7 +182,7 @@ static void job_sleep_timer_cb(void *opaque)
 }
 
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 int flags, Error **errp)
+                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp)
 {
     Job *job;
 
@@ -214,6 +214,8 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
     job->pause_count   = 1;
     job->auto_finalize = !(flags & JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & JOB_MANUAL_DISMISS);
+    job->cb            = cb;
+    job->opaque        = opaque;
 
     notifier_list_init(&job->on_finalize_cancelled);
     notifier_list_init(&job->on_finalize_completed);
@@ -441,6 +443,100 @@ void job_user_resume(Job *job, Error **errp)
     job_resume(job);
 }
 
+void job_do_dismiss(Job *job)
+{
+    assert(job);
+    job->busy = false;
+    job->paused = false;
+    job->deferred_to_main_loop = true;
+
+    /* TODO Don't assume it's a BlockJob */
+    block_job_txn_del_job((BlockJob*) job);
+
+    job_state_transition(job, JOB_STATUS_NULL);
+    job_unref(job);
+}
+
+void job_early_fail(Job *job)
+{
+    assert(job->status == JOB_STATUS_CREATED);
+    job_do_dismiss(job);
+}
+
+static void job_conclude(Job *job)
+{
+    job_state_transition(job, JOB_STATUS_CONCLUDED);
+    if (job->auto_dismiss || !job_started(job)) {
+        job_do_dismiss(job);
+    }
+}
+
+void job_update_rc(Job *job)
+{
+    if (!job->ret && job_is_cancelled(job)) {
+        job->ret = -ECANCELED;
+    }
+    if (job->ret) {
+        job_state_transition(job, JOB_STATUS_ABORTING);
+    }
+}
+
+static void job_commit(Job *job)
+{
+    assert(!job->ret);
+    if (job->driver->commit) {
+        job->driver->commit(job);
+    }
+}
+
+static void job_abort(Job *job)
+{
+    assert(job->ret);
+    if (job->driver->abort) {
+        job->driver->abort(job);
+    }
+}
+
+static void job_clean(Job *job)
+{
+    if (job->driver->clean) {
+        job->driver->clean(job);
+    }
+}
+
+int job_finalize_single(Job *job)
+{
+    assert(job_is_completed(job));
+
+    /* Ensure abort is called for late-transactional failures */
+    job_update_rc(job);
+
+    if (!job->ret) {
+        job_commit(job);
+    } else {
+        job_abort(job);
+    }
+    job_clean(job);
+
+    if (job->cb) {
+        job->cb(job->opaque, job->ret);
+    }
+
+    /* Emit events only if we actually started */
+    if (job_started(job)) {
+        if (job_is_cancelled(job)) {
+            job_event_cancelled(job);
+        } else {
+            job_event_completed(job);
+        }
+    }
+
+    /* TODO Don't assume it's a BlockJob */
+    block_job_txn_del_job((BlockJob*) job);
+    job_conclude(job);
+    return 0;
+}
+
 
 typedef struct {
     Job *job;
diff --git a/qemu-img.c b/qemu-img.c
index 94115c0330..ee214269aa 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -872,7 +872,7 @@ static void run_block_job(BlockJob *job, Error **errp)
     if (!job_is_completed(&job->job)) {
         ret = block_job_complete_sync(job, errp);
     } else {
-        ret = job->ret;
+        ret = job->job.ret;
     }
     job_unref(&job->job);
     aio_context_release(aio_context);
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 8bb0aa8f85..1fe6803fe0 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -128,11 +128,11 @@ static void test_job_ids(void)
     job[1] = do_test_id(blk[1], "id0", false);
 
     /* But once job[0] finishes we can reuse its ID */
-    block_job_early_fail(job[0]);
+    job_early_fail(&job[0]->job);
     job[1] = do_test_id(blk[1], "id0", true);
 
     /* No job ID specified, defaults to the backend name ('drive1') */
-    block_job_early_fail(job[1]);
+    job_early_fail(&job[1]->job);
     job[1] = do_test_id(blk[1], NULL, true);
 
     /* Duplicate job ID */
@@ -145,9 +145,9 @@ static void test_job_ids(void)
     /* This one is valid */
     job[2] = do_test_id(blk[2], "id_2", true);
 
-    block_job_early_fail(job[0]);
-    block_job_early_fail(job[1]);
-    block_job_early_fail(job[2]);
+    job_early_fail(&job[0]->job);
+    job_early_fail(&job[1]->job);
+    job_early_fail(&job[2]->job);
 
     destroy_blk(blk[0]);
     destroy_blk(blk[1]);
-- 
2.13.6

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

* [Qemu-devel] [PATCH 26/42] job: Convert block_job_cancel_async() to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (24 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 25/42] job: Move single job finalisation to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 19:00   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 27/42] job: Add job_drain() Kevin Wolf
                   ` (16 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

block_job_cancel_async() did two things that were still block job
specific:

* Setting job->force. This field makes sense on the Job level, so we can
  just move it. While at it, rename it to job->force_cancel to make its
  purpose more obvious.

* Resetting the I/O status. This can't be moved because generic Jobs
  don't have an I/O status. What the function really implements is a
  user resume, except without entering the coroutine. Consequently, it
  makes sense to call the .user_resume driver callback here which
  already resets the I/O status.

  The old block_job_cancel_async() has two separate if statements that
  check job->iostatus != BLOCK_DEVICE_IO_STATUS_OK and job->user_paused.
  However, the former condition always implies the latter (as is
  asserted in block_job_iostatus_reset()), so changing the explicit call
  of block_job_iostatus_reset() on the former condition with the
  .user_resume callback on the latter condition is equivalent and
  doesn't need to access any BlockJob specific state.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h |  6 ------
 include/qemu/job.h       |  6 ++++++
 block/mirror.c           |  4 ++--
 blockjob.c               | 25 +++++++++++++------------
 4 files changed, 21 insertions(+), 20 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index e5af02f751..8477fc5a78 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -51,12 +51,6 @@ typedef struct BlockJob {
     BlockBackend *blk;
 
     /**
-     * Set to true if the job should abort immediately without waiting
-     * for data to be in sync.
-     */
-    bool force;
-
-    /**
      * Set to true when the job is ready to be completed.
      */
     bool ready;
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 12edb822d8..b96ec10413 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -97,6 +97,12 @@ typedef struct Job {
      */
     bool cancelled;
 
+    /**
+     * Set to true if the job should abort immediately without waiting
+     * for data to be in sync.
+     */
+    bool force_cancel;
+
     /** Set to true when the job has deferred work to the main loop. */
     bool deferred_to_main_loop;
 
diff --git a/block/mirror.c b/block/mirror.c
index dcf0856c97..f18240e34c 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -871,7 +871,7 @@ static void coroutine_fn mirror_run(void *opaque)
         trace_mirror_before_sleep(s, cnt, s->synced, delay_ns);
         job_sleep_ns(&s->common.job, delay_ns);
         if (job_is_cancelled(&s->common.job) &&
-            (!s->synced || s->common.force))
+            (!s->synced || s->common.job.force_cancel))
         {
             break;
         }
@@ -884,7 +884,7 @@ immediate_exit:
          * or it was cancelled prematurely so that we do not guarantee that
          * the target is a copy of the source.
          */
-        assert(ret < 0 || ((s->common.force || !s->synced) &&
+        assert(ret < 0 || ((s->common.job.force_cancel || !s->synced) &&
                job_is_cancelled(&s->common.job)));
         assert(need_drain);
         mirror_wait_for_all_io(s);
diff --git a/blockjob.c b/blockjob.c
index 9280e92062..ca88e166e1 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -271,19 +271,20 @@ static int block_job_prepare(BlockJob *job)
     return job->job.ret;
 }
 
-static void block_job_cancel_async(BlockJob *job, bool force)
+static void job_cancel_async(Job *job, bool force)
 {
-    if (job->iostatus != BLOCK_DEVICE_IO_STATUS_OK) {
-        block_job_iostatus_reset(job);
-    }
-    if (job->job.user_paused) {
-        /* Do not call block_job_enter here, the caller will handle it.  */
-        job->job.user_paused = false;
-        job->job.pause_count--;
+    if (job->user_paused) {
+        /* Do not call job_enter here, the caller will handle it.  */
+        job->user_paused = false;
+        if (job->driver->user_resume) {
+            job->driver->user_resume(job);
+        }
+        assert(job->pause_count > 0);
+        job->pause_count--;
     }
-    job->job.cancelled = true;
+    job->cancelled = true;
     /* To prevent 'force == false' overriding a previous 'force == true' */
-    job->force |= force;
+    job->force_cancel |= force;
 }
 
 static int block_job_txn_apply(BlockJobTxn *txn, int fn(BlockJob *), bool lock)
@@ -368,7 +369,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
      * on the caller, so leave it. */
     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
         if (other_job != job) {
-            block_job_cancel_async(other_job, false);
+            job_cancel_async(&other_job->job, false);
         }
     }
     while (!QLIST_EMPTY(&txn->jobs)) {
@@ -528,7 +529,7 @@ void block_job_cancel(BlockJob *job, bool force)
         job_do_dismiss(&job->job);
         return;
     }
-    block_job_cancel_async(job, force);
+    job_cancel_async(&job->job, force);
     if (!job_started(&job->job)) {
         block_job_completed(job, -ECANCELED);
     } else if (job->job.deferred_to_main_loop) {
-- 
2.13.6

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

* [Qemu-devel] [PATCH 27/42] job: Add job_drain()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (25 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 26/42] job: Convert block_job_cancel_async() " Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 19:26   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 28/42] job: Move .complete callback to Job Kevin Wolf
                   ` (15 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

block_job_drain() contains a blk_drain() call which cannot be moved to
Job, so add a new JobDriver callback JobDriver.drain which has a common
implementation for all BlockJobs. In addition to this we keep the
existing BlockJobDriver.drain callback that is called by the common
drain implementation for all block jobs.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob_int.h | 12 ++++++++++++
 include/qemu/job.h           | 13 +++++++++++++
 block/backup.c               |  1 +
 block/commit.c               |  1 +
 block/mirror.c               |  2 ++
 block/stream.c               |  1 +
 blockjob.c                   | 20 ++++++++++----------
 job.c                        | 11 +++++++++++
 tests/test-bdrv-drain.c      |  1 +
 tests/test-blockjob-txn.c    |  1 +
 tests/test-blockjob.c        |  2 ++
 11 files changed, 55 insertions(+), 10 deletions(-)

diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index bf2b762808..38fe22d7e0 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -65,6 +65,10 @@ struct BlockJobDriver {
      * If the callback is not NULL, it will be invoked when the job has to be
      * synchronously cancelled or completed; it should drain BlockDriverStates
      * as required to ensure progress.
+     *
+     * Block jobs must use the default implementation for job_driver.drain,
+     * which will in turn call this callback after doing generic block job
+     * stuff.
      */
     void (*drain)(BlockJob *job);
 };
@@ -112,6 +116,14 @@ void block_job_free(Job *job);
 void block_job_user_resume(Job *job);
 
 /**
+ * block_job_drain:
+ * Callback to be used for JobDriver.drain in all block jobs. Drains the main
+ * block node associated with the block jobs and calls BlockJobDriver.drain for
+ * job-specific actions.
+ */
+void block_job_drain(Job *job);
+
+/**
  * block_job_yield:
  * @job: The job that calls the function.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index b96ec10413..49e254f01a 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -167,6 +167,13 @@ struct JobDriver {
      */
     void (*user_resume)(Job *job);
 
+    /*
+     * If the callback is not NULL, it will be invoked when the job has to be
+     * synchronously cancelled or completed; it should drain any activities
+     * as required to ensure progress.
+     */
+    void (*drain)(Job *job);
+
     /**
      * 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
@@ -325,6 +332,12 @@ bool job_user_paused(Job *job);
  */
 void job_user_resume(Job *job, Error **errp);
 
+/*
+ * Drain any activities as required to ensure progress. This can be called in a
+ * loop to synchronously complete a job.
+ */
+void job_drain(Job *job);
+
 /**
  * Get the next element from the list of block jobs after @job, or the
  * first one if @job is %NULL.
diff --git a/block/backup.c b/block/backup.c
index bd31282924..ca7d990b21 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -529,6 +529,7 @@ static const BlockJobDriver backup_job_driver = {
         .job_type               = JOB_TYPE_BACKUP,
         .free                   = block_job_free,
         .user_resume            = block_job_user_resume,
+        .drain                  = block_job_drain,
         .start                  = backup_run,
         .commit                 = backup_commit,
         .abort                  = backup_abort,
diff --git a/block/commit.c b/block/commit.c
index e53b2d7d55..02a8af9127 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -221,6 +221,7 @@ static const BlockJobDriver commit_job_driver = {
         .job_type      = JOB_TYPE_COMMIT,
         .free          = block_job_free,
         .user_resume   = block_job_user_resume,
+        .drain         = block_job_drain,
         .start         = commit_run,
     },
 };
diff --git a/block/mirror.c b/block/mirror.c
index f18240e34c..4ab7e088ee 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -992,6 +992,7 @@ static const BlockJobDriver mirror_job_driver = {
         .job_type               = JOB_TYPE_MIRROR,
         .free                   = block_job_free,
         .user_resume            = block_job_user_resume,
+        .drain                  = block_job_drain,
         .start                  = mirror_run,
         .pause                  = mirror_pause,
     },
@@ -1006,6 +1007,7 @@ static const BlockJobDriver commit_active_job_driver = {
         .job_type               = JOB_TYPE_COMMIT,
         .free                   = block_job_free,
         .user_resume            = block_job_user_resume,
+        .drain                  = block_job_drain,
         .start                  = mirror_run,
         .pause                  = mirror_pause,
     },
diff --git a/block/stream.c b/block/stream.c
index eee02538ed..b996278ab3 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -215,6 +215,7 @@ static const BlockJobDriver stream_job_driver = {
         .free          = block_job_free,
         .start         = stream_run,
         .user_resume   = block_job_user_resume,
+        .drain         = block_job_drain,
     },
 };
 
diff --git a/blockjob.c b/blockjob.c
index ca88e166e1..76d9cb32b3 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -170,14 +170,13 @@ static void block_job_attached_aio_context(AioContext *new_context,
     job_resume(&job->job);
 }
 
-static void block_job_drain(BlockJob *job)
+void block_job_drain(Job *job)
 {
-    /* If job is !job->job.busy this kicks it into the next pause point. */
-    block_job_enter(job);
+    BlockJob *bjob = container_of(job, BlockJob, job);
 
-    blk_drain(job->blk);
-    if (job->driver->drain) {
-        job->driver->drain(job);
+    blk_drain(bjob->blk);
+    if (bjob->driver->drain) {
+        bjob->driver->drain(bjob);
     }
 }
 
@@ -191,7 +190,7 @@ static void block_job_detach_aio_context(void *opaque)
     job_pause(&job->job);
 
     while (!job->job.paused && !job_is_completed(&job->job)) {
-        block_job_drain(job);
+        job_drain(&job->job);
     }
 
     job->job.aio_context = NULL;
@@ -328,11 +327,11 @@ static int block_job_finish_sync(BlockJob *job,
         job_unref(&job->job);
         return -EBUSY;
     }
-    /* block_job_drain calls block_job_enter, and it should be enough to
-     * induce progress until the job completes or moves to the main thread.
+    /* job_drain calls job_enter, and it should be enough to induce progress
+     * until the job completes or moves to the main thread.
     */
     while (!job->job.deferred_to_main_loop && !job_is_completed(&job->job)) {
-        block_job_drain(job);
+        job_drain(&job->job);
     }
     while (!job_is_completed(&job->job)) {
         aio_poll(qemu_get_aio_context(), true);
@@ -714,6 +713,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     assert(is_block_job(&job->job));
     assert(job->job.driver->free == &block_job_free);
     assert(job->job.driver->user_resume == &block_job_user_resume);
+    assert(job->job.driver->drain == &block_job_drain);
 
     job->driver        = driver;
     job->blk           = blk;
diff --git a/job.c b/job.c
index 4591e09e92..6cc05f4476 100644
--- a/job.c
+++ b/job.c
@@ -367,6 +367,17 @@ void coroutine_fn job_sleep_ns(Job *job, int64_t ns)
     job_pause_point(job);
 }
 
+void job_drain(Job *job)
+{
+    /* If job is !busy this kicks it into the next pause point. */
+    job_enter(job);
+
+    if (job->driver->drain) {
+        job->driver->drain(job);
+    }
+}
+
+
 /**
  * All jobs must allow a pause point before entering their job proper. This
  * ensures that jobs can be paused prior to being started, then resumed later.
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index c993512f66..58ea5663f0 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -525,6 +525,7 @@ BlockJobDriver test_job_driver = {
         .instance_size  = sizeof(TestBlockJob),
         .free           = block_job_free,
         .user_resume    = block_job_user_resume,
+        .drain          = block_job_drain,
         .start          = test_job_start,
     },
     .complete       = test_job_complete,
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 60e9fa2298..1572f8d96f 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -79,6 +79,7 @@ static const BlockJobDriver test_block_job_driver = {
         .instance_size = sizeof(TestBlockJob),
         .free          = block_job_free,
         .user_resume   = block_job_user_resume,
+        .drain         = block_job_drain,
         .start         = test_block_job_run,
     },
 };
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 1fe6803fe0..592a13613d 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -21,6 +21,7 @@ static const BlockJobDriver test_block_job_driver = {
         .instance_size = sizeof(BlockJob),
         .free          = block_job_free,
         .user_resume   = block_job_user_resume,
+        .drain         = block_job_drain,
     },
 };
 
@@ -201,6 +202,7 @@ static const BlockJobDriver test_cancel_driver = {
         .instance_size = sizeof(CancelJob),
         .free          = block_job_free,
         .user_resume   = block_job_user_resume,
+        .drain         = block_job_drain,
         .start         = cancel_job_start,
     },
     .complete      = cancel_job_complete,
-- 
2.13.6

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

* [Qemu-devel] [PATCH 28/42] job: Move .complete callback to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (26 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 27/42] job: Add job_drain() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 19:37   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 29/42] job: Move job_finish_sync() " Kevin Wolf
                   ` (14 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves the .complete callback that tells a READY job to complete
from BlockJobDriver to JobDriver. The wrapper function job_complete()
doesn't require anything block job specific any more and can be moved
to Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     | 10 ----------
 include/block/blockjob_int.h |  6 ------
 include/qemu/job.h           |  8 ++++++++
 block/mirror.c               | 10 +++++-----
 blockdev.c                   |  2 +-
 blockjob.c                   | 23 +++++------------------
 job.c                        | 16 ++++++++++++++++
 tests/test-bdrv-drain.c      |  6 +++---
 tests/test-blockjob.c        | 10 +++++-----
 9 files changed, 43 insertions(+), 48 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 8477fc5a78..91fdbc9042 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -154,16 +154,6 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 void block_job_cancel(BlockJob *job, bool force);
 
 /**
- * block_job_complete:
- * @job: The job to be completed.
- * @errp: Error object.
- *
- * Asynchronously complete the specified job.
- */
-void block_job_complete(BlockJob *job, Error **errp);
-
-
-/**
  * block_job_finalize:
  * @job: The job to fully commit and finish.
  * @errp: Error object.
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 38fe22d7e0..b8ca7bb0c9 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -39,12 +39,6 @@ struct BlockJobDriver {
     JobDriver job_driver;
 
     /**
-     * Optional callback for job types whose completion must be triggered
-     * manually.
-     */
-    void (*complete)(BlockJob *job, Error **errp);
-
-    /**
      * If the callback is not NULL, prepare 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.
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 49e254f01a..90f8b1ba03 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -167,6 +167,12 @@ struct JobDriver {
      */
     void (*user_resume)(Job *job);
 
+    /**
+     * Optional callback for job types whose completion must be triggered
+     * manually.
+     */
+    void (*complete)(Job *job, Error **errp);
+
     /*
      * If the callback is not NULL, it will be invoked when the job has to be
      * synchronously cancelled or completed; it should drain any activities
@@ -363,6 +369,8 @@ int job_apply_verb(Job *job, JobVerb bv, Error **errp);
 /** The @job could not be started, free it. */
 void job_early_fail(Job *job);
 
+/** Asynchronously complete the specified @job. */
+void job_complete(Job *job, Error **errp);;
 
 typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
 
diff --git a/block/mirror.c b/block/mirror.c
index 4ab7e088ee..f4e3576a17 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -905,16 +905,16 @@ immediate_exit:
     job_defer_to_main_loop(&s->common.job, mirror_exit, data);
 }
 
-static void mirror_complete(BlockJob *job, Error **errp)
+static void mirror_complete(Job *job, Error **errp)
 {
-    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
     BlockDriverState *target;
 
     target = blk_bs(s->target);
 
     if (!s->synced) {
         error_setg(errp, "The active block job '%s' cannot be completed",
-                   job->job.id);
+                   job->id);
         return;
     }
 
@@ -995,8 +995,8 @@ static const BlockJobDriver mirror_job_driver = {
         .drain                  = block_job_drain,
         .start                  = mirror_run,
         .pause                  = mirror_pause,
+        .complete               = mirror_complete,
     },
-    .complete               = mirror_complete,
     .attached_aio_context   = mirror_attached_aio_context,
     .drain                  = mirror_drain,
 };
@@ -1010,8 +1010,8 @@ static const BlockJobDriver commit_active_job_driver = {
         .drain                  = block_job_drain,
         .start                  = mirror_run,
         .pause                  = mirror_pause,
+        .complete               = mirror_complete,
     },
-    .complete               = mirror_complete,
     .attached_aio_context   = mirror_attached_aio_context,
     .drain                  = mirror_drain,
 };
diff --git a/blockdev.c b/blockdev.c
index 278b92ce03..0967f6ab66 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3894,7 +3894,7 @@ void qmp_block_job_complete(const char *device, Error **errp)
     }
 
     trace_qmp_block_job_complete(job);
-    block_job_complete(job, errp);
+    job_complete(&job->job, errp);
     aio_context_release(aio_context);
 }
 
diff --git a/blockjob.c b/blockjob.c
index 76d9cb32b3..a82fdc1c80 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -482,24 +482,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
     return ratelimit_calculate_delay(&job->limit, n);
 }
 
-void block_job_complete(BlockJob *job, Error **errp)
-{
-    /* Should not be reachable via external interface for internal jobs */
-    assert(job->job.id);
-    if (job_apply_verb(&job->job, JOB_VERB_COMPLETE, errp)) {
-        return;
-    }
-    if (job->job.pause_count || job_is_cancelled(&job->job) ||
-        !job->driver->complete)
-    {
-        error_setg(errp, "The active block job '%s' cannot be completed",
-                   job->job.id);
-        return;
-    }
-
-    job->driver->complete(job, errp);
-}
-
 void block_job_finalize(BlockJob *job, Error **errp)
 {
     assert(job && job->job.id);
@@ -572,6 +554,11 @@ void block_job_cancel_sync_all(void)
     }
 }
 
+static void block_job_complete(BlockJob *job, Error **errp)
+{
+    job_complete(&job->job, errp);
+}
+
 int block_job_complete_sync(BlockJob *job, Error **errp)
 {
     return block_job_finish_sync(job, &block_job_complete, errp);
diff --git a/job.c b/job.c
index 6cc05f4476..bb91cb31c6 100644
--- a/job.c
+++ b/job.c
@@ -548,6 +548,22 @@ int job_finalize_single(Job *job)
     return 0;
 }
 
+void job_complete(Job *job, Error **errp)
+{
+    /* Should not be reachable via external interface for internal jobs */
+    assert(job->id);
+    if (job_apply_verb(job, JOB_VERB_COMPLETE, errp)) {
+        return;
+    }
+    if (job->pause_count || job_is_cancelled(job) || !job->driver->complete) {
+        error_setg(errp, "The active block job '%s' cannot be completed",
+                   job->id);
+        return;
+    }
+
+    job->driver->complete(job, errp);
+}
+
 
 typedef struct {
     Job *job;
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index 58ea5663f0..b428aaca68 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -514,9 +514,9 @@ static void coroutine_fn test_job_start(void *opaque)
     job_defer_to_main_loop(&s->common.job, test_job_completed, NULL);
 }
 
-static void test_job_complete(BlockJob *job, Error **errp)
+static void test_job_complete(Job *job, Error **errp)
 {
-    TestBlockJob *s = container_of(job, TestBlockJob, common);
+    TestBlockJob *s = container_of(job, TestBlockJob, common.job);
     s->should_complete = true;
 }
 
@@ -527,8 +527,8 @@ BlockJobDriver test_job_driver = {
         .user_resume    = block_job_user_resume,
         .drain          = block_job_drain,
         .start          = test_job_start,
+        .complete       = test_job_complete,
     },
-    .complete       = test_job_complete,
 };
 
 static void test_blockjob_common(enum drain_type drain_type)
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 592a13613d..e44c608327 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -171,9 +171,9 @@ static void cancel_job_completed(Job *job, void *opaque)
     block_job_completed(bjob, 0);
 }
 
-static void cancel_job_complete(BlockJob *job, Error **errp)
+static void cancel_job_complete(Job *job, Error **errp)
 {
-    CancelJob *s = container_of(job, CancelJob, common);
+    CancelJob *s = container_of(job, CancelJob, common.job);
     s->should_complete = true;
 }
 
@@ -204,8 +204,8 @@ static const BlockJobDriver test_cancel_driver = {
         .user_resume   = block_job_user_resume,
         .drain         = block_job_drain,
         .start         = cancel_job_start,
+        .complete      = cancel_job_complete,
     },
-    .complete      = cancel_job_complete,
 };
 
 static CancelJob *create_common(BlockJob **pjob)
@@ -333,7 +333,7 @@ static void test_cancel_pending(void)
     block_job_enter(job);
     assert(job->job.status == JOB_STATUS_READY);
 
-    block_job_complete(job, &error_abort);
+    job_complete(&job->job, &error_abort);
     block_job_enter(job);
     while (!s->completed) {
         aio_poll(qemu_get_aio_context(), true);
@@ -357,7 +357,7 @@ static void test_cancel_concluded(void)
     block_job_enter(job);
     assert(job->job.status == JOB_STATUS_READY);
 
-    block_job_complete(job, &error_abort);
+    job_complete(&job->job, &error_abort);
     block_job_enter(job);
     while (!s->completed) {
         aio_poll(qemu_get_aio_context(), true);
-- 
2.13.6

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

* [Qemu-devel] [PATCH 29/42] job: Move job_finish_sync() to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (27 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 28/42] job: Move .complete callback to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 19:49   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 30/42] job: Switch transactions to JobTxn Kevin Wolf
                   ` (13 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

block_job_finish_sync() doesn't contain anything block job specific any
more, so it can be moved to Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/qemu/job.h |  9 +++++++++
 blockjob.c         | 55 +++++++++---------------------------------------------
 job.c              | 28 +++++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 46 deletions(-)

diff --git a/include/qemu/job.h b/include/qemu/job.h
index 90f8b1ba03..1c3cdcdad2 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -389,6 +389,15 @@ typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
  */
 void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
 
+/**
+ * Synchronously finishes the given @job. If @finish is given, it is called to
+ * trigger completion or cancellation of the job.
+ *
+ * Returns 0 if the job is successfully completed, -ECANCELED if the job was
+ * cancelled before completing, and -errno in other error cases.
+ */
+int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp);
+
 /* TODO To be removed from the public interface */
 void job_state_transition(Job *job, JobStatus s1);
 void coroutine_fn job_do_yield(Job *job, uint64_t ns);
diff --git a/blockjob.c b/blockjob.c
index a82fdc1c80..22c9998a8c 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -308,40 +308,6 @@ static int block_job_txn_apply(BlockJobTxn *txn, int fn(BlockJob *), bool lock)
     return rc;
 }
 
-static int block_job_finish_sync(BlockJob *job,
-                                 void (*finish)(BlockJob *, Error **errp),
-                                 Error **errp)
-{
-    Error *local_err = NULL;
-    int ret;
-
-    assert(blk_bs(job->blk)->job == job);
-
-    job_ref(&job->job);
-
-    if (finish) {
-        finish(job, &local_err);
-    }
-    if (local_err) {
-        error_propagate(errp, local_err);
-        job_unref(&job->job);
-        return -EBUSY;
-    }
-    /* job_drain calls job_enter, and it should be enough to induce progress
-     * until the job completes or moves to the main thread.
-    */
-    while (!job->job.deferred_to_main_loop && !job_is_completed(&job->job)) {
-        job_drain(&job->job);
-    }
-    while (!job_is_completed(&job->job)) {
-        aio_poll(qemu_get_aio_context(), true);
-    }
-    ret = (job_is_cancelled(&job->job) && job->job.ret == 0)
-          ? -ECANCELED : job->job.ret;
-    job_unref(&job->job);
-    return ret;
-}
-
 static void block_job_completed_txn_abort(BlockJob *job)
 {
     AioContext *ctx;
@@ -376,7 +342,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
         ctx = blk_get_aio_context(other_job->blk);
         if (!job_is_completed(&other_job->job)) {
             assert(job_is_cancelled(&other_job->job));
-            block_job_finish_sync(other_job, NULL, NULL);
+            job_finish_sync(&other_job->job, NULL, NULL);
         }
         job_finalize_single(&other_job->job);
         aio_context_release(ctx);
@@ -529,16 +495,18 @@ void block_job_user_cancel(BlockJob *job, bool force, Error **errp)
 }
 
 /* A wrapper around block_job_cancel() taking an Error ** parameter so it may be
- * used with block_job_finish_sync() without the need for (rather nasty)
- * function pointer casts there. */
-static void block_job_cancel_err(BlockJob *job, Error **errp)
+ * used with job_finish_sync() without the need for (rather nasty) function
+ * pointer casts there. */
+static void block_job_cancel_err(Job *job, Error **errp)
 {
-    block_job_cancel(job, false);
+    BlockJob *bjob = container_of(job, BlockJob, job);
+    assert(is_block_job(job));
+    block_job_cancel(bjob, false);
 }
 
 int block_job_cancel_sync(BlockJob *job)
 {
-    return block_job_finish_sync(job, &block_job_cancel_err, NULL);
+    return job_finish_sync(&job->job, &block_job_cancel_err, NULL);
 }
 
 void block_job_cancel_sync_all(void)
@@ -554,14 +522,9 @@ void block_job_cancel_sync_all(void)
     }
 }
 
-static void block_job_complete(BlockJob *job, Error **errp)
-{
-    job_complete(&job->job, errp);
-}
-
 int block_job_complete_sync(BlockJob *job, Error **errp)
 {
-    return block_job_finish_sync(job, &block_job_complete, errp);
+    return job_finish_sync(&job->job, job_complete, errp);
 }
 
 void block_job_progress_update(BlockJob *job, uint64_t done)
diff --git a/job.c b/job.c
index bb91cb31c6..49dce57c9e 100644
--- a/job.c
+++ b/job.c
@@ -596,3 +596,31 @@ void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque)
     aio_bh_schedule_oneshot(qemu_get_aio_context(),
                             job_defer_to_main_loop_bh, data);
 }
+
+int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)
+{
+    Error *local_err = NULL;
+    int ret;
+
+    job_ref(job);
+
+    if (finish) {
+        finish(job, &local_err);
+    }
+    if (local_err) {
+        error_propagate(errp, local_err);
+        job_unref(job);
+        return -EBUSY;
+    }
+    /* job_drain calls job_enter, and it should be enough to induce progress
+     * until the job completes or moves to the main thread. */
+    while (!job->deferred_to_main_loop && !job_is_completed(job)) {
+        job_drain(job);
+    }
+    while (!job_is_completed(job)) {
+        aio_poll(qemu_get_aio_context(), true);
+    }
+    ret = (job_is_cancelled(job) && job->ret == 0) ? -ECANCELED : job->ret;
+    job_unref(job);
+    return ret;
+}
-- 
2.13.6

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

* [Qemu-devel] [PATCH 30/42] job: Switch transactions to JobTxn
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (28 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 29/42] job: Move job_finish_sync() " Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 20:00   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 31/42] job: Move transactions to Job Kevin Wolf
                   ` (12 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This doesn't actually move any transaction code to Job yet, but it
renames the type for transactions from BlockJobTxn to JobTxn and makes
them contain Jobs rather than BlockJobs

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/block_int.h    |  2 +-
 include/block/blockjob.h     | 11 ++++----
 include/block/blockjob_int.h |  2 +-
 include/qemu/job.h           |  3 +++
 block/backup.c               |  2 +-
 blockdev.c                   | 14 +++++------
 blockjob.c                   | 60 +++++++++++++++++++++++---------------------
 tests/test-blockjob-txn.c    |  8 +++---
 8 files changed, 54 insertions(+), 48 deletions(-)

diff --git a/include/block/block_int.h b/include/block/block_int.h
index e3d6219f4e..4b68545866 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -1015,7 +1015,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
                             BlockdevOnError on_target_error,
                             int creation_flags,
                             BlockCompletionFunc *cb, void *opaque,
-                            BlockJobTxn *txn, Error **errp);
+                            JobTxn *txn, Error **errp);
 
 void hmp_drive_add_node(Monitor *mon, const char *optstr);
 
diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 91fdbc9042..fbb8f54dc6 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -33,7 +33,7 @@
 #define SLICE_TIME 100000000ULL /* ns */
 
 typedef struct BlockJobDriver BlockJobDriver;
-typedef struct BlockJobTxn BlockJobTxn;
+typedef struct JobTxn JobTxn;
 
 /**
  * BlockJob:
@@ -85,8 +85,7 @@ typedef struct BlockJob {
     /** BlockDriverStates that are involved in this block job */
     GSList *nodes;
 
-    BlockJobTxn *txn;
-    QLIST_ENTRY(BlockJob) txn_list;
+    JobTxn *txn;
 } BlockJob;
 
 /**
@@ -273,7 +272,7 @@ void block_job_iostatus_reset(BlockJob *job);
  * group.  Jobs wait for each other before completing.  Cancelling one job
  * cancels all jobs in the transaction.
  */
-BlockJobTxn *block_job_txn_new(void);
+JobTxn *block_job_txn_new(void);
 
 /**
  * block_job_txn_unref:
@@ -282,7 +281,7 @@ BlockJobTxn *block_job_txn_new(void);
  * 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);
+void block_job_txn_unref(JobTxn *txn);
 
 /**
  * block_job_txn_add_job:
@@ -293,7 +292,7 @@ void block_job_txn_unref(BlockJobTxn *txn);
  * 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);
+void block_job_txn_add_job(JobTxn *txn, BlockJob *job);
 
 /**
  * block_job_is_internal:
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index b8ca7bb0c9..ce66a9b51c 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -91,7 +91,7 @@ struct BlockJobDriver {
  * called from a wrapper that is specific to the job type.
  */
 void *block_job_create(const char *job_id, const BlockJobDriver *driver,
-                       BlockJobTxn *txn, BlockDriverState *bs, uint64_t perm,
+                       JobTxn *txn, BlockDriverState *bs, uint64_t perm,
                        uint64_t shared_perm, int64_t speed, int flags,
                        BlockCompletionFunc *cb, void *opaque, Error **errp);
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 1c3cdcdad2..614a2dea92 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -132,6 +132,9 @@ typedef struct Job {
 
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
+
+    /** Element of the list of jobs in a job transaction */
+    QLIST_ENTRY(Job) txn_list;
 } Job;
 
 /**
diff --git a/block/backup.c b/block/backup.c
index ca7d990b21..6172f90c90 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -547,7 +547,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
                   BlockdevOnError on_target_error,
                   int creation_flags,
                   BlockCompletionFunc *cb, void *opaque,
-                  BlockJobTxn *txn, Error **errp)
+                  JobTxn *txn, Error **errp)
 {
     int64_t len;
     BlockDriverInfo bdi;
diff --git a/blockdev.c b/blockdev.c
index 0967f6ab66..817c3848c0 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1446,7 +1446,7 @@ typedef struct BlkActionOps {
 struct BlkActionState {
     TransactionAction *action;
     const BlkActionOps *ops;
-    BlockJobTxn *block_job_txn;
+    JobTxn *block_job_txn;
     TransactionProperties *txn_props;
     QSIMPLEQ_ENTRY(BlkActionState) entry;
 };
@@ -1864,7 +1864,7 @@ typedef struct DriveBackupState {
     BlockJob *job;
 } DriveBackupState;
 
-static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
+static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn,
                             Error **errp);
 
 static void drive_backup_prepare(BlkActionState *common, Error **errp)
@@ -1954,7 +1954,7 @@ typedef struct BlockdevBackupState {
     BlockJob *job;
 } BlockdevBackupState;
 
-static BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
+static BlockJob *do_blockdev_backup(BlockdevBackup *backup, JobTxn *txn,
                                     Error **errp);
 
 static void blockdev_backup_prepare(BlkActionState *common, Error **errp)
@@ -2243,7 +2243,7 @@ void qmp_transaction(TransactionActionList *dev_list,
                      Error **errp)
 {
     TransactionActionList *dev_entry = dev_list;
-    BlockJobTxn *block_job_txn = NULL;
+    JobTxn *block_job_txn = NULL;
     BlkActionState *state, *next;
     Error *local_err = NULL;
 
@@ -2251,7 +2251,7 @@ void qmp_transaction(TransactionActionList *dev_list,
     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.
+     * If not, we don't really need to make a JobTxn.
      */
     props = get_transaction_properties(props);
     if (props->completion_mode != ACTION_COMPLETION_MODE_INDIVIDUAL) {
@@ -3264,7 +3264,7 @@ out:
     aio_context_release(aio_context);
 }
 
-static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
+static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn,
                                  Error **errp)
 {
     BlockDriverState *bs;
@@ -3434,7 +3434,7 @@ BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
     return bdrv_named_nodes_list(errp);
 }
 
-BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
+BlockJob *do_blockdev_backup(BlockdevBackup *backup, JobTxn *txn,
                              Error **errp)
 {
     BlockDriverState *bs;
diff --git a/blockjob.c b/blockjob.c
index 22c9998a8c..cc8b98dee0 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -37,13 +37,13 @@
 #include "qemu/timer.h"
 
 /* Transactional group of block jobs */
-struct BlockJobTxn {
+struct JobTxn {
 
     /* Is this txn being cancelled? */
     bool aborting;
 
     /* List of jobs */
-    QLIST_HEAD(, BlockJob) jobs;
+    QLIST_HEAD(, Job) jobs;
 
     /* Reference count */
     int refcnt;
@@ -95,27 +95,27 @@ BlockJob *block_job_get(const char *id)
     }
 }
 
-BlockJobTxn *block_job_txn_new(void)
+JobTxn *block_job_txn_new(void)
 {
-    BlockJobTxn *txn = g_new0(BlockJobTxn, 1);
+    JobTxn *txn = g_new0(JobTxn, 1);
     QLIST_INIT(&txn->jobs);
     txn->refcnt = 1;
     return txn;
 }
 
-static void block_job_txn_ref(BlockJobTxn *txn)
+static void block_job_txn_ref(JobTxn *txn)
 {
     txn->refcnt++;
 }
 
-void block_job_txn_unref(BlockJobTxn *txn)
+void block_job_txn_unref(JobTxn *txn)
 {
     if (txn && --txn->refcnt == 0) {
         g_free(txn);
     }
 }
 
-void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job)
+void block_job_txn_add_job(JobTxn *txn, BlockJob *job)
 {
     if (!txn) {
         return;
@@ -124,14 +124,14 @@ void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job)
     assert(!job->txn);
     job->txn = txn;
 
-    QLIST_INSERT_HEAD(&txn->jobs, job, txn_list);
+    QLIST_INSERT_HEAD(&txn->jobs, &job->job, txn_list);
     block_job_txn_ref(txn);
 }
 
 void block_job_txn_del_job(BlockJob *job)
 {
     if (job->txn) {
-        QLIST_REMOVE(job, txn_list);
+        QLIST_REMOVE(&job->job, txn_list);
         block_job_txn_unref(job->txn);
         job->txn = NULL;
     }
@@ -286,18 +286,22 @@ static void job_cancel_async(Job *job, bool force)
     job->force_cancel |= force;
 }
 
-static int block_job_txn_apply(BlockJobTxn *txn, int fn(BlockJob *), bool lock)
+static int block_job_txn_apply(JobTxn *txn, int fn(BlockJob *), bool lock)
 {
     AioContext *ctx;
-    BlockJob *job, *next;
+    Job *job, *next;
+    BlockJob *bjob;
     int rc = 0;
 
     QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) {
+        assert(is_block_job(job));
+        bjob = container_of(job, BlockJob, job);
+
         if (lock) {
-            ctx = blk_get_aio_context(job->blk);
+            ctx = job->aio_context;
             aio_context_acquire(ctx);
         }
-        rc = fn(job);
+        rc = fn(bjob);
         if (lock) {
             aio_context_release(ctx);
         }
@@ -311,8 +315,8 @@ static int block_job_txn_apply(BlockJobTxn *txn, int fn(BlockJob *), bool lock)
 static void block_job_completed_txn_abort(BlockJob *job)
 {
     AioContext *ctx;
-    BlockJobTxn *txn = job->txn;
-    BlockJob *other_job;
+    JobTxn *txn = job->txn;
+    Job *other_job;
 
     if (txn->aborting) {
         /*
@@ -325,7 +329,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
 
     /* We are the first failed job. Cancel other jobs. */
     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        ctx = blk_get_aio_context(other_job->blk);
+        ctx = other_job->aio_context;
         aio_context_acquire(ctx);
     }
 
@@ -333,18 +337,18 @@ static void block_job_completed_txn_abort(BlockJob *job)
      * them; this job, however, may or may not be cancelled, depending
      * on the caller, so leave it. */
     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        if (other_job != job) {
-            job_cancel_async(&other_job->job, false);
+        if (other_job != &job->job) {
+            job_cancel_async(other_job, false);
         }
     }
     while (!QLIST_EMPTY(&txn->jobs)) {
         other_job = QLIST_FIRST(&txn->jobs);
-        ctx = blk_get_aio_context(other_job->blk);
-        if (!job_is_completed(&other_job->job)) {
-            assert(job_is_cancelled(&other_job->job));
-            job_finish_sync(&other_job->job, NULL, NULL);
+        ctx = other_job->aio_context;
+        if (!job_is_completed(other_job)) {
+            assert(job_is_cancelled(other_job));
+            job_finish_sync(other_job, NULL, NULL);
         }
-        job_finalize_single(&other_job->job);
+        job_finalize_single(other_job);
         aio_context_release(ctx);
     }
 
@@ -386,8 +390,8 @@ static int block_job_transition_to_pending(BlockJob *job)
 
 static void block_job_completed_txn_success(BlockJob *job)
 {
-    BlockJobTxn *txn = job->txn;
-    BlockJob *other_job;
+    JobTxn *txn = job->txn;
+    Job *other_job;
 
     job_state_transition(&job->job, JOB_STATUS_WAITING);
 
@@ -396,10 +400,10 @@ static void block_job_completed_txn_success(BlockJob *job)
      * txn.
      */
     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        if (!job_is_completed(&other_job->job)) {
+        if (!job_is_completed(other_job)) {
             return;
         }
-        assert(other_job->job.ret == 0);
+        assert(other_job->ret == 0);
     }
 
     block_job_txn_apply(txn, block_job_transition_to_pending, false);
@@ -629,7 +633,7 @@ static void block_job_event_pending(Notifier *n, void *opaque)
  */
 
 void *block_job_create(const char *job_id, const BlockJobDriver *driver,
-                       BlockJobTxn *txn, BlockDriverState *bs, uint64_t perm,
+                       JobTxn *txn, BlockDriverState *bs, uint64_t perm,
                        uint64_t shared_perm, int64_t speed, int flags,
                        BlockCompletionFunc *cb, void *opaque, Error **errp)
 {
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 1572f8d96f..ec5d592b68 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -93,7 +93,7 @@ static const BlockJobDriver test_block_job_driver = {
  */
 static BlockJob *test_block_job_start(unsigned int iterations,
                                       bool use_timer,
-                                      int rc, int *result, BlockJobTxn *txn)
+                                      int rc, int *result, JobTxn *txn)
 {
     BlockDriverState *bs;
     TestBlockJob *s;
@@ -122,7 +122,7 @@ static BlockJob *test_block_job_start(unsigned int iterations,
 static void test_single_job(int expected)
 {
     BlockJob *job;
-    BlockJobTxn *txn;
+    JobTxn *txn;
     int result = -EINPROGRESS;
 
     txn = block_job_txn_new();
@@ -160,7 +160,7 @@ static void test_pair_jobs(int expected1, int expected2)
 {
     BlockJob *job1;
     BlockJob *job2;
-    BlockJobTxn *txn;
+    JobTxn *txn;
     int result1 = -EINPROGRESS;
     int result2 = -EINPROGRESS;
 
@@ -222,7 +222,7 @@ static void test_pair_jobs_fail_cancel_race(void)
 {
     BlockJob *job1;
     BlockJob *job2;
-    BlockJobTxn *txn;
+    JobTxn *txn;
     int result1 = -EINPROGRESS;
     int result2 = -EINPROGRESS;
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 31/42] job: Move transactions to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (29 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 30/42] job: Switch transactions to JobTxn Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 20:28   ` Max Reitz
  2018-05-14 21:17   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation " Kevin Wolf
                   ` (11 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves the logic that implements job transactions from BlockJob to
Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |  54 ----------
 include/block/blockjob_int.h |  10 --
 include/qemu/job.h           |  71 +++++++++++--
 blockdev.c                   |   6 +-
 blockjob.c                   | 238 +------------------------------------------
 job.c                        | 235 ++++++++++++++++++++++++++++++++++++++++--
 tests/test-blockjob-txn.c    |  12 +--
 tests/test-blockjob.c        |   2 +-
 8 files changed, 304 insertions(+), 324 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index fbb8f54dc6..e8e9e5f370 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -33,7 +33,6 @@
 #define SLICE_TIME 100000000ULL /* ns */
 
 typedef struct BlockJobDriver BlockJobDriver;
-typedef struct JobTxn JobTxn;
 
 /**
  * BlockJob:
@@ -84,8 +83,6 @@ typedef struct BlockJob {
 
     /** BlockDriverStates that are involved in this block job */
     GSList *nodes;
-
-    JobTxn *txn;
 } BlockJob;
 
 /**
@@ -153,22 +150,6 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 void block_job_cancel(BlockJob *job, bool force);
 
 /**
- * block_job_finalize:
- * @job: The job to fully commit and finish.
- * @errp: Error object.
- *
- * For jobs that have finished their work and are pending
- * awaiting explicit acknowledgement to commit their work,
- * This will commit that work.
- *
- * FIXME: Make the below statement universally true:
- * For jobs that support the manual workflow mode, all graph
- * changes that occur as a result will occur after this command
- * and before a successful reply.
- */
-void block_job_finalize(BlockJob *job, Error **errp);
-
-/**
  * block_job_dismiss:
  * @job: The job to be dismissed.
  * @errp: Error object.
@@ -260,41 +241,6 @@ int block_job_complete_sync(BlockJob *job, Error **errp);
 void block_job_iostatus_reset(BlockJob *job);
 
 /**
- * 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.
- */
-JobTxn *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(JobTxn *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(JobTxn *txn, BlockJob *job);
-
-/**
  * block_job_is_internal:
  * @job: The job to determine if it is user-visible or not.
  *
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index ce66a9b51c..29a28020ac 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -38,16 +38,6 @@ struct BlockJobDriver {
     /** Generic JobDriver callbacks and settings */
     JobDriver job_driver;
 
-    /**
-     * If the callback is not NULL, prepare 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.
-     *
-     * This callback will not be invoked if the job has already failed.
-     * If it fails, abort and then clean will be called.
-     */
-    int (*prepare)(BlockJob *job);
-
     /*
      * If the callback is not NULL, it will be invoked before the job is
      * resumed in a new AioContext.  This is the place to move any resources
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 614a2dea92..84a9eb7980 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -32,6 +32,8 @@
 #include "block/aio.h"
 
 typedef struct JobDriver JobDriver;
+typedef struct JobTxn JobTxn;
+
 
 /**
  * Long-running operation.
@@ -133,6 +135,9 @@ typedef struct Job {
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 
+    /** Transaction this job is part of */
+    JobTxn *txn;
+
     /** Element of the list of jobs in a job transaction */
     QLIST_ENTRY(Job) txn_list;
 } Job;
@@ -184,6 +189,16 @@ struct JobDriver {
     void (*drain)(Job *job);
 
     /**
+     * If the callback is not NULL, prepare 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.
+     *
+     * This callback will not be invoked if the job has already failed.
+     * If it fails, abort and then clean will be called.
+     */
+    int (*prepare)(Job *job);
+
+    /**
      * 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.
@@ -227,20 +242,52 @@ typedef enum JobCreateFlags {
     JOB_MANUAL_DISMISS = 0x04,
 } JobCreateFlags;
 
+/**
+ * Allocate and return a new job transaction. Jobs can be added to the
+ * transaction using 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.
+ */
+JobTxn *job_txn_new(void);
+
+/**
+ * Release a reference that was previously acquired with job_txn_add_job or
+ * job_txn_new. If it's the last reference to the object, it will be freed.
+ */
+void job_txn_unref(JobTxn *txn);
+
+/**
+ * @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.
+ *
+ * If @txn is NULL, the function does nothing.
+ */
+void job_txn_add_job(JobTxn *txn, Job *job);
 
 /**
  * Create a new long-running job and return it.
  *
  * @job_id: The id of the newly-created job, or %NULL for internal jobs
  * @driver: The class object for the newly-created job.
+ * @txn: The transaction this job belongs to, if any. %NULL otherwise.
  * @ctx: The AioContext to run the job coroutine in.
  * @flags: Creation flags for the job. See @JobCreateFlags.
  * @cb: Completion function for the job.
  * @opaque: Opaque pointer value passed to @cb.
  * @errp: Error object.
  */
-void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp);
+void *job_create(const char *job_id, const JobDriver *driver, JobTxn *txn,
+                 AioContext *ctx, int flags, BlockCompletionFunc *cb,
+                 void *opaque, Error **errp);
 
 /**
  * Add a reference to Job refcnt, it will be decreased with job_unref, and then
@@ -260,9 +307,6 @@ void job_event_cancelled(Job *job);
 /** To be called when a successfully completed job is finalised. */
 void job_event_completed(Job *job);
 
-/** To be called when the job transitions to PENDING */
-void job_event_pending(Job *job);
-
 /**
  * Conditionally enter the job coroutine if the job is ready to run, not
  * already busy and fn() returns true. fn() is called while under the job_lock
@@ -375,6 +419,16 @@ void job_early_fail(Job *job);
 /** Asynchronously complete the specified @job. */
 void job_complete(Job *job, Error **errp);;
 
+/**
+ * For a @job that has finished its work and is pending awaiting explicit
+ * acknowledgement to commit its work, this will commit that work.
+ *
+ * FIXME: Make the below statement universally true:
+ * For jobs that support the manual workflow mode, all graph changes that occur
+ * as a result will occur after this command and before a successful reply.
+ */
+void job_finalize(Job *job, Error **errp);
+
 typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
 
 /**
@@ -407,10 +461,9 @@ void coroutine_fn job_do_yield(Job *job, uint64_t ns);
 bool job_should_pause(Job *job);
 bool job_started(Job *job);
 void job_do_dismiss(Job *job);
-int job_finalize_single(Job *job);
 void job_update_rc(Job *job);
-
-typedef struct BlockJob BlockJob;
-void block_job_txn_del_job(BlockJob *job);
+void job_cancel_async(Job *job, bool force);
+void job_completed_txn_abort(Job *job);
+void job_completed_txn_success(Job *job);
 
 #endif
diff --git a/blockdev.c b/blockdev.c
index 817c3848c0..87a23bed6f 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2255,7 +2255,7 @@ void qmp_transaction(TransactionActionList *dev_list,
      */
     props = get_transaction_properties(props);
     if (props->completion_mode != ACTION_COMPLETION_MODE_INDIVIDUAL) {
-        block_job_txn = block_job_txn_new();
+        block_job_txn = job_txn_new();
     }
 
     /* drain all i/o before any operations */
@@ -2314,7 +2314,7 @@ exit:
     if (!has_props) {
         qapi_free_TransactionProperties(props);
     }
-    block_job_txn_unref(block_job_txn);
+    job_txn_unref(block_job_txn);
 }
 
 void qmp_eject(bool has_device, const char *device,
@@ -3908,7 +3908,7 @@ void qmp_block_job_finalize(const char *id, Error **errp)
     }
 
     trace_qmp_block_job_finalize(job);
-    block_job_finalize(job, errp);
+    job_finalize(&job->job, errp);
     aio_context_release(aio_context);
 }
 
diff --git a/blockjob.c b/blockjob.c
index cc8b98dee0..fd096e0f0c 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -36,19 +36,6 @@
 #include "qemu/coroutine.h"
 #include "qemu/timer.h"
 
-/* Transactional group of block jobs */
-struct JobTxn {
-
-    /* Is this txn being cancelled? */
-    bool aborting;
-
-    /* List of jobs */
-    QLIST_HEAD(, Job) jobs;
-
-    /* Reference count */
-    int refcnt;
-};
-
 /*
  * The block job API is composed of two categories of functions.
  *
@@ -95,48 +82,6 @@ BlockJob *block_job_get(const char *id)
     }
 }
 
-JobTxn *block_job_txn_new(void)
-{
-    JobTxn *txn = g_new0(JobTxn, 1);
-    QLIST_INIT(&txn->jobs);
-    txn->refcnt = 1;
-    return txn;
-}
-
-static void block_job_txn_ref(JobTxn *txn)
-{
-    txn->refcnt++;
-}
-
-void block_job_txn_unref(JobTxn *txn)
-{
-    if (txn && --txn->refcnt == 0) {
-        g_free(txn);
-    }
-}
-
-void block_job_txn_add_job(JobTxn *txn, BlockJob *job)
-{
-    if (!txn) {
-        return;
-    }
-
-    assert(!job->txn);
-    job->txn = txn;
-
-    QLIST_INSERT_HEAD(&txn->jobs, &job->job, txn_list);
-    block_job_txn_ref(txn);
-}
-
-void block_job_txn_del_job(BlockJob *job)
-{
-    if (job->txn) {
-        QLIST_REMOVE(&job->job, txn_list);
-        block_job_txn_unref(job->txn);
-        job->txn = NULL;
-    }
-}
-
 static void block_job_attached_aio_context(AioContext *new_context,
                                            void *opaque);
 static void block_job_detach_aio_context(void *opaque);
@@ -146,8 +91,6 @@ void block_job_free(Job *job)
     BlockJob *bjob = container_of(job, BlockJob, job);
     BlockDriverState *bs = blk_bs(bjob->blk);
 
-    assert(!bjob->txn);
-
     bs->job = NULL;
     block_job_remove_all_bdrv(bjob);
     blk_remove_aio_context_notifier(bjob->blk,
@@ -262,158 +205,6 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
     return job->driver;
 }
 
-static int block_job_prepare(BlockJob *job)
-{
-    if (job->job.ret == 0 && job->driver->prepare) {
-        job->job.ret = job->driver->prepare(job);
-    }
-    return job->job.ret;
-}
-
-static void job_cancel_async(Job *job, bool force)
-{
-    if (job->user_paused) {
-        /* Do not call job_enter here, the caller will handle it.  */
-        job->user_paused = false;
-        if (job->driver->user_resume) {
-            job->driver->user_resume(job);
-        }
-        assert(job->pause_count > 0);
-        job->pause_count--;
-    }
-    job->cancelled = true;
-    /* To prevent 'force == false' overriding a previous 'force == true' */
-    job->force_cancel |= force;
-}
-
-static int block_job_txn_apply(JobTxn *txn, int fn(BlockJob *), bool lock)
-{
-    AioContext *ctx;
-    Job *job, *next;
-    BlockJob *bjob;
-    int rc = 0;
-
-    QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) {
-        assert(is_block_job(job));
-        bjob = container_of(job, BlockJob, job);
-
-        if (lock) {
-            ctx = job->aio_context;
-            aio_context_acquire(ctx);
-        }
-        rc = fn(bjob);
-        if (lock) {
-            aio_context_release(ctx);
-        }
-        if (rc) {
-            break;
-        }
-    }
-    return rc;
-}
-
-static void block_job_completed_txn_abort(BlockJob *job)
-{
-    AioContext *ctx;
-    JobTxn *txn = job->txn;
-    Job *other_job;
-
-    if (txn->aborting) {
-        /*
-         * We are cancelled by another job, which will handle everything.
-         */
-        return;
-    }
-    txn->aborting = true;
-    block_job_txn_ref(txn);
-
-    /* We are the first failed job. Cancel other jobs. */
-    QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        ctx = other_job->aio_context;
-        aio_context_acquire(ctx);
-    }
-
-    /* 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. */
-    QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        if (other_job != &job->job) {
-            job_cancel_async(other_job, false);
-        }
-    }
-    while (!QLIST_EMPTY(&txn->jobs)) {
-        other_job = QLIST_FIRST(&txn->jobs);
-        ctx = other_job->aio_context;
-        if (!job_is_completed(other_job)) {
-            assert(job_is_cancelled(other_job));
-            job_finish_sync(other_job, NULL, NULL);
-        }
-        job_finalize_single(other_job);
-        aio_context_release(ctx);
-    }
-
-    block_job_txn_unref(txn);
-}
-
-static int block_job_needs_finalize(BlockJob *job)
-{
-    return !job->job.auto_finalize;
-}
-
-static int block_job_finalize_single(BlockJob *job)
-{
-    return job_finalize_single(&job->job);
-}
-
-static void block_job_do_finalize(BlockJob *job)
-{
-    int rc;
-    assert(job && job->txn);
-
-    /* prepare the transaction to complete */
-    rc = block_job_txn_apply(job->txn, block_job_prepare, true);
-    if (rc) {
-        block_job_completed_txn_abort(job);
-    } else {
-        block_job_txn_apply(job->txn, block_job_finalize_single, true);
-    }
-}
-
-static int block_job_transition_to_pending(BlockJob *job)
-{
-    job_state_transition(&job->job, JOB_STATUS_PENDING);
-    if (!job->job.auto_finalize) {
-        job_event_pending(&job->job);
-    }
-    return 0;
-}
-
-static void block_job_completed_txn_success(BlockJob *job)
-{
-    JobTxn *txn = job->txn;
-    Job *other_job;
-
-    job_state_transition(&job->job, JOB_STATUS_WAITING);
-
-    /*
-     * Successful completion, see if there are other running jobs in this
-     * txn.
-     */
-    QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-        if (!job_is_completed(other_job)) {
-            return;
-        }
-        assert(other_job->ret == 0);
-    }
-
-    block_job_txn_apply(txn, block_job_transition_to_pending, false);
-
-    /* If no jobs need manual finalization, automatically do so */
-    if (block_job_txn_apply(txn, block_job_needs_finalize, false) == 0) {
-        block_job_do_finalize(job);
-    }
-}
-
 /* Assumes the block_job_mutex is held */
 static bool job_timer_pending(Job *job)
 {
@@ -452,15 +243,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
     return ratelimit_calculate_delay(&job->limit, n);
 }
 
-void block_job_finalize(BlockJob *job, Error **errp)
-{
-    assert(job && job->job.id);
-    if (job_apply_verb(&job->job, JOB_VERB_FINALIZE, errp)) {
-        return;
-    }
-    block_job_do_finalize(job);
-}
-
 void block_job_dismiss(BlockJob **jobptr, Error **errp)
 {
     BlockJob *job = *jobptr;
@@ -484,7 +266,7 @@ void block_job_cancel(BlockJob *job, bool force)
     if (!job_started(&job->job)) {
         block_job_completed(job, -ECANCELED);
     } else if (job->job.deferred_to_main_loop) {
-        block_job_completed_txn_abort(job);
+        job_completed_txn_abort(&job->job);
     } else {
         block_job_enter(job);
     }
@@ -657,7 +439,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
         return NULL;
     }
 
-    job = job_create(job_id, &driver->job_driver, blk_get_aio_context(blk),
+    job = job_create(job_id, &driver->job_driver, txn, blk_get_aio_context(blk),
                      flags, cb, opaque, errp);
     if (job == NULL) {
         blk_unref(blk);
@@ -705,30 +487,20 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
         }
     }
 
-    /* Single jobs are modeled as single-job transactions for sake of
-     * consolidating the job management logic */
-    if (!txn) {
-        txn = block_job_txn_new();
-        block_job_txn_add_job(txn, job);
-        block_job_txn_unref(txn);
-    } else {
-        block_job_txn_add_job(txn, job);
-    }
-
     return job;
 }
 
 void block_job_completed(BlockJob *job, int ret)
 {
-    assert(job && job->txn && !job_is_completed(&job->job));
+    assert(job && job->job.txn && !job_is_completed(&job->job));
     assert(blk_bs(job->blk)->job == job);
     job->job.ret = ret;
     job_update_rc(&job->job);
     trace_block_job_completed(job, ret, job->job.ret);
     if (job->job.ret) {
-        block_job_completed_txn_abort(job);
+        job_completed_txn_abort(&job->job);
     } else {
-        block_job_completed_txn_success(job);
+        job_completed_txn_success(&job->job);
     }
 }
 
diff --git a/job.c b/job.c
index 49dce57c9e..2d782859ac 100644
--- a/job.c
+++ b/job.c
@@ -60,6 +60,19 @@ bool JobVerbTable[JOB_VERB__MAX][JOB_STATUS__MAX] = {
     [JOB_VERB_DISMISS]              = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
 };
 
+/* Transactional group of jobs */
+struct JobTxn {
+
+    /* Is this txn being cancelled? */
+    bool aborting;
+
+    /* List of jobs */
+    QLIST_HEAD(, Job) jobs;
+
+    /* Reference count */
+    int refcnt;
+};
+
 /* Right now, this mutex is only needed to synchronize accesses to job->busy
  * and job->sleep_timer, such as concurrent calls to job_do_yield and
  * job_enter. */
@@ -80,6 +93,71 @@ static void __attribute__((__constructor__)) job_init(void)
     qemu_mutex_init(&job_mutex);
 }
 
+JobTxn *job_txn_new(void)
+{
+    JobTxn *txn = g_new0(JobTxn, 1);
+    QLIST_INIT(&txn->jobs);
+    txn->refcnt = 1;
+    return txn;
+}
+
+static void job_txn_ref(JobTxn *txn)
+{
+    txn->refcnt++;
+}
+
+void job_txn_unref(JobTxn *txn)
+{
+    if (txn && --txn->refcnt == 0) {
+        g_free(txn);
+    }
+}
+
+void job_txn_add_job(JobTxn *txn, Job *job)
+{
+    if (!txn) {
+        return;
+    }
+
+    assert(!job->txn);
+    job->txn = txn;
+
+    QLIST_INSERT_HEAD(&txn->jobs, job, txn_list);
+    job_txn_ref(txn);
+}
+
+static void job_txn_del_job(Job *job)
+{
+    if (job->txn) {
+        QLIST_REMOVE(job, txn_list);
+        job_txn_unref(job->txn);
+        job->txn = NULL;
+    }
+}
+
+static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)
+{
+    AioContext *ctx;
+    Job *job, *next;
+    int rc = 0;
+
+    QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) {
+        if (lock) {
+            ctx = job->aio_context;
+            aio_context_acquire(ctx);
+        }
+        rc = fn(job);
+        if (lock) {
+            aio_context_release(ctx);
+        }
+        if (rc) {
+            break;
+        }
+    }
+    return rc;
+}
+
+
 /* TODO Make static once the whole state machine is in job.c */
 void job_state_transition(Job *job, JobStatus s1)
 {
@@ -181,8 +259,9 @@ static void job_sleep_timer_cb(void *opaque)
     job_enter(job);
 }
 
-void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp)
+void *job_create(const char *job_id, const JobDriver *driver, JobTxn *txn,
+                 AioContext *ctx, int flags, BlockCompletionFunc *cb,
+                 void *opaque, Error **errp)
 {
     Job *job;
 
@@ -228,6 +307,16 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
 
     QLIST_INSERT_HEAD(&jobs, job, job_list);
 
+    /* Single jobs are modeled as single-job transactions for sake of
+     * consolidating the job management logic */
+    if (!txn) {
+        txn = job_txn_new();
+        job_txn_add_job(txn, job);
+        job_txn_unref(txn);
+    } else {
+        job_txn_add_job(txn, job);
+    }
+
     return job;
 }
 
@@ -241,6 +330,7 @@ void job_unref(Job *job)
     if (--job->refcnt == 0) {
         assert(job->status == JOB_STATUS_NULL);
         assert(!timer_pending(&job->sleep_timer));
+        assert(!job->txn);
 
         if (job->driver->free) {
             job->driver->free(job);
@@ -263,9 +353,10 @@ void job_event_completed(Job *job)
     notifier_list_notify(&job->on_finalize_completed, job);
 }
 
-void job_event_pending(Job *job)
+static int job_event_pending(Job *job)
 {
     notifier_list_notify(&job->on_pending, job);
+    return 0;
 }
 
 void job_enter_cond(Job *job, bool(*fn)(Job *job))
@@ -461,8 +552,7 @@ void job_do_dismiss(Job *job)
     job->paused = false;
     job->deferred_to_main_loop = true;
 
-    /* TODO Don't assume it's a BlockJob */
-    block_job_txn_del_job((BlockJob*) job);
+    job_txn_del_job(job);
 
     job_state_transition(job, JOB_STATUS_NULL);
     job_unref(job);
@@ -515,7 +605,7 @@ static void job_clean(Job *job)
     }
 }
 
-int job_finalize_single(Job *job)
+static int job_finalize_single(Job *job)
 {
     assert(job_is_completed(job));
 
@@ -542,12 +632,141 @@ int job_finalize_single(Job *job)
         }
     }
 
-    /* TODO Don't assume it's a BlockJob */
-    block_job_txn_del_job((BlockJob*) job);
+    job_txn_del_job(job);
     job_conclude(job);
     return 0;
 }
 
+void job_cancel_async(Job *job, bool force)
+{
+    if (job->user_paused) {
+        /* Do not call job_enter here, the caller will handle it.  */
+        job->user_paused = false;
+        if (job->driver->user_resume) {
+            job->driver->user_resume(job);
+        }
+        assert(job->pause_count > 0);
+        job->pause_count--;
+    }
+    job->cancelled = true;
+    /* To prevent 'force == false' overriding a previous 'force == true' */
+    job->force_cancel |= force;
+}
+
+void job_completed_txn_abort(Job *job)
+{
+    AioContext *ctx;
+    JobTxn *txn = job->txn;
+    Job *other_job;
+
+    if (txn->aborting) {
+        /*
+         * We are cancelled by another job, which will handle everything.
+         */
+        return;
+    }
+    txn->aborting = true;
+    job_txn_ref(txn);
+
+    /* We are the first failed job. Cancel other jobs. */
+    QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
+        ctx = other_job->aio_context;
+        aio_context_acquire(ctx);
+    }
+
+    /* 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. */
+    QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
+        if (other_job != job) {
+            job_cancel_async(other_job, false);
+        }
+    }
+    while (!QLIST_EMPTY(&txn->jobs)) {
+        other_job = QLIST_FIRST(&txn->jobs);
+        ctx = other_job->aio_context;
+        if (!job_is_completed(other_job)) {
+            assert(job_is_cancelled(other_job));
+            job_finish_sync(other_job, NULL, NULL);
+        }
+        job_finalize_single(other_job);
+        aio_context_release(ctx);
+    }
+
+    job_txn_unref(txn);
+}
+
+static int job_prepare(Job *job)
+{
+    if (job->ret == 0 && job->driver->prepare) {
+        job->ret = job->driver->prepare(job);
+    }
+    return job->ret;
+}
+
+static int job_needs_finalize(Job *job)
+{
+    return !job->auto_finalize;
+}
+
+static void job_do_finalize(Job *job)
+{
+    int rc;
+    assert(job && job->txn);
+
+    /* prepare the transaction to complete */
+    rc = job_txn_apply(job->txn, job_prepare, true);
+    if (rc) {
+        job_completed_txn_abort(job);
+    } else {
+        job_txn_apply(job->txn, job_finalize_single, true);
+    }
+}
+
+void job_finalize(Job *job, Error **errp)
+{
+    assert(job && job->id);
+    if (job_apply_verb(job, JOB_VERB_FINALIZE, errp)) {
+        return;
+    }
+    job_do_finalize(job);
+}
+
+static int job_transition_to_pending(Job *job)
+{
+    job_state_transition(job, JOB_STATUS_PENDING);
+    if (!job->auto_finalize) {
+        job_event_pending(job);
+    }
+    return 0;
+}
+
+void job_completed_txn_success(Job *job)
+{
+    JobTxn *txn = job->txn;
+    Job *other_job;
+
+    job_state_transition(job, JOB_STATUS_WAITING);
+
+    /*
+     * Successful completion, see if there are other running jobs in this
+     * txn.
+     */
+    QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
+        if (!job_is_completed(other_job)) {
+            return;
+        }
+        assert(other_job->ret == 0);
+    }
+
+    job_txn_apply(txn, job_transition_to_pending, false);
+
+    /* If no jobs need manual finalization, automatically do so */
+    if (job_txn_apply(txn, job_needs_finalize, false) == 0) {
+        job_do_finalize(job);
+    }
+}
+
 void job_complete(Job *job, Error **errp)
 {
     /* Should not be reachable via external interface for internal jobs */
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index ec5d592b68..6ee31d59ad 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -125,7 +125,7 @@ static void test_single_job(int expected)
     JobTxn *txn;
     int result = -EINPROGRESS;
 
-    txn = block_job_txn_new();
+    txn = job_txn_new();
     job = test_block_job_start(1, true, expected, &result, txn);
     job_start(&job->job);
 
@@ -138,7 +138,7 @@ static void test_single_job(int expected)
     }
     g_assert_cmpint(result, ==, expected);
 
-    block_job_txn_unref(txn);
+    job_txn_unref(txn);
 }
 
 static void test_single_job_success(void)
@@ -164,7 +164,7 @@ static void test_pair_jobs(int expected1, int expected2)
     int result1 = -EINPROGRESS;
     int result2 = -EINPROGRESS;
 
-    txn = block_job_txn_new();
+    txn = job_txn_new();
     job1 = test_block_job_start(1, true, expected1, &result1, txn);
     job2 = test_block_job_start(2, true, expected2, &result2, txn);
     job_start(&job1->job);
@@ -173,7 +173,7 @@ static void test_pair_jobs(int expected1, int expected2)
     /* Release our reference now to trigger as many nice
      * use-after-free bugs as possible.
      */
-    block_job_txn_unref(txn);
+    job_txn_unref(txn);
 
     if (expected1 == -ECANCELED) {
         block_job_cancel(job1, false);
@@ -226,7 +226,7 @@ static void test_pair_jobs_fail_cancel_race(void)
     int result1 = -EINPROGRESS;
     int result2 = -EINPROGRESS;
 
-    txn = block_job_txn_new();
+    txn = job_txn_new();
     job1 = test_block_job_start(1, true, -ECANCELED, &result1, txn);
     job2 = test_block_job_start(2, false, 0, &result2, txn);
     job_start(&job1->job);
@@ -247,7 +247,7 @@ static void test_pair_jobs_fail_cancel_race(void)
     g_assert_cmpint(result1, ==, -ECANCELED);
     g_assert_cmpint(result2, ==, -ECANCELED);
 
-    block_job_txn_unref(txn);
+    job_txn_unref(txn);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index e44c608327..1e052c2e9c 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -364,7 +364,7 @@ static void test_cancel_concluded(void)
     }
     assert(job->job.status == JOB_STATUS_PENDING);
 
-    block_job_finalize(job, &error_abort);
+    job_finalize(&job->job, &error_abort);
     assert(job->job.status == JOB_STATUS_CONCLUDED);
 
     cancel_common(s);
-- 
2.13.6

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

* [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (30 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 31/42] job: Move transactions to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 20:53   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 33/42] job: Add job_yield() Kevin Wolf
                   ` (10 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves the top-level job completion and cancellation functions from
BlockJob to Job.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     | 55 -------------------------------
 include/block/blockjob_int.h | 18 ----------
 include/qemu/job.h           | 58 ++++++++++++++++++++++++++++----
 block.c                      |  2 +-
 block/backup.c               |  3 +-
 block/commit.c               |  8 ++---
 block/mirror.c               |  6 ++--
 block/replication.c          |  4 +--
 block/stream.c               |  2 +-
 blockdev.c                   |  8 ++---
 blockjob.c                   | 76 ------------------------------------------
 job.c                        | 78 +++++++++++++++++++++++++++++++++++++++++---
 qemu-img.c                   |  2 +-
 tests/test-bdrv-drain.c      |  5 ++-
 tests/test-blockjob-txn.c    | 14 ++++----
 tests/test-blockjob.c        | 21 ++++++------
 block/trace-events           |  3 --
 trace-events                 |  1 +
 18 files changed, 162 insertions(+), 202 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index e8e9e5f370..7a0d95e789 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -141,15 +141,6 @@ void block_job_remove_all_bdrv(BlockJob *job);
 void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 
 /**
- * block_job_cancel:
- * @job: The job to be canceled.
- * @force: Quit a job without waiting for data to be in sync.
- *
- * Asynchronously cancel the specified job.
- */
-void block_job_cancel(BlockJob *job, bool force);
-
-/**
  * block_job_dismiss:
  * @job: The job to be dismissed.
  * @errp: Error object.
@@ -186,52 +177,6 @@ void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);
 BlockJobInfo *block_job_query(BlockJob *job, Error **errp);
 
 /**
- * block_job_user_cancel:
- * @job: The job to be cancelled.
- * @force: Quit a job without waiting for data to be in sync.
- *
- * Cancels the specified job, but may refuse to do so if the
- * operation isn't currently meaningful.
- */
-void block_job_user_cancel(BlockJob *job, bool force, Error **errp);
-
-/**
- * block_job_cancel_sync:
- * @job: The job to be canceled.
- *
- * Synchronously cancel the job.  The completion callback is called
- * before the function returns.  The job may actually complete
- * instead of canceling itself; the circumstances under which this
- * happens depend on the kind of job that is active.
- *
- * Returns the return value from the job if the job actually completed
- * during the call, or -ECANCELED if it was canceled.
- */
-int block_job_cancel_sync(BlockJob *job);
-
-/**
- * block_job_cancel_sync_all:
- *
- * Synchronously cancels all jobs using block_job_cancel_sync().
- */
-void block_job_cancel_sync_all(void);
-
-/**
- * block_job_complete_sync:
- * @job: The job to be completed.
- * @errp: Error object which may be set by block_job_complete(); this is not
- *        necessarily set on every error, the job return value has to be
- *        checked as well.
- *
- * Synchronously complete the job.  The completion callback is called before the
- * function returns, unless it is NULL (which is permissible when using this
- * function).
- *
- * Returns the return value from the job.
- */
-int block_job_complete_sync(BlockJob *job, Error **errp);
-
-/**
  * block_job_iostatus_reset:
  * @job: The job whose I/O status should be reset.
  *
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 29a28020ac..7df07b20cf 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -124,24 +124,6 @@ void block_job_yield(BlockJob *job);
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
 
 /**
- * block_job_completed:
- * @job: The job being completed.
- * @ret: The status code.
- *
- * Call the completion function that was registered at creation time, and
- * free @job.
- */
-void block_job_completed(BlockJob *job, int ret);
-
-/**
- * block_job_enter:
- * @job: The job to enter.
- *
- * Continue the specified job by entering the coroutine.
- */
-void block_job_enter(BlockJob *job);
-
-/**
  * block_job_event_ready:
  * @job: The job which is now ready to be completed.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 84a9eb7980..a7e253a6fb 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -416,8 +416,59 @@ int job_apply_verb(Job *job, JobVerb bv, Error **errp);
 /** The @job could not be started, free it. */
 void job_early_fail(Job *job);
 
+/**
+ * @job: The job being completed.
+ * @ret: The status code.
+ *
+ * Marks @job as completed. If @ret is non-zero, the job transaction it is part
+ * of is aborted. If @ret is zero, the job moves into the WAITING state. If it
+ * is the last job to complete in its transaction, all jobs in the transaction
+ * move from WAITING to PENDING.
+ */
+void job_completed(Job *job, int ret);
+
 /** Asynchronously complete the specified @job. */
-void job_complete(Job *job, Error **errp);;
+void job_complete(Job *job, Error **errp);
+
+/**
+ * Asynchronously cancel the specified @job. If @force is true, the job should
+ * be cancelled immediately without waiting for a consistent state.
+ */
+void job_cancel(Job *job, bool force);
+
+/**
+ * Cancels the specified job like job_cancel(), but may refuse to do so if the
+ * operation isn't meaningful in the current state of the job.
+ */
+void job_user_cancel(Job *job, bool force, Error **errp);
+
+/**
+ * Synchronously cancel the @job.  The completion callback is called
+ * before the function returns.  The job may actually complete
+ * instead of canceling itself; the circumstances under which this
+ * happens depend on the kind of job that is active.
+ *
+ * Returns the return value from the job if the job actually completed
+ * during the call, or -ECANCELED if it was canceled.
+ */
+int job_cancel_sync(Job *job);
+
+/** Synchronously cancels all jobs using job_cancel_sync(). */
+void job_cancel_sync_all(void);
+
+/**
+ * @job: The job to be completed.
+ * @errp: Error object which may be set by job_complete(); this is not
+ *        necessarily set on every error, the job return value has to be
+ *        checked as well.
+ *
+ * Synchronously complete the job.  The completion callback is called before the
+ * function returns, unless it is NULL (which is permissible when using this
+ * function).
+ *
+ * Returns the return value from the job.
+ */
+int job_complete_sync(Job *job, Error **errp);
 
 /**
  * For a @job that has finished its work and is pending awaiting explicit
@@ -459,11 +510,6 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)
 void job_state_transition(Job *job, JobStatus s1);
 void coroutine_fn job_do_yield(Job *job, uint64_t ns);
 bool job_should_pause(Job *job);
-bool job_started(Job *job);
 void job_do_dismiss(Job *job);
-void job_update_rc(Job *job);
-void job_cancel_async(Job *job, bool force);
-void job_completed_txn_abort(Job *job);
-void job_completed_txn_success(Job *job);
 
 #endif
diff --git a/block.c b/block.c
index 676e57f562..7a149bfea9 100644
--- a/block.c
+++ b/block.c
@@ -3362,7 +3362,7 @@ static void bdrv_close(BlockDriverState *bs)
 
 void bdrv_close_all(void)
 {
-    block_job_cancel_sync_all();
+    job_cancel_sync_all();
     nbd_export_close_all();
 
     /* Drop references from requests still in flight, such as canceled block
diff --git a/block/backup.c b/block/backup.c
index 6172f90c90..b13f91d8a7 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -319,10 +319,9 @@ typedef struct {
 
 static void backup_complete(Job *job, void *opaque)
 {
-    BlockJob *bjob = container_of(job, BlockJob, job);
     BackupCompleteData *data = opaque;
 
-    block_job_completed(bjob, data->ret);
+    job_completed(job, data->ret);
     g_free(data);
 }
 
diff --git a/block/commit.c b/block/commit.c
index 02a8af9127..56c3810bad 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -112,12 +112,12 @@ static void commit_complete(Job *job, void *opaque)
     blk_unref(s->top);
 
     /* If there is more than one reference to the job (e.g. if called from
-     * block_job_finish_sync()), block_job_completed() won't free it and
-     * therefore the blockers on the intermediate nodes remain. This would
-     * cause bdrv_set_backing_hd() to fail. */
+     * block_job_finish_sync()), job_completed() won't free it and therefore
+     * the blockers on the intermediate nodes remain. This would cause
+     * bdrv_set_backing_hd() to fail. */
     block_job_remove_all_bdrv(bjob);
 
-    block_job_completed(&s->common, ret);
+    job_completed(job, ret);
     g_free(data);
 
     /* If bdrv_drop_intermediate() didn't already do that, remove the commit
diff --git a/block/mirror.c b/block/mirror.c
index f4e3576a17..2f33ee42fc 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -498,7 +498,7 @@ static void mirror_exit(Job *job, void *opaque)
     bdrv_release_dirty_bitmap(src, s->dirty_bitmap);
 
     /* Make sure that the source BDS doesn't go away before we called
-     * block_job_completed(). */
+     * job_completed(). */
     bdrv_ref(src);
     bdrv_ref(mirror_top_bs);
     bdrv_ref(target_bs);
@@ -581,7 +581,7 @@ static void mirror_exit(Job *job, void *opaque)
     blk_set_perm(bjob->blk, 0, BLK_PERM_ALL, &error_abort);
     blk_insert_bs(bjob->blk, mirror_top_bs, &error_abort);
 
-    block_job_completed(&s->common, data->ret);
+    job_completed(job, data->ret);
 
     g_free(data);
     bdrv_drained_end(src);
@@ -954,7 +954,7 @@ static void mirror_complete(Job *job, Error **errp)
     }
 
     s->should_complete = true;
-    block_job_enter(&s->common);
+    job_enter(job);
 }
 
 static void mirror_pause(Job *job)
diff --git a/block/replication.c b/block/replication.c
index ff2e2028af..826db7b304 100644
--- a/block/replication.c
+++ b/block/replication.c
@@ -145,7 +145,7 @@ static void replication_close(BlockDriverState *bs)
         replication_stop(s->rs, false, NULL);
     }
     if (s->stage == BLOCK_REPLICATION_FAILOVER) {
-        block_job_cancel_sync(s->active_disk->bs->job);
+        job_cancel_sync(&s->active_disk->bs->job->job);
     }
 
     if (s->mode == REPLICATION_MODE_SECONDARY) {
@@ -681,7 +681,7 @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp)
          * disk, secondary disk in backup_job_completed().
          */
         if (s->secondary_disk->bs->job) {
-            block_job_cancel_sync(s->secondary_disk->bs->job);
+            job_cancel_sync(&s->secondary_disk->bs->job->job);
         }
 
         if (!failover) {
diff --git a/block/stream.c b/block/stream.c
index b996278ab3..8546c412cd 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -93,7 +93,7 @@ out:
     }
 
     g_free(s->backing_file_str);
-    block_job_completed(&s->common, data->ret);
+    job_completed(job, data->ret);
     g_free(data);
 }
 
diff --git a/blockdev.c b/blockdev.c
index 87a23bed6f..31319a6d5a 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -150,7 +150,7 @@ void blockdev_mark_auto_del(BlockBackend *blk)
         aio_context_acquire(aio_context);
 
         if (bs->job) {
-            block_job_cancel(bs->job, false);
+            job_cancel(&bs->job->job, false);
         }
 
         aio_context_release(aio_context);
@@ -1925,7 +1925,7 @@ static void drive_backup_abort(BlkActionState *common)
         aio_context = bdrv_get_aio_context(state->bs);
         aio_context_acquire(aio_context);
 
-        block_job_cancel_sync(state->job);
+        job_cancel_sync(&state->job->job);
 
         aio_context_release(aio_context);
     }
@@ -2023,7 +2023,7 @@ static void blockdev_backup_abort(BlkActionState *common)
         aio_context = bdrv_get_aio_context(state->bs);
         aio_context_acquire(aio_context);
 
-        block_job_cancel_sync(state->job);
+        job_cancel_sync(&state->job->job);
 
         aio_context_release(aio_context);
     }
@@ -3851,7 +3851,7 @@ void qmp_block_job_cancel(const char *device,
     }
 
     trace_qmp_block_job_cancel(job);
-    block_job_user_cancel(job, force, errp);
+    job_user_cancel(&job->job, force, errp);
 out:
     aio_context_release(aio_context);
 }
diff --git a/blockjob.c b/blockjob.c
index fd096e0f0c..c409788723 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -256,63 +256,6 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
     *jobptr = NULL;
 }
 
-void block_job_cancel(BlockJob *job, bool force)
-{
-    if (job->job.status == JOB_STATUS_CONCLUDED) {
-        job_do_dismiss(&job->job);
-        return;
-    }
-    job_cancel_async(&job->job, force);
-    if (!job_started(&job->job)) {
-        block_job_completed(job, -ECANCELED);
-    } else if (job->job.deferred_to_main_loop) {
-        job_completed_txn_abort(&job->job);
-    } else {
-        block_job_enter(job);
-    }
-}
-
-void block_job_user_cancel(BlockJob *job, bool force, Error **errp)
-{
-    if (job_apply_verb(&job->job, JOB_VERB_CANCEL, errp)) {
-        return;
-    }
-    block_job_cancel(job, force);
-}
-
-/* A wrapper around block_job_cancel() taking an Error ** parameter so it may be
- * used with job_finish_sync() without the need for (rather nasty) function
- * pointer casts there. */
-static void block_job_cancel_err(Job *job, Error **errp)
-{
-    BlockJob *bjob = container_of(job, BlockJob, job);
-    assert(is_block_job(job));
-    block_job_cancel(bjob, false);
-}
-
-int block_job_cancel_sync(BlockJob *job)
-{
-    return job_finish_sync(&job->job, &block_job_cancel_err, NULL);
-}
-
-void block_job_cancel_sync_all(void)
-{
-    BlockJob *job;
-    AioContext *aio_context;
-
-    while ((job = block_job_next(NULL))) {
-        aio_context = blk_get_aio_context(job->blk);
-        aio_context_acquire(aio_context);
-        block_job_cancel_sync(job);
-        aio_context_release(aio_context);
-    }
-}
-
-int block_job_complete_sync(BlockJob *job, Error **errp)
-{
-    return job_finish_sync(&job->job, job_complete, errp);
-}
-
 void block_job_progress_update(BlockJob *job, uint64_t done)
 {
     job->offset += done;
@@ -490,25 +433,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     return job;
 }
 
-void block_job_completed(BlockJob *job, int ret)
-{
-    assert(job && job->job.txn && !job_is_completed(&job->job));
-    assert(blk_bs(job->blk)->job == job);
-    job->job.ret = ret;
-    job_update_rc(&job->job);
-    trace_block_job_completed(job, ret, job->job.ret);
-    if (job->job.ret) {
-        job_completed_txn_abort(&job->job);
-    } else {
-        job_completed_txn_success(&job->job);
-    }
-}
-
-void block_job_enter(BlockJob *job)
-{
-    job_enter_cond(&job->job, NULL);
-}
-
 void block_job_yield(BlockJob *job)
 {
     assert(job->job.busy);
diff --git a/job.c b/job.c
index 2d782859ac..892affba54 100644
--- a/job.c
+++ b/job.c
@@ -221,7 +221,7 @@ bool job_is_completed(Job *job)
     return false;
 }
 
-bool job_started(Job *job)
+static bool job_started(Job *job)
 {
     return job->co;
 }
@@ -572,7 +572,7 @@ static void job_conclude(Job *job)
     }
 }
 
-void job_update_rc(Job *job)
+static void job_update_rc(Job *job)
 {
     if (!job->ret && job_is_cancelled(job)) {
         job->ret = -ECANCELED;
@@ -637,7 +637,7 @@ static int job_finalize_single(Job *job)
     return 0;
 }
 
-void job_cancel_async(Job *job, bool force)
+static void job_cancel_async(Job *job, bool force)
 {
     if (job->user_paused) {
         /* Do not call job_enter here, the caller will handle it.  */
@@ -653,7 +653,7 @@ void job_cancel_async(Job *job, bool force)
     job->force_cancel |= force;
 }
 
-void job_completed_txn_abort(Job *job)
+static void job_completed_txn_abort(Job *job)
 {
     AioContext *ctx;
     JobTxn *txn = job->txn;
@@ -741,7 +741,7 @@ static int job_transition_to_pending(Job *job)
     return 0;
 }
 
-void job_completed_txn_success(Job *job)
+static void job_completed_txn_success(Job *job)
 {
     JobTxn *txn = job->txn;
     Job *other_job;
@@ -767,6 +767,74 @@ void job_completed_txn_success(Job *job)
     }
 }
 
+void job_completed(Job *job, int ret)
+{
+    assert(job && job->txn && !job_is_completed(job));
+    job->ret = ret;
+    job_update_rc(job);
+    trace_job_completed(job, ret, job->ret);
+    if (job->ret) {
+        job_completed_txn_abort(job);
+    } else {
+        job_completed_txn_success(job);
+    }
+}
+
+void job_cancel(Job *job, bool force)
+{
+    if (job->status == JOB_STATUS_CONCLUDED) {
+        job_do_dismiss(job);
+        return;
+    }
+    job_cancel_async(job, force);
+    if (!job_started(job)) {
+        job_completed(job, -ECANCELED);
+    } else if (job->deferred_to_main_loop) {
+        job_completed_txn_abort(job);
+    } else {
+        job_enter(job);
+    }
+}
+
+void job_user_cancel(Job *job, bool force, Error **errp)
+{
+    if (job_apply_verb(job, JOB_VERB_CANCEL, errp)) {
+        return;
+    }
+    job_cancel(job, force);
+}
+
+/* A wrapper around job_cancel() taking an Error ** parameter so it may be
+ * used with job_finish_sync() without the need for (rather nasty) function
+ * pointer casts there. */
+static void job_cancel_err(Job *job, Error **errp)
+{
+    job_cancel(job, false);
+}
+
+int job_cancel_sync(Job *job)
+{
+    return job_finish_sync(job, &job_cancel_err, NULL);
+}
+
+void job_cancel_sync_all(void)
+{
+    Job *job;
+    AioContext *aio_context;
+
+    while ((job = job_next(NULL))) {
+        aio_context = job->aio_context;
+        aio_context_acquire(aio_context);
+        job_cancel_sync(job);
+        aio_context_release(aio_context);
+    }
+}
+
+int job_complete_sync(Job *job, Error **errp)
+{
+    return job_finish_sync(job, job_complete, errp);
+}
+
 void job_complete(Job *job, Error **errp)
 {
     /* Should not be reachable via external interface for internal jobs */
diff --git a/qemu-img.c b/qemu-img.c
index ee214269aa..176eb630a1 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -870,7 +870,7 @@ static void run_block_job(BlockJob *job, Error **errp)
     } while (!job->ready && !job_is_completed(&job->job));
 
     if (!job_is_completed(&job->job)) {
-        ret = block_job_complete_sync(job, errp);
+        ret = job_complete_sync(&job->job, errp);
     } else {
         ret = job->job.ret;
     }
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index b428aaca68..3600ffd3fb 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -498,8 +498,7 @@ typedef struct TestBlockJob {
 
 static void test_job_completed(Job *job, void *opaque)
 {
-    BlockJob *bjob = container_of(job, BlockJob, job);
-    block_job_completed(bjob, 0);
+    job_completed(job, 0);
 }
 
 static void coroutine_fn test_job_start(void *opaque)
@@ -593,7 +592,7 @@ static void test_blockjob_common(enum drain_type drain_type)
     g_assert_false(job->job.paused);
     g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
 
-    ret = block_job_complete_sync(job, &error_abort);
+    ret = job_complete_sync(&job->job, &error_abort);
     g_assert_cmpint(ret, ==, 0);
 
     blk_unref(blk_src);
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 6ee31d59ad..34ee179574 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -34,7 +34,7 @@ static void test_block_job_complete(Job *job, void *opaque)
         rc = -ECANCELED;
     }
 
-    block_job_completed(bjob, rc);
+    job_completed(job, rc);
     bdrv_unref(bs);
 }
 
@@ -130,7 +130,7 @@ static void test_single_job(int expected)
     job_start(&job->job);
 
     if (expected == -ECANCELED) {
-        block_job_cancel(job, false);
+        job_cancel(&job->job, false);
     }
 
     while (result == -EINPROGRESS) {
@@ -176,10 +176,10 @@ static void test_pair_jobs(int expected1, int expected2)
     job_txn_unref(txn);
 
     if (expected1 == -ECANCELED) {
-        block_job_cancel(job1, false);
+        job_cancel(&job1->job, false);
     }
     if (expected2 == -ECANCELED) {
-        block_job_cancel(job2, false);
+        job_cancel(&job2->job, false);
     }
 
     while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) {
@@ -232,13 +232,13 @@ static void test_pair_jobs_fail_cancel_race(void)
     job_start(&job1->job);
     job_start(&job2->job);
 
-    block_job_cancel(job1, false);
+    job_cancel(&job1->job, false);
 
     /* Now make job2 finish before the main loop kicks jobs.  This simulates
      * the race between a pending kick and another job completing.
      */
-    block_job_enter(job2);
-    block_job_enter(job2);
+    job_enter(&job2->job);
+    job_enter(&job2->job);
 
     while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) {
         aio_poll(qemu_get_aio_context(), true);
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 1e052c2e9c..46a78739aa 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -165,10 +165,9 @@ typedef struct CancelJob {
 
 static void cancel_job_completed(Job *job, void *opaque)
 {
-    BlockJob *bjob = container_of(job, BlockJob, job);
     CancelJob *s = opaque;
     s->completed = true;
-    block_job_completed(bjob, 0);
+    job_completed(job, 0);
 }
 
 static void cancel_job_complete(Job *job, Error **errp)
@@ -232,7 +231,7 @@ static void cancel_common(CancelJob *s)
     BlockBackend *blk = s->blk;
     JobStatus sts = job->job.status;
 
-    block_job_cancel_sync(job);
+    job_cancel_sync(&job->job);
     if (sts != JOB_STATUS_CREATED && sts != JOB_STATUS_CONCLUDED) {
         BlockJob *dummy = job;
         block_job_dismiss(&dummy, &error_abort);
@@ -275,7 +274,7 @@ static void test_cancel_paused(void)
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     job_user_pause(&job->job, &error_abort);
-    block_job_enter(job);
+    job_enter(&job->job);
     assert(job->job.status == JOB_STATUS_PAUSED);
 
     cancel_common(s);
@@ -292,7 +291,7 @@ static void test_cancel_ready(void)
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
-    block_job_enter(job);
+    job_enter(&job->job);
     assert(job->job.status == JOB_STATUS_READY);
 
     cancel_common(s);
@@ -309,11 +308,11 @@ static void test_cancel_standby(void)
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
-    block_job_enter(job);
+    job_enter(&job->job);
     assert(job->job.status == JOB_STATUS_READY);
 
     job_user_pause(&job->job, &error_abort);
-    block_job_enter(job);
+    job_enter(&job->job);
     assert(job->job.status == JOB_STATUS_STANDBY);
 
     cancel_common(s);
@@ -330,11 +329,11 @@ static void test_cancel_pending(void)
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
-    block_job_enter(job);
+    job_enter(&job->job);
     assert(job->job.status == JOB_STATUS_READY);
 
     job_complete(&job->job, &error_abort);
-    block_job_enter(job);
+    job_enter(&job->job);
     while (!s->completed) {
         aio_poll(qemu_get_aio_context(), true);
     }
@@ -354,11 +353,11 @@ static void test_cancel_concluded(void)
     assert(job->job.status == JOB_STATUS_RUNNING);
 
     s->should_converge = true;
-    block_job_enter(job);
+    job_enter(&job->job);
     assert(job->job.status == JOB_STATUS_READY);
 
     job_complete(&job->job, &error_abort);
-    block_job_enter(job);
+    job_enter(&job->job);
     while (!s->completed) {
         aio_poll(qemu_get_aio_context(), true);
     }
diff --git a/block/trace-events b/block/trace-events
index 93b927908a..2d59b53fd3 100644
--- a/block/trace-events
+++ b/block/trace-events
@@ -4,9 +4,6 @@
 bdrv_open_common(void *bs, const char *filename, int flags, const char *format_name) "bs %p filename \"%s\" flags 0x%x format_name \"%s\""
 bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d"
 
-# blockjob.c
-block_job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d"
-
 # block/block-backend.c
 blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
 blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
diff --git a/trace-events b/trace-events
index 2507e1327d..ef7579a285 100644
--- a/trace-events
+++ b/trace-events
@@ -107,6 +107,7 @@ gdbstub_err_checksum_incorrect(uint8_t expected, uint8_t got) "got command packe
 # job.c
 job_state_transition(void *job,  int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)"
 job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
+job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d"
 
 ### Guest events, keep at bottom
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 33/42] job: Add job_yield()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (31 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation " Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 20:59   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 34/42] job: Add job_dismiss() Kevin Wolf
                   ` (9 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves block_job_yield() to the Job layer.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob_int.h |  8 --------
 include/qemu/job.h           |  9 +++++++--
 block/backup.c               |  2 +-
 block/mirror.c               |  2 +-
 blockjob.c                   | 16 ----------------
 job.c                        | 20 ++++++++++++++++++--
 tests/test-blockjob-txn.c    |  2 +-
 7 files changed, 28 insertions(+), 31 deletions(-)

diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 7df07b20cf..806ac64d87 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -108,14 +108,6 @@ void block_job_user_resume(Job *job);
 void block_job_drain(Job *job);
 
 /**
- * block_job_yield:
- * @job: The job that calls the function.
- *
- * Yield the block job coroutine.
- */
-void block_job_yield(BlockJob *job);
-
-/**
  * block_job_ratelimit_get_delay:
  *
  * Calculate and return delay for the next request in ns. See the documentation
diff --git a/include/qemu/job.h b/include/qemu/job.h
index a7e253a6fb..5ec3e3c01d 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -339,6 +339,13 @@ void coroutine_fn job_pause_point(Job *job);
 
 /**
  * @job: The job that calls the function.
+ *
+ * Yield the job coroutine.
+ */
+void job_yield(Job *job);
+
+/**
+ * @job: The job that calls the function.
  * @ns: How many nanoseconds to stop for.
  *
  * Put the job to sleep (assuming that it wasn't canceled) for @ns
@@ -508,8 +515,6 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)
 
 /* TODO To be removed from the public interface */
 void job_state_transition(Job *job, JobStatus s1);
-void coroutine_fn job_do_yield(Job *job, uint64_t ns);
-bool job_should_pause(Job *job);
 void job_do_dismiss(Job *job);
 
 #endif
diff --git a/block/backup.c b/block/backup.c
index b13f91d8a7..6f4f3df229 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -444,7 +444,7 @@ static void coroutine_fn backup_run(void *opaque)
         while (!job_is_cancelled(&job->common.job)) {
             /* Yield until the job is cancelled.  We just let our before_write
              * notify callback service CoW requests. */
-            block_job_yield(&job->common);
+            job_yield(&job->common.job);
         }
     } else if (job->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
         ret = backup_run_incremental(job);
diff --git a/block/mirror.c b/block/mirror.c
index 2f33ee42fc..67a9482c16 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -731,7 +731,7 @@ static void coroutine_fn mirror_run(void *opaque)
         block_job_event_ready(&s->common);
         s->synced = true;
         while (!job_is_cancelled(&s->common.job) && !s->should_complete) {
-            block_job_yield(&s->common);
+            job_yield(&s->common.job);
         }
         s->common.job.cancelled = false;
         goto immediate_exit;
diff --git a/blockjob.c b/blockjob.c
index c409788723..e8d7e85d6b 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -433,22 +433,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     return job;
 }
 
-void block_job_yield(BlockJob *job)
-{
-    assert(job->job.busy);
-
-    /* Check cancellation *before* setting busy = false, too!  */
-    if (job_is_cancelled(&job->job)) {
-        return;
-    }
-
-    if (!job_should_pause(&job->job)) {
-        job_do_yield(&job->job, -1);
-    }
-
-    job_pause_point(&job->job);
-}
-
 void block_job_iostatus_reset(BlockJob *job)
 {
     if (job->iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
diff --git a/job.c b/job.c
index 892affba54..1627118c26 100644
--- a/job.c
+++ b/job.c
@@ -226,7 +226,7 @@ static bool job_started(Job *job)
     return job->co;
 }
 
-bool job_should_pause(Job *job)
+static bool job_should_pause(Job *job)
 {
     return job->pause_count > 0;
 }
@@ -397,7 +397,7 @@ void job_enter(Job *job)
  *
  * If @ns is (uint64_t) -1, no timer is scheduled and block_job_enter() must be
  * called explicitly. */
-void coroutine_fn job_do_yield(Job *job, uint64_t ns)
+static void coroutine_fn job_do_yield(Job *job, uint64_t ns)
 {
     job_lock();
     if (ns != -1) {
@@ -442,6 +442,22 @@ void coroutine_fn job_pause_point(Job *job)
     }
 }
 
+void job_yield(Job *job)
+{
+    assert(job->busy);
+
+    /* Check cancellation *before* setting busy = false, too!  */
+    if (job_is_cancelled(job)) {
+        return;
+    }
+
+    if (!job_should_pause(job)) {
+        job_do_yield(job, -1);
+    }
+
+    job_pause_point(job);
+}
+
 void coroutine_fn job_sleep_ns(Job *job, int64_t ns)
 {
     assert(job->busy);
diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
index 34ee179574..fce836639a 100644
--- a/tests/test-blockjob-txn.c
+++ b/tests/test-blockjob-txn.c
@@ -47,7 +47,7 @@ static void coroutine_fn test_block_job_run(void *opaque)
         if (s->use_timer) {
             job_sleep_ns(&job->job, 0);
         } else {
-            block_job_yield(job);
+            job_yield(&job->job);
         }
 
         if (job_is_cancelled(&job->job)) {
-- 
2.13.6

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

* [Qemu-devel] [PATCH 34/42] job: Add job_dismiss()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (32 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 33/42] job: Add job_yield() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 21:06   ` Max Reitz
  2018-05-14 22:26   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 35/42] job: Add job_is_ready() Kevin Wolf
                   ` (8 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This moves block_job_dismiss() to the Job layer.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h |  9 ---------
 include/qemu/job.h       |  7 ++++++-
 blockdev.c               |  8 +++++---
 blockjob.c               | 13 -------------
 job.c                    | 15 ++++++++++++++-
 tests/test-blockjob.c    |  4 ++--
 6 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 7a0d95e789..ba08bcf7c0 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -141,15 +141,6 @@ void block_job_remove_all_bdrv(BlockJob *job);
 void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 
 /**
- * block_job_dismiss:
- * @job: The job to be dismissed.
- * @errp: Error object.
- *
- * Remove a concluded job from the query list.
- */
-void block_job_dismiss(BlockJob **job, Error **errp);
-
-/**
  * block_job_progress_update:
  * @job: The job that has made progress
  * @done: How much progress the job made
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 5ec3e3c01d..f3acebbc17 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -487,6 +487,12 @@ int job_complete_sync(Job *job, Error **errp);
  */
 void job_finalize(Job *job, Error **errp);
 
+/**
+ * Remove the concluded @job from the query list and resets the passed pointer
+ * to %NULL. Returns an error if the job is not actually concluded.
+ */
+void job_dismiss(Job **job, Error **errp);
+
 typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
 
 /**
@@ -515,6 +521,5 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)
 
 /* TODO To be removed from the public interface */
 void job_state_transition(Job *job, JobStatus s1);
-void job_do_dismiss(Job *job);
 
 #endif
diff --git a/blockdev.c b/blockdev.c
index 31319a6d5a..15753d9719 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3915,14 +3915,16 @@ void qmp_block_job_finalize(const char *id, Error **errp)
 void qmp_block_job_dismiss(const char *id, Error **errp)
 {
     AioContext *aio_context;
-    BlockJob *job = find_block_job(id, &aio_context, errp);
+    BlockJob *bjob = find_block_job(id, &aio_context, errp);
+    Job *job;
 
-    if (!job) {
+    if (!bjob) {
         return;
     }
 
     trace_qmp_block_job_dismiss(job);
-    block_job_dismiss(&job, errp);
+    job = &bjob->job;
+    job_dismiss(&job, errp);
     aio_context_release(aio_context);
 }
 
diff --git a/blockjob.c b/blockjob.c
index e8d7e85d6b..5ab22596bf 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -243,19 +243,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
     return ratelimit_calculate_delay(&job->limit, n);
 }
 
-void block_job_dismiss(BlockJob **jobptr, Error **errp)
-{
-    BlockJob *job = *jobptr;
-    /* similarly to _complete, this is QMP-interface only. */
-    assert(job->job.id);
-    if (job_apply_verb(&job->job, JOB_VERB_DISMISS, errp)) {
-        return;
-    }
-
-    job_do_dismiss(&job->job);
-    *jobptr = NULL;
-}
-
 void block_job_progress_update(BlockJob *job, uint64_t done)
 {
     job->offset += done;
diff --git a/job.c b/job.c
index 1627118c26..744490dffe 100644
--- a/job.c
+++ b/job.c
@@ -561,7 +561,7 @@ void job_user_resume(Job *job, Error **errp)
     job_resume(job);
 }
 
-void job_do_dismiss(Job *job)
+static void job_do_dismiss(Job *job)
 {
     assert(job);
     job->busy = false;
@@ -574,6 +574,19 @@ void job_do_dismiss(Job *job)
     job_unref(job);
 }
 
+void job_dismiss(Job **jobptr, Error **errp)
+{
+    Job *job = *jobptr;
+    /* similarly to _complete, this is QMP-interface only. */
+    assert(job->id);
+    if (job_apply_verb(job, JOB_VERB_DISMISS, errp)) {
+        return;
+    }
+
+    job_do_dismiss(job);
+    *jobptr = NULL;
+}
+
 void job_early_fail(Job *job)
 {
     assert(job->status == JOB_STATUS_CREATED);
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 46a78739aa..7131cabb16 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -233,8 +233,8 @@ static void cancel_common(CancelJob *s)
 
     job_cancel_sync(&job->job);
     if (sts != JOB_STATUS_CREATED && sts != JOB_STATUS_CONCLUDED) {
-        BlockJob *dummy = job;
-        block_job_dismiss(&dummy, &error_abort);
+        Job *dummy = &job->job;
+        job_dismiss(&dummy, &error_abort);
     }
     assert(job->job.status == JOB_STATUS_NULL);
     job_unref(&job->job);
-- 
2.13.6

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

* [Qemu-devel] [PATCH 35/42] job: Add job_is_ready()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (33 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 34/42] job: Add job_dismiss() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 21:11   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 36/42] job: Add job_transition_to_ready() Kevin Wolf
                   ` (7 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Instead of having a 'bool ready' in BlockJob, add a function that
derives its value from the job status.

At the same time, this fixes the behaviour to match what the QAPI
documentation promises for query-block-job: 'true if the job may be
completed'. When the ready flag was introduced in commit ef6dbf1e46e,
the flag never had to be reset to match the description because after
being ready, the jobs would immediately complete and disappear.

Job transactions and manual job finalisation were introduced only later.
With these changes, jobs may stay around even after having completed
(and they are not ready to be completed a second time), however their
patches forgot to reset the ready flag.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h |  5 -----
 include/qemu/job.h       |  3 +++
 blockjob.c               |  3 +--
 job.c                    | 22 ++++++++++++++++++++++
 qemu-img.c               |  2 +-
 tests/test-blockjob.c    |  2 +-
 6 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index ba08bcf7c0..932a12d1a6 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -49,11 +49,6 @@ typedef struct BlockJob {
     /** The block device on which the job is operating.  */
     BlockBackend *blk;
 
-    /**
-     * Set to true when the job is ready to be completed.
-     */
-    bool ready;
-
     /** Status that is published by the query-block-jobs QMP API */
     BlockDeviceIoStatus iostatus;
 
diff --git a/include/qemu/job.h b/include/qemu/job.h
index f3acebbc17..fb81cc7c09 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -367,6 +367,9 @@ bool job_is_cancelled(Job *job);
 /** Returns whether the job is in a completed state. */
 bool job_is_completed(Job *job);
 
+/** Returns whether the job is ready to be completed. */
+bool job_is_ready(Job *job);
+
 /**
  * Request @job to pause at the next pause point. Must be paired with
  * job_resume(). If the job is supposed to be resumed by user action, call
diff --git a/blockjob.c b/blockjob.c
index 5ab22596bf..0512b41901 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -270,7 +270,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info->offset    = job->offset;
     info->speed     = job->speed;
     info->io_status = job->iostatus;
-    info->ready     = job->ready;
+    info->ready     = job_is_ready(&job->job),
     info->status    = job->job.status;
     info->auto_finalize = job->job.auto_finalize;
     info->auto_dismiss  = job->job.auto_dismiss;
@@ -438,7 +438,6 @@ void block_job_user_resume(Job *job)
 void block_job_event_ready(BlockJob *job)
 {
     job_state_transition(&job->job, JOB_STATUS_READY);
-    job->ready = true;
 
     if (block_job_is_internal(job)) {
         return;
diff --git a/job.c b/job.c
index 744490dffe..a500e8d1fd 100644
--- a/job.c
+++ b/job.c
@@ -199,6 +199,28 @@ bool job_is_cancelled(Job *job)
     return job->cancelled;
 }
 
+bool job_is_ready(Job *job)
+{
+    switch (job->status) {
+    case JOB_STATUS_UNDEFINED:
+    case JOB_STATUS_CREATED:
+    case JOB_STATUS_RUNNING:
+    case JOB_STATUS_PAUSED:
+    case JOB_STATUS_WAITING:
+    case JOB_STATUS_PENDING:
+    case JOB_STATUS_ABORTING:
+    case JOB_STATUS_CONCLUDED:
+    case JOB_STATUS_NULL:
+        return false;
+    case JOB_STATUS_READY:
+    case JOB_STATUS_STANDBY:
+        return true;
+    default:
+        g_assert_not_reached();
+    }
+    return false;
+}
+
 bool job_is_completed(Job *job)
 {
     switch (job->status) {
diff --git a/qemu-img.c b/qemu-img.c
index 176eb630a1..6361d4edf5 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -867,7 +867,7 @@ static void run_block_job(BlockJob *job, Error **errp)
         aio_poll(aio_context, true);
         qemu_progress_print(job->len ?
                             ((float)job->offset / job->len * 100.f) : 0.0f, 0);
-    } while (!job->ready && !job_is_completed(&job->job));
+    } while (!job_is_ready(&job->job) && !job_is_completed(&job->job));
 
     if (!job_is_completed(&job->job)) {
         ret = job_complete_sync(&job->job, errp);
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 7131cabb16..8180d03a5f 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -185,7 +185,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
             goto defer;
         }
 
-        if (!s->common.ready && s->should_converge) {
+        if (!job_is_ready(&s->common.job) && s->should_converge) {
             block_job_event_ready(&s->common);
         }
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 36/42] job: Add job_transition_to_ready()
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (34 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 35/42] job: Add job_is_ready() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 21:22   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job Kevin Wolf
                   ` (6 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

The transition to the READY state was still performed in the BlockJob
layer, in the same function that sent the BLOCK_JOB_READY QMP event.

This patch brings the state transition to the Job layer and implements
the QMP event using a notifier called from the Job layer, like we
already do for other events related to state transitions.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h     |  3 +++
 include/block/blockjob_int.h |  8 --------
 include/qemu/job.h           |  9 ++++++---
 block/mirror.c               |  6 +++---
 blockjob.c                   | 36 +++++++++++++++++++-----------------
 job.c                        | 16 +++++++++++++---
 tests/test-bdrv-drain.c      |  2 +-
 tests/test-blockjob.c        |  2 +-
 8 files changed, 46 insertions(+), 36 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index 932a12d1a6..d2f0d81dfa 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -76,6 +76,9 @@ typedef struct BlockJob {
     /** Called when the job transitions to PENDING */
     Notifier pending_notifier;
 
+    /** Called when the job transitions to READY */
+    Notifier ready_notifier;
+
     /** BlockDriverStates that are involved in this block job */
     GSList *nodes;
 } BlockJob;
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 806ac64d87..5cd50c6639 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -116,14 +116,6 @@ void block_job_drain(Job *job);
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
 
 /**
- * block_job_event_ready:
- * @job: The job which is now ready to be completed.
- *
- * Send a BLOCK_JOB_READY event for the specified job.
- */
-void block_job_event_ready(BlockJob *job);
-
-/**
  * block_job_error_action:
  * @job: The job to signal an error for.
  * @on_err: The error action setting.
diff --git a/include/qemu/job.h b/include/qemu/job.h
index fb81cc7c09..20b48926d9 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -132,6 +132,9 @@ typedef struct Job {
     /** Notifiers called when the job transitions to PENDING */
     NotifierList on_pending;
 
+    /** Notifiers called when the job transitions to READY */
+    NotifierList on_ready;
+
     /** Element of the list of jobs */
     QLIST_ENTRY(Job) job_list;
 
@@ -426,6 +429,9 @@ int job_apply_verb(Job *job, JobVerb bv, Error **errp);
 /** The @job could not be started, free it. */
 void job_early_fail(Job *job);
 
+/** Moves the @job from RUNNING to READY */
+void job_transition_to_ready(Job *job);
+
 /**
  * @job: The job being completed.
  * @ret: The status code.
@@ -522,7 +528,4 @@ void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
  */
 int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp);
 
-/* TODO To be removed from the public interface */
-void job_state_transition(Job *job, JobStatus s1);
-
 #endif
diff --git a/block/mirror.c b/block/mirror.c
index 67a9482c16..376b08bdcd 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -727,8 +727,8 @@ static void coroutine_fn mirror_run(void *opaque)
     }
 
     if (s->bdev_length == 0) {
-        /* Report BLOCK_JOB_READY and wait for complete. */
-        block_job_event_ready(&s->common);
+        /* Transition to the READY state and wait for complete. */
+        job_transition_to_ready(&s->common.job);
         s->synced = true;
         while (!job_is_cancelled(&s->common.job) && !s->should_complete) {
             job_yield(&s->common.job);
@@ -825,7 +825,7 @@ static void coroutine_fn mirror_run(void *opaque)
                  * report completion.  This way, block-job-cancel will leave
                  * the target in a consistent state.
                  */
-                block_job_event_ready(&s->common);
+                job_transition_to_ready(&s->common.job);
                 s->synced = true;
             }
 
diff --git a/blockjob.c b/blockjob.c
index 0512b41901..27f3199a20 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -339,6 +339,22 @@ static void block_job_event_pending(Notifier *n, void *opaque)
                                       &error_abort);
 }
 
+static void block_job_event_ready(Notifier *n, void *opaque)
+{
+    BlockJob *job = opaque;
+
+    if (block_job_is_internal(job)) {
+        return;
+    }
+
+    qapi_event_send_block_job_ready(job_type(&job->job),
+                                    job->job.id,
+                                    job->len,
+                                    job->offset,
+                                    job->speed, &error_abort);
+}
+
+
 /*
  * API for block job drivers and the block layer.  These functions are
  * declared in blockjob_int.h.
@@ -387,13 +403,14 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     job->finalize_cancelled_notifier.notify = block_job_event_cancelled;
     job->finalize_completed_notifier.notify = block_job_event_completed;
     job->pending_notifier.notify = block_job_event_pending;
+    job->ready_notifier.notify = block_job_event_ready;
 
     notifier_list_add(&job->job.on_finalize_cancelled,
                       &job->finalize_cancelled_notifier);
     notifier_list_add(&job->job.on_finalize_completed,
                       &job->finalize_completed_notifier);
-    notifier_list_add(&job->job.on_pending,
-                      &job->pending_notifier);
+    notifier_list_add(&job->job.on_pending, &job->pending_notifier);
+    notifier_list_add(&job->job.on_ready, &job->ready_notifier);
 
     error_setg(&job->blocker, "block device is in use by block job: %s",
                job_type_str(&job->job));
@@ -435,21 +452,6 @@ void block_job_user_resume(Job *job)
     block_job_iostatus_reset(bjob);
 }
 
-void block_job_event_ready(BlockJob *job)
-{
-    job_state_transition(&job->job, JOB_STATUS_READY);
-
-    if (block_job_is_internal(job)) {
-        return;
-    }
-
-    qapi_event_send_block_job_ready(job_type(&job->job),
-                                    job->job.id,
-                                    job->len,
-                                    job->offset,
-                                    job->speed, &error_abort);
-}
-
 BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
                                         int is_read, int error)
 {
diff --git a/job.c b/job.c
index a500e8d1fd..4e898b5c5e 100644
--- a/job.c
+++ b/job.c
@@ -157,9 +157,7 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)
     return rc;
 }
 
-
-/* TODO Make static once the whole state machine is in job.c */
-void job_state_transition(Job *job, JobStatus s1)
+static void job_state_transition(Job *job, JobStatus s1)
 {
     JobStatus s0 = job->status;
     assert(s1 >= 0 && s1 <= JOB_STATUS__MAX);
@@ -321,6 +319,7 @@ void *job_create(const char *job_id, const JobDriver *driver, JobTxn *txn,
     notifier_list_init(&job->on_finalize_cancelled);
     notifier_list_init(&job->on_finalize_completed);
     notifier_list_init(&job->on_pending);
+    notifier_list_init(&job->on_ready);
 
     job_state_transition(job, JOB_STATUS_CREATED);
     aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
@@ -381,6 +380,11 @@ static int job_event_pending(Job *job)
     return 0;
 }
 
+static void job_event_ready(Job *job)
+{
+    notifier_list_notify(&job->on_ready, job);
+}
+
 void job_enter_cond(Job *job, bool(*fn)(Job *job))
 {
     if (!job_started(job)) {
@@ -792,6 +796,12 @@ static int job_transition_to_pending(Job *job)
     return 0;
 }
 
+void job_transition_to_ready(Job *job)
+{
+    job_state_transition(job, JOB_STATUS_READY);
+    job_event_ready(job);
+}
+
 static void job_completed_txn_success(Job *job)
 {
     JobTxn *txn = job->txn;
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
index 3600ffd3fb..2cba63b881 100644
--- a/tests/test-bdrv-drain.c
+++ b/tests/test-bdrv-drain.c
@@ -505,7 +505,7 @@ static void coroutine_fn test_job_start(void *opaque)
 {
     TestBlockJob *s = opaque;
 
-    block_job_event_ready(&s->common);
+    job_transition_to_ready(&s->common.job);
     while (!s->should_complete) {
         job_sleep_ns(&s->common.job, 100000);
     }
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 8180d03a5f..e408d52351 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -186,7 +186,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
         }
 
         if (!job_is_ready(&s->common.job) && s->should_converge) {
-            block_job_event_ready(&s->common);
+            job_transition_to_ready(&s->common.job);
         }
 
         job_sleep_ns(&s->common.job, 100000);
-- 
2.13.6

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

* [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (35 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 36/42] job: Add job_transition_to_ready() Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 21:33   ` Max Reitz
  2018-05-16 21:23   ` Eric Blake
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event Kevin Wolf
                   ` (5 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

BlockJob has fields .offset and .len, which are actually misnomers today
because they are no longer tied to block device sizes, but just progress
counters. As such they make a lot of sense in generic Jobs.

This patch moves the fields to Job and renames them to .progress_current
and .progress_total to describe their function better.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 include/block/blockjob.h | 25 -------------------------
 include/qemu/job.h       | 27 +++++++++++++++++++++++++++
 block/backup.c           |  8 ++++----
 block/commit.c           |  4 ++--
 block/mirror.c           |  4 ++--
 block/stream.c           |  4 ++--
 blockjob.c               | 26 ++++++++------------------
 job.c                    | 10 ++++++++++
 qemu-img.c               |  8 ++++++--
 9 files changed, 61 insertions(+), 55 deletions(-)

diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index d2f0d81dfa..af34b97b4b 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -52,12 +52,6 @@ typedef struct BlockJob {
     /** Status that is published by the query-block-jobs QMP API */
     BlockDeviceIoStatus iostatus;
 
-    /** Offset that is published by the query-block-jobs QMP API */
-    int64_t offset;
-
-    /** Length that is published by the query-block-jobs QMP API */
-    int64_t len;
-
     /** Speed that was set with @block_job_set_speed.  */
     int64_t speed;
 
@@ -139,25 +133,6 @@ void block_job_remove_all_bdrv(BlockJob *job);
 void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
 
 /**
- * block_job_progress_update:
- * @job: The job that has made progress
- * @done: How much progress the job made
- *
- * Updates the progress counter of the job.
- */
-void block_job_progress_update(BlockJob *job, uint64_t done);
-
-/**
- * block_job_progress_set_remaining:
- * @job: The job whose expected progress end value is set
- * @remaining: Expected end value of the progress counter of the job
- *
- * Sets the expected end value of the progress counter of a job so that a
- * completion percentage can be calculated when the progress is updated.
- */
-void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);
-
-/**
  * block_job_query:
  * @job: The job to get information about.
  *
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 20b48926d9..9e61663f3a 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -114,6 +114,16 @@ typedef struct Job {
     /** True if this job should automatically dismiss itself */
     bool auto_dismiss;
 
+    /**
+     * Current progress. The unit is arbitrary as long as the ratio between
+     * progress_current and progress_total represents the estimated percentage
+     * of work already done.
+     */
+    int64_t progress_current;
+
+    /** Estimated progress_current value at the completion of the job */
+    int64_t progress_total;
+
     /** ret code passed to block_job_completed. */
     int ret;
 
@@ -304,6 +314,23 @@ void job_ref(Job *job);
  */
 void job_unref(Job *job);
 
+/**
+ * @job: The job that has made progress
+ * @done: How much progress the job made
+ *
+ * Updates the progress counter of the job.
+ */
+void job_progress_update(Job *job, uint64_t done);
+
+/**
+ * @job: The job whose expected progress end value is set
+ * @remaining: Expected end value of the progress counter of the job
+ *
+ * Sets the expected end value of the progress counter of a job so that a
+ * completion percentage can be calculated when the progress is updated.
+ */
+void job_progress_set_remaining(Job *job, uint64_t remaining);
+
 /** To be called when a cancelled job is finalised. */
 void job_event_cancelled(Job *job);
 
diff --git a/block/backup.c b/block/backup.c
index 6f4f3df229..4e228e959b 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -160,7 +160,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
          * offset field is an opaque progress value, it is not a disk offset.
          */
         job->bytes_read += n;
-        block_job_progress_update(&job->common, n);
+        job_progress_update(&job->common.job, n);
     }
 
 out:
@@ -406,8 +406,8 @@ static void backup_incremental_init_copy_bitmap(BackupBlockJob *job)
         bdrv_set_dirty_iter(dbi, next_cluster * job->cluster_size);
     }
 
-    /* TODO block_job_progress_set_remaining() would make more sense */
-    block_job_progress_update(&job->common,
+    /* TODO job_progress_set_remaining() would make more sense */
+    job_progress_update(&job->common.job,
         job->len - hbitmap_count(job->copy_bitmap) * job->cluster_size);
 
     bdrv_dirty_iter_free(dbi);
@@ -425,7 +425,7 @@ static void coroutine_fn backup_run(void *opaque)
     qemu_co_rwlock_init(&job->flush_rwlock);
 
     nb_clusters = DIV_ROUND_UP(job->len, job->cluster_size);
-    block_job_progress_set_remaining(&job->common, job->len);
+    job_progress_set_remaining(&job->common.job, job->len);
 
     job->copy_bitmap = hbitmap_alloc(nb_clusters, 0);
     if (job->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
diff --git a/block/commit.c b/block/commit.c
index 56c3810bad..4b48c25750 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -150,7 +150,7 @@ static void coroutine_fn commit_run(void *opaque)
     if (len < 0) {
         goto out;
     }
-    block_job_progress_set_remaining(&s->common, len);
+    job_progress_set_remaining(&s->common.job, len);
 
     ret = base_len = blk_getlength(s->base);
     if (base_len < 0) {
@@ -196,7 +196,7 @@ static void coroutine_fn commit_run(void *opaque)
             }
         }
         /* Publish progress */
-        block_job_progress_update(&s->common, n);
+        job_progress_update(&s->common.job, n);
 
         if (copy) {
             delay_ns = block_job_ratelimit_get_delay(&s->common, n);
diff --git a/block/mirror.c b/block/mirror.c
index 376b08bdcd..9583870425 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -119,7 +119,7 @@ static void mirror_iteration_done(MirrorOp *op, int ret)
             bitmap_set(s->cow_bitmap, chunk_num, nb_chunks);
         }
         if (!s->initial_zeroing_ongoing) {
-            block_job_progress_update(&s->common, op->bytes);
+            job_progress_update(&s->common.job, op->bytes);
         }
     }
     qemu_iovec_destroy(&op->qiov);
@@ -792,7 +792,7 @@ static void coroutine_fn mirror_run(void *opaque)
         /* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
          * the number of bytes currently being processed; together those are
          * the current total operation length */
-        block_job_progress_set_remaining(&s->common, s->bytes_in_flight + cnt);
+        job_progress_set_remaining(&s->common.job, s->bytes_in_flight + cnt);
 
         /* Note that even when no rate limit is applied we need to yield
          * periodically with no pending I/O so that bdrv_drain_all() returns.
diff --git a/block/stream.c b/block/stream.c
index 8546c412cd..a5d6e0cf8a 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -121,7 +121,7 @@ static void coroutine_fn stream_run(void *opaque)
         ret = len;
         goto out;
     }
-    block_job_progress_set_remaining(&s->common, len);
+    job_progress_set_remaining(&s->common.job, len);
 
     buf = qemu_blockalign(bs, STREAM_BUFFER_SIZE);
 
@@ -184,7 +184,7 @@ static void coroutine_fn stream_run(void *opaque)
         ret = 0;
 
         /* Publish progress */
-        block_job_progress_update(&s->common, n);
+        job_progress_update(&s->common.job, n);
         if (copy) {
             delay_ns = block_job_ratelimit_get_delay(&s->common, n);
         } else {
diff --git a/blockjob.c b/blockjob.c
index 27f3199a20..26d9017a61 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -243,16 +243,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
     return ratelimit_calculate_delay(&job->limit, n);
 }
 
-void block_job_progress_update(BlockJob *job, uint64_t done)
-{
-    job->offset += done;
-}
-
-void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining)
-{
-    job->len = job->offset + remaining;
-}
-
 BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
 {
     BlockJobInfo *info;
@@ -264,10 +254,10 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info = g_new0(BlockJobInfo, 1);
     info->type      = g_strdup(job_type_str(&job->job));
     info->device    = g_strdup(job->job.id);
-    info->len       = job->len;
     info->busy      = atomic_read(&job->job.busy);
     info->paused    = job->job.pause_count > 0;
-    info->offset    = job->offset;
+    info->offset    = job->job.progress_current;
+    info->len       = job->job.progress_total;
     info->speed     = job->speed;
     info->io_status = job->iostatus;
     info->ready     = job_is_ready(&job->job),
@@ -297,8 +287,8 @@ static void block_job_event_cancelled(Notifier *n, void *opaque)
 
     qapi_event_send_block_job_cancelled(job_type(&job->job),
                                         job->job.id,
-                                        job->len,
-                                        job->offset,
+                                        job->job.progress_total,
+                                        job->job.progress_current,
                                         job->speed,
                                         &error_abort);
 }
@@ -318,8 +308,8 @@ static void block_job_event_completed(Notifier *n, void *opaque)
 
     qapi_event_send_block_job_completed(job_type(&job->job),
                                         job->job.id,
-                                        job->len,
-                                        job->offset,
+                                        job->job.progress_total,
+                                        job->job.progress_current,
                                         job->speed,
                                         !!msg,
                                         msg,
@@ -349,8 +339,8 @@ static void block_job_event_ready(Notifier *n, void *opaque)
 
     qapi_event_send_block_job_ready(job_type(&job->job),
                                     job->job.id,
-                                    job->len,
-                                    job->offset,
+                                    job->job.progress_total,
+                                    job->job.progress_current,
                                     job->speed, &error_abort);
 }
 
diff --git a/job.c b/job.c
index 4e898b5c5e..e45c301fcd 100644
--- a/job.c
+++ b/job.c
@@ -364,6 +364,16 @@ void job_unref(Job *job)
     }
 }
 
+void job_progress_update(Job *job, uint64_t done)
+{
+    job->progress_current += done;
+}
+
+void job_progress_set_remaining(Job *job, uint64_t remaining)
+{
+    job->progress_total = job->progress_current + remaining;
+}
+
 void job_event_cancelled(Job *job)
 {
     notifier_list_notify(&job->on_finalize_cancelled, job);
diff --git a/qemu-img.c b/qemu-img.c
index 6361d4edf5..b248357e22 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -864,9 +864,13 @@ static void run_block_job(BlockJob *job, Error **errp)
     aio_context_acquire(aio_context);
     job_ref(&job->job);
     do {
+        float progress = 0.0f;
         aio_poll(aio_context, true);
-        qemu_progress_print(job->len ?
-                            ((float)job->offset / job->len * 100.f) : 0.0f, 0);
+        if (job->job.progress_total) {
+            progress = (float)job->job.progress_current /
+                       job->job.progress_total * 100.f;
+        }
+        qemu_progress_print(progress, 0);
     } while (!job_is_ready(&job->job) && !job_is_completed(&job->job));
 
     if (!job_is_completed(&job->job)) {
-- 
2.13.6

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

* [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (36 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 22:11   ` Max Reitz
  2018-05-16 19:26   ` Eric Blake
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands Kevin Wolf
                   ` (4 subsequent siblings)
  42 siblings, 2 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This adds a QMP event that is emitted whenever a job transitions from
one status to another. For the event, a new qapi/job.json schema file is
created which will contain all job-related definitions that aren't tied
to the block layer.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 qapi/block-core.json       |  47 +-----------
 qapi/job.json              |  65 +++++++++++++++++
 qapi/qapi-schema.json      |   1 +
 job.c                      |  10 +++
 Makefile                   |   9 +++
 Makefile.objs              |   4 +
 tests/qemu-iotests/030     |   6 +-
 tests/qemu-iotests/040     |   2 +
 tests/qemu-iotests/041     |  17 ++++-
 tests/qemu-iotests/095     |   2 +-
 tests/qemu-iotests/095.out |   6 ++
 tests/qemu-iotests/109     |   2 +-
 tests/qemu-iotests/109.out | 178 +++++++++++++++++++++++++++++++++++++++------
 tests/qemu-iotests/124     |   8 ++
 tests/qemu-iotests/127.out |   7 ++
 tests/qemu-iotests/141     |  10 +--
 tests/qemu-iotests/141.out |  29 ++++++++
 tests/qemu-iotests/144     |   2 +-
 tests/qemu-iotests/144.out |   7 ++
 tests/qemu-iotests/156     |   2 +-
 tests/qemu-iotests/156.out |   7 ++
 tests/qemu-iotests/185     |  12 +--
 tests/qemu-iotests/185.out |  10 +++
 tests/qemu-iotests/191     |   4 +-
 tests/qemu-iotests/191.out | 132 +++++++++++++++++++++++++++++++++
 25 files changed, 492 insertions(+), 87 deletions(-)
 create mode 100644 qapi/job.json

diff --git a/qapi/block-core.json b/qapi/block-core.json
index f86004fbc3..f2da7d696d 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -6,6 +6,7 @@
 
 { 'include': 'common.json' }
 { 'include': 'crypto.json' }
+{ 'include': 'job.json' }
 { 'include': 'sockets.json' }
 
 ##
@@ -1093,52 +1094,6 @@
            'finalize' ] }
 
 ##
-# @JobStatus:
-#
-# Indicates the present state of a given job in its lifetime.
-#
-# @undefined: Erroneous, default state. Should not ever be visible.
-#
-# @created: The job has been created, but not yet started.
-#
-# @running: The job is currently running.
-#
-# @paused: The job is running, but paused. The pause may be requested by
-#          either the QMP user or by internal processes.
-#
-# @ready: The job is running, but is ready for the user to signal completion.
-#         This is used for long-running jobs like mirror that are designed to
-#         run indefinitely.
-#
-# @standby: The job is ready, but paused. This is nearly identical to @paused.
-#           The job may return to @ready or otherwise be canceled.
-#
-# @waiting: The job is waiting for other jobs in the transaction to converge
-#           to the waiting state. This status will likely not be visible for
-#           the last job in a transaction.
-#
-# @pending: The job has finished its work, but has finalization steps that it
-#           needs to make prior to completing. These changes may require
-#           manual intervention by the management process if manual was set
-#           to true. These changes may still fail.
-#
-# @aborting: The job is in the process of being aborted, and will finish with
-#            an error. The job will afterwards report that it is @concluded.
-#            This status may not be visible to the management process.
-#
-# @concluded: The job has finished all work. If manual was set to true, the job
-#             will remain in the query list until it is dismissed.
-#
-# @null: The job is in the process of being dismantled. This state should not
-#        ever be visible externally.
-#
-# Since: 2.12
-##
-{ 'enum': 'JobStatus',
-  'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby',
-           'waiting', 'pending', 'aborting', 'concluded', 'null' ] }
-
-##
 # @BlockJobInfo:
 #
 # Information about a long-running block device operation.
diff --git a/qapi/job.json b/qapi/job.json
new file mode 100644
index 0000000000..bd88a358d0
--- /dev/null
+++ b/qapi/job.json
@@ -0,0 +1,65 @@
+# -*- Mode: Python -*-
+
+##
+# == Background jobs
+##
+
+##
+# @JobStatus:
+#
+# Indicates the present state of a given job in its lifetime.
+#
+# @undefined: Erroneous, default state. Should not ever be visible.
+#
+# @created: The job has been created, but not yet started.
+#
+# @running: The job is currently running.
+#
+# @paused: The job is running, but paused. The pause may be requested by
+#          either the QMP user or by internal processes.
+#
+# @ready: The job is running, but is ready for the user to signal completion.
+#         This is used for long-running jobs like mirror that are designed to
+#         run indefinitely.
+#
+# @standby: The job is ready, but paused. This is nearly identical to @paused.
+#           The job may return to @ready or otherwise be canceled.
+#
+# @waiting: The job is waiting for other jobs in the transaction to converge
+#           to the waiting state. This status will likely not be visible for
+#           the last job in a transaction.
+#
+# @pending: The job has finished its work, but has finalization steps that it
+#           needs to make prior to completing. These changes may require
+#           manual intervention by the management process if manual was set
+#           to true. These changes may still fail.
+#
+# @aborting: The job is in the process of being aborted, and will finish with
+#            an error. The job will afterwards report that it is @concluded.
+#            This status may not be visible to the management process.
+#
+# @concluded: The job has finished all work. If manual was set to true, the job
+#             will remain in the query list until it is dismissed.
+#
+# @null: The job is in the process of being dismantled. This state should not
+#        ever be visible externally.
+#
+# Since: 2.12
+##
+{ 'enum': 'JobStatus',
+  'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby',
+           'waiting', 'pending', 'aborting', 'concluded', 'null' ] }
+
+##
+# @JOB_STATUS_CHANGE:
+#
+# Emitted when a job transitions to a different status.
+#
+# @id: The job identifier
+# @status: The new job status
+#
+# Since: 2.13
+##
+{ 'event': 'JOB_STATUS_CHANGE',
+  'data': { 'id': 'str',
+            'status': 'JobStatus' } }
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 25bce78352..65b6dc2f6f 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -84,6 +84,7 @@
 { 'include': 'crypto.json' }
 { 'include': 'block.json' }
 { 'include': 'char.json' }
+{ 'include': 'job.json' }
 { 'include': 'net.json' }
 { 'include': 'rocker.json' }
 { 'include': 'tpm.json' }
diff --git a/job.c b/job.c
index e45c301fcd..4dc45648e9 100644
--- a/job.c
+++ b/job.c
@@ -30,6 +30,7 @@
 #include "qemu/id.h"
 #include "qemu/main-loop.h"
 #include "trace-root.h"
+#include "qapi/qapi-events-job.h"
 
 static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
 
@@ -157,6 +158,11 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)
     return rc;
 }
 
+static bool job_is_internal(Job *job)
+{
+    return (job->id == NULL);
+}
+
 static void job_state_transition(Job *job, JobStatus s1)
 {
     JobStatus s0 = job->status;
@@ -166,6 +172,10 @@ static void job_state_transition(Job *job, JobStatus s1)
                                JobStatus_str(s0), JobStatus_str(s1));
     assert(JobSTT[s0][s1]);
     job->status = s1;
+
+    if (!job_is_internal(job) && s1 != s0) {
+        qapi_event_send_job_status_change(job->id, job->status, &error_abort);
+    }
 }
 
 int job_apply_verb(Job *job, JobVerb bv, Error **errp)
diff --git a/Makefile b/Makefile
index d71dd5bea4..1404e5d8df 100644
--- a/Makefile
+++ b/Makefile
@@ -98,6 +98,7 @@ GENERATED_FILES += qapi/qapi-types-char.h qapi/qapi-types-char.c
 GENERATED_FILES += qapi/qapi-types-common.h qapi/qapi-types-common.c
 GENERATED_FILES += qapi/qapi-types-crypto.h qapi/qapi-types-crypto.c
 GENERATED_FILES += qapi/qapi-types-introspect.h qapi/qapi-types-introspect.c
+GENERATED_FILES += qapi/qapi-types-job.h qapi/qapi-types-job.c
 GENERATED_FILES += qapi/qapi-types-migration.h qapi/qapi-types-migration.c
 GENERATED_FILES += qapi/qapi-types-misc.h qapi/qapi-types-misc.c
 GENERATED_FILES += qapi/qapi-types-net.h qapi/qapi-types-net.c
@@ -116,6 +117,7 @@ GENERATED_FILES += qapi/qapi-visit-char.h qapi/qapi-visit-char.c
 GENERATED_FILES += qapi/qapi-visit-common.h qapi/qapi-visit-common.c
 GENERATED_FILES += qapi/qapi-visit-crypto.h qapi/qapi-visit-crypto.c
 GENERATED_FILES += qapi/qapi-visit-introspect.h qapi/qapi-visit-introspect.c
+GENERATED_FILES += qapi/qapi-visit-job.h qapi/qapi-visit-job.c
 GENERATED_FILES += qapi/qapi-visit-migration.h qapi/qapi-visit-migration.c
 GENERATED_FILES += qapi/qapi-visit-misc.h qapi/qapi-visit-misc.c
 GENERATED_FILES += qapi/qapi-visit-net.h qapi/qapi-visit-net.c
@@ -133,6 +135,7 @@ GENERATED_FILES += qapi/qapi-commands-char.h qapi/qapi-commands-char.c
 GENERATED_FILES += qapi/qapi-commands-common.h qapi/qapi-commands-common.c
 GENERATED_FILES += qapi/qapi-commands-crypto.h qapi/qapi-commands-crypto.c
 GENERATED_FILES += qapi/qapi-commands-introspect.h qapi/qapi-commands-introspect.c
+GENERATED_FILES += qapi/qapi-commands-job.h qapi/qapi-commands-job.c
 GENERATED_FILES += qapi/qapi-commands-migration.h qapi/qapi-commands-migration.c
 GENERATED_FILES += qapi/qapi-commands-misc.h qapi/qapi-commands-misc.c
 GENERATED_FILES += qapi/qapi-commands-net.h qapi/qapi-commands-net.c
@@ -150,6 +153,7 @@ GENERATED_FILES += qapi/qapi-events-char.h qapi/qapi-events-char.c
 GENERATED_FILES += qapi/qapi-events-common.h qapi/qapi-events-common.c
 GENERATED_FILES += qapi/qapi-events-crypto.h qapi/qapi-events-crypto.c
 GENERATED_FILES += qapi/qapi-events-introspect.h qapi/qapi-events-introspect.c
+GENERATED_FILES += qapi/qapi-events-job.h qapi/qapi-events-job.c
 GENERATED_FILES += qapi/qapi-events-migration.h qapi/qapi-events-migration.c
 GENERATED_FILES += qapi/qapi-events-misc.h qapi/qapi-events-misc.c
 GENERATED_FILES += qapi/qapi-events-net.h qapi/qapi-events-net.c
@@ -582,6 +586,7 @@ qapi-modules = $(SRC_PATH)/qapi/qapi-schema.json $(SRC_PATH)/qapi/common.json \
                $(SRC_PATH)/qapi/char.json \
                $(SRC_PATH)/qapi/crypto.json \
                $(SRC_PATH)/qapi/introspect.json \
+               $(SRC_PATH)/qapi/job.json \
                $(SRC_PATH)/qapi/migration.json \
                $(SRC_PATH)/qapi/misc.json \
                $(SRC_PATH)/qapi/net.json \
@@ -601,6 +606,7 @@ qapi/qapi-types-char.c qapi/qapi-types-char.h \
 qapi/qapi-types-common.c qapi/qapi-types-common.h \
 qapi/qapi-types-crypto.c qapi/qapi-types-crypto.h \
 qapi/qapi-types-introspect.c qapi/qapi-types-introspect.h \
+qapi/qapi-types-job.c qapi/qapi-types-job.h \
 qapi/qapi-types-migration.c qapi/qapi-types-migration.h \
 qapi/qapi-types-misc.c qapi/qapi-types-misc.h \
 qapi/qapi-types-net.c qapi/qapi-types-net.h \
@@ -619,6 +625,7 @@ qapi/qapi-visit-char.c qapi/qapi-visit-char.h \
 qapi/qapi-visit-common.c qapi/qapi-visit-common.h \
 qapi/qapi-visit-crypto.c qapi/qapi-visit-crypto.h \
 qapi/qapi-visit-introspect.c qapi/qapi-visit-introspect.h \
+qapi/qapi-visit-job.c qapi/qapi-visit-job.h \
 qapi/qapi-visit-migration.c qapi/qapi-visit-migration.h \
 qapi/qapi-visit-misc.c qapi/qapi-visit-misc.h \
 qapi/qapi-visit-net.c qapi/qapi-visit-net.h \
@@ -636,6 +643,7 @@ qapi/qapi-commands-char.c qapi/qapi-commands-char.h \
 qapi/qapi-commands-common.c qapi/qapi-commands-common.h \
 qapi/qapi-commands-crypto.c qapi/qapi-commands-crypto.h \
 qapi/qapi-commands-introspect.c qapi/qapi-commands-introspect.h \
+qapi/qapi-commands-job.c qapi/qapi-commands-job.h \
 qapi/qapi-commands-migration.c qapi/qapi-commands-migration.h \
 qapi/qapi-commands-misc.c qapi/qapi-commands-misc.h \
 qapi/qapi-commands-net.c qapi/qapi-commands-net.h \
@@ -653,6 +661,7 @@ qapi/qapi-events-char.c qapi/qapi-events-char.h \
 qapi/qapi-events-common.c qapi/qapi-events-common.h \
 qapi/qapi-events-crypto.c qapi/qapi-events-crypto.h \
 qapi/qapi-events-introspect.c qapi/qapi-events-introspect.h \
+qapi/qapi-events-job.c qapi/qapi-events-job.h \
 qapi/qapi-events-migration.c qapi/qapi-events-migration.h \
 qapi/qapi-events-misc.c qapi/qapi-events-misc.h \
 qapi/qapi-events-net.c qapi/qapi-events-net.h \
diff --git a/Makefile.objs b/Makefile.objs
index 92b73fc272..3df8d58e49 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -10,6 +10,7 @@ util-obj-y += qapi/qapi-types-char.o
 util-obj-y += qapi/qapi-types-common.o
 util-obj-y += qapi/qapi-types-crypto.o
 util-obj-y += qapi/qapi-types-introspect.o
+util-obj-y += qapi/qapi-types-job.o
 util-obj-y += qapi/qapi-types-migration.o
 util-obj-y += qapi/qapi-types-misc.o
 util-obj-y += qapi/qapi-types-net.o
@@ -28,6 +29,7 @@ util-obj-y += qapi/qapi-visit-char.o
 util-obj-y += qapi/qapi-visit-common.o
 util-obj-y += qapi/qapi-visit-crypto.o
 util-obj-y += qapi/qapi-visit-introspect.o
+util-obj-y += qapi/qapi-visit-job.o
 util-obj-y += qapi/qapi-visit-migration.o
 util-obj-y += qapi/qapi-visit-misc.o
 util-obj-y += qapi/qapi-visit-net.o
@@ -45,6 +47,7 @@ util-obj-y += qapi/qapi-events-char.o
 util-obj-y += qapi/qapi-events-common.o
 util-obj-y += qapi/qapi-events-crypto.o
 util-obj-y += qapi/qapi-events-introspect.o
+util-obj-y += qapi/qapi-events-job.o
 util-obj-y += qapi/qapi-events-migration.o
 util-obj-y += qapi/qapi-events-misc.o
 util-obj-y += qapi/qapi-events-net.o
@@ -140,6 +143,7 @@ common-obj-y += qapi/qapi-commands-char.o
 common-obj-y += qapi/qapi-commands-common.o
 common-obj-y += qapi/qapi-commands-crypto.o
 common-obj-y += qapi/qapi-commands-introspect.o
+common-obj-y += qapi/qapi-commands-job.o
 common-obj-y += qapi/qapi-commands-migration.o
 common-obj-y += qapi/qapi-commands-misc.o
 common-obj-y += qapi/qapi-commands-net.o
diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030
index 640a6dfd10..03aea460c9 100755
--- a/tests/qemu-iotests/030
+++ b/tests/qemu-iotests/030
@@ -304,7 +304,7 @@ class TestParallelOps(iotests.QMPTestCase):
         result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6')
         self.assert_qmp(result, 'error/class', 'GenericError')
 
-        event = self.vm.get_qmp_event(wait=True)
+        event = self.vm.event_wait(name='BLOCK_JOB_READY')
         self.assertEqual(event['event'], 'BLOCK_JOB_READY')
         self.assert_qmp(event, 'data/device', 'commit-drive0')
         self.assert_qmp(event, 'data/type', 'commit')
@@ -751,7 +751,9 @@ class TestStreamStop(iotests.QMPTestCase):
 
         time.sleep(0.1)
         events = self.vm.get_qmp_events(wait=False)
-        self.assertEqual(events, [], 'unexpected QMP event: %s' % events)
+        for e in events:
+            if e['event'] != 'JOB_STATUS_CHANGE':
+                self.assertEqual(events, [], 'unexpected QMP event: %s' % events)
 
         self.cancel_and_wait(resume=True)
 
diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040
index 90b5b4f2ad..1beb5e6dab 100755
--- a/tests/qemu-iotests/040
+++ b/tests/qemu-iotests/040
@@ -162,6 +162,8 @@ class TestSingleDrive(ImageCommitTestCase):
                 elif event['event'] == 'BLOCK_JOB_CANCELLED':
                     self.assert_qmp(event, 'data/device', 'drive0')
                     cancelled = True
+                elif event['event'] == 'JOB_STATUS_CHANGE':
+                    self.assert_qmp(event, 'data/id', 'drive0')
                 else:
                     self.fail("Unexpected event %s" % (event['event']))
 
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
index a860a31e9a..e94587950c 100755
--- a/tests/qemu-iotests/041
+++ b/tests/qemu-iotests/041
@@ -445,6 +445,8 @@ new_state = "1"
                     self.assert_qmp(event, 'data/device', 'drive0')
                     self.assert_qmp(event, 'data/error', 'Input/output error')
                     completed = True
+                elif event['event'] == 'JOB_STATUS_CHANGE':
+                    self.assert_qmp(event, 'data/id', 'drive0')
 
         self.assert_no_active_block_jobs()
         self.vm.shutdown()
@@ -457,6 +459,10 @@ new_state = "1"
         self.assert_qmp(result, 'return', {})
 
         event = self.vm.get_qmp_event(wait=True)
+        while event['event'] == 'JOB_STATUS_CHANGE':
+            self.assert_qmp(event, 'data/id', 'drive0')
+            event = self.vm.get_qmp_event(wait=True)
+
         self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
         self.assert_qmp(event, 'data/device', 'drive0')
         self.assert_qmp(event, 'data/operation', 'read')
@@ -478,6 +484,10 @@ new_state = "1"
         self.assert_qmp(result, 'return', {})
 
         event = self.vm.get_qmp_event(wait=True)
+        while event['event'] == 'JOB_STATUS_CHANGE':
+            self.assert_qmp(event, 'data/id', 'drive0')
+            event = self.vm.get_qmp_event(wait=True)
+
         self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
         self.assert_qmp(event, 'data/device', 'drive0')
         self.assert_qmp(event, 'data/operation', 'read')
@@ -608,7 +618,7 @@ new_state = "1"
                              on_target_error='ignore')
         self.assert_qmp(result, 'return', {})
 
-        event = self.vm.get_qmp_event(wait=True)
+        event = self.vm.event_wait(name='BLOCK_JOB_ERROR')
         self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
         self.assert_qmp(event, 'data/device', 'drive0')
         self.assert_qmp(event, 'data/operation', 'write')
@@ -784,7 +794,12 @@ class TestGranularity(iotests.QMPTestCase):
                              sync='full', target=target_img,
                              mode='absolute-paths', granularity=8192)
         self.assert_qmp(result, 'return', {})
+
         event = self.vm.get_qmp_event(wait=60.0)
+        while event['event'] == 'JOB_STATUS_CHANGE':
+            self.assert_qmp(event, 'data/id', 'drive0')
+            event = self.vm.get_qmp_event(wait=60.0)
+
         # Failures will manifest as COMPLETED/ERROR.
         self.assert_qmp(event, 'event', 'BLOCK_JOB_READY')
         self.complete_and_wait(drive='drive0', wait_ready=False)
diff --git a/tests/qemu-iotests/095 b/tests/qemu-iotests/095
index 030adb22e1..72ecc22e1b 100755
--- a/tests/qemu-iotests/095
+++ b/tests/qemu-iotests/095
@@ -72,7 +72,7 @@ _send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" "return"
 
 _send_qemu_cmd $h "{ 'execute': 'block-commit',
                                  'arguments': { 'device': 'test',
-                                 'top': '"${TEST_IMG}.snp1"' } }" "BLOCK_JOB_COMPLETED"
+                                 'top': '"${TEST_IMG}.snp1"' } }" '"status": "null"'
 
 _cleanup_qemu
 
diff --git a/tests/qemu-iotests/095.out b/tests/qemu-iotests/095.out
index 73875cab40..8c093dfff3 100644
--- a/tests/qemu-iotests/095.out
+++ b/tests/qemu-iotests/095.out
@@ -11,8 +11,14 @@ virtual size: 5.0M (5242880 bytes)
 === Running QEMU Live Commit Test ===
 
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "test"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "test"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "test"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "test"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "test", "len": 104857600, "offset": 104857600, "speed": 0, "type": "commit"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "test"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "test"}}
 
 === Base image info after commit and resize ===
 image: TEST_DIR/t.IMGFMT.base
diff --git a/tests/qemu-iotests/109 b/tests/qemu-iotests/109
index d70b574d88..acbd079136 100755
--- a/tests/qemu-iotests/109
+++ b/tests/qemu-iotests/109
@@ -64,7 +64,7 @@ function run_qemu()
 
     _send_qemu_cmd $QEMU_HANDLE '' "$qmp_event"
     if test "$qmp_event" = BLOCK_JOB_ERROR; then
-        _send_qemu_cmd $QEMU_HANDLE '' "BLOCK_JOB_COMPLETED"
+        _send_qemu_cmd $QEMU_HANDLE '' '"status": "null"'
     fi
     _send_qemu_cmd $QEMU_HANDLE '{"execute":"query-block-jobs"}' "return"
     _send_qemu_cmd $QEMU_HANDLE '{"execute":"quit"}' "return"
diff --git a/tests/qemu-iotests/109.out b/tests/qemu-iotests/109.out
index 8a9b93672b..ad0ee6fb48 100644
--- a/tests/qemu-iotests/109.out
+++ b/tests/qemu-iotests/109.out
@@ -6,23 +6,35 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Formatting 'TEST_DIR/t.raw.src', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": 0, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 1024, "offset": 1024, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -32,23 +44,35 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Formatting 'TEST_DIR/t.raw.src', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": 512, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 197120, "offset": 197120, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 197120, "offset": 197120, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 197120, "offset": 197120, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -58,23 +82,35 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Formatting 'TEST_DIR/t.raw.src', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": 262144, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 327680, "offset": 327680, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -84,23 +120,35 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Formatting 'TEST_DIR/t.raw.src', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": 0, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 1024, "offset": 1024, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -110,23 +158,35 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Formatting 'TEST_DIR/t.raw.src', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": 0, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 65536, "offset": 65536, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -136,23 +196,35 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Formatting 'TEST_DIR/t.raw.src', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": 0, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 2560, "offset": 2560, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -161,23 +233,35 @@ Images are identical.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 2560, "offset": 2560, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -186,23 +270,35 @@ Images are identical.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 31457280, "offset": 31457280, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 31457280, "offset": 31457280, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 31457280, "offset": 31457280, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -211,23 +307,35 @@ Images are identical.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 327680, "offset": 327680, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -236,23 +344,35 @@ Images are identical.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "src", "operation": "write", "action": "report"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror", "error": "Operation not permitted"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 {"return": []}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 2048, "offset": 2048, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 2048, "offset": 2048, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 2048, "offset": 2048, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 
@@ -261,23 +381,37 @@ Images are identical.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 {"return": {}}
 WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed raw.
-Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
-Specify the 'raw' format explicitly to remove the restrictions.
+         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
+         Specify the 'raw' format explicitly to remove the restrictions.
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 512, "offset": 512, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}}
 {"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 512, "offset": 512, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}}
 Warning: Image size mismatch!
 Images are identical.
 *** done
diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 8e76e62f93..3ea4ac53f5 100755
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -151,10 +151,17 @@ class TestIncrementalBackupBase(iotests.QMPTestCase):
         return self.wait_qmp_backup(kwargs['device'], error)
 
 
+    def ignore_job_status_change_events(self):
+        while True:
+            e = self.vm.event_wait(name="JOB_STATUS_CHANGE")
+            if e['data']['status'] == 'null':
+                break
+
     def wait_qmp_backup(self, device, error='Input/output error'):
         event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
                                    match={'data': {'device': device}})
         self.assertNotEqual(event, None)
+        self.ignore_job_status_change_events()
 
         try:
             failure = self.dictpath(event, 'data/error')
@@ -172,6 +179,7 @@ class TestIncrementalBackupBase(iotests.QMPTestCase):
         event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
                                    match={'data': {'device': device}})
         self.assertNotEqual(event, None)
+        self.ignore_job_status_change_events()
 
 
     def create_anchor_backup(self, drive=None):
diff --git a/tests/qemu-iotests/127.out b/tests/qemu-iotests/127.out
index 543d075005..83b522d4c2 100644
--- a/tests/qemu-iotests/127.out
+++ b/tests/qemu-iotests/127.out
@@ -5,10 +5,17 @@ Formatting 'TEST_DIR/t.IMGFMT.overlay1', fmt=IMGFMT size=65536 backing_file=TEST
 wrote 42/42 bytes at offset 0
 42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "mirror"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "mirror"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "mirror", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "mirror"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "mirror", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "mirror"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
 *** done
diff --git a/tests/qemu-iotests/141 b/tests/qemu-iotests/141
index 2f9d7b9bc2..9ae23a6c63 100755
--- a/tests/qemu-iotests/141
+++ b/tests/qemu-iotests/141
@@ -107,7 +107,7 @@ test_blockjob \
                     'format': '$IMGFMT',
                     'sync': 'none'}}" \
     'return' \
-    'BLOCK_JOB_CANCELLED'
+    '"status": "null"'
 
 echo
 echo '=== Testing drive-mirror ==='
@@ -124,7 +124,7 @@ test_blockjob \
                     'format': '$IMGFMT',
                     'sync': 'none'}}" \
     'BLOCK_JOB_READY' \
-    'BLOCK_JOB_COMPLETED'
+    '"status": "null"'
 
 echo
 echo '=== Testing active block-commit ==='
@@ -138,7 +138,7 @@ test_blockjob \
     "{'execute': 'block-commit',
       'arguments': {'job-id': 'job0', 'device': 'drv0'}}" \
     'BLOCK_JOB_READY' \
-    'BLOCK_JOB_COMPLETED'
+    '"status": "null"'
 
 echo
 echo '=== Testing non-active block-commit ==='
@@ -157,7 +157,7 @@ test_blockjob \
                     'top':    '$TEST_DIR/m.$IMGFMT',
                     'speed':  1}}" \
     'return' \
-    'BLOCK_JOB_CANCELLED'
+    '"status": "null"'
 
 echo
 echo '=== Testing block-stream ==='
@@ -179,7 +179,7 @@ test_blockjob \
                     'device': 'drv0',
                     'speed': 1}}" \
     'return' \
-    'BLOCK_JOB_CANCELLED'
+    '"status": "null"'
 
 _cleanup_qemu
 
diff --git a/tests/qemu-iotests/141.out b/tests/qemu-iotests/141.out
index 82e763b68d..f252c86875 100644
--- a/tests/qemu-iotests/141.out
+++ b/tests/qemu-iotests/141.out
@@ -8,31 +8,50 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/m.
 
 {"return": {}}
 Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT backing_fmt=IMGFMT
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}}
 {"return": {}}
 {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "job0", "len": 1048576, "offset": 0, "speed": 0, "type": "backup"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}}
 {"return": {}}
 
 === Testing drive-mirror ===
 
 {"return": {}}
 Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT backing_fmt=IMGFMT
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "mirror"}}
 {"return": {}}
 {"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: node is used as backing hd of 'NODE_NAME'"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}}
 {"return": {}}
 
 === Testing active block-commit ===
 
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}}
 {"return": {}}
 {"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: node is used as backing hd of 'NODE_NAME'"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}}
 {"return": {}}
 
 === Testing non-active block-commit ===
@@ -40,10 +59,15 @@ Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.
 wrote 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}}
 {"return": {}}
 {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "job0", "len": 1048576, "offset": 524288, "speed": 1, "type": "commit"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}}
 {"return": {}}
 
 === Testing block-stream ===
@@ -51,9 +75,14 @@ wrote 1048576/1048576 bytes at offset 0
 wrote 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}}
 {"return": {}}
 {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "job0", "len": 1048576, "offset": 524288, "speed": 1, "type": "stream"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}}
 {"return": {}}
 *** done
diff --git a/tests/qemu-iotests/144 b/tests/qemu-iotests/144
index 00de3c33cf..4b915718cd 100755
--- a/tests/qemu-iotests/144
+++ b/tests/qemu-iotests/144
@@ -93,7 +93,7 @@ _send_qemu_cmd $h "{ 'execute': 'block-job-complete',
                                 'arguments': {
                                                 'device': 'virtio0'
                                               }
-                   }" "COMPLETED"
+                   }" '"status": "null"'
 
 echo
 echo === Performing Live Snapshot 2 ===
diff --git a/tests/qemu-iotests/144.out b/tests/qemu-iotests/144.out
index 014b2817ee..55299201e4 100644
--- a/tests/qemu-iotests/144.out
+++ b/tests/qemu-iotests/144.out
@@ -12,10 +12,17 @@ Formatting 'TEST_DIR/tmp.qcow2', fmt=qcow2 size=536870912 backing_file=TEST_DIR/
 
 === Performing block-commit on active layer ===
 
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "virtio0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "virtio0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "virtio0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "virtio0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}}
 {"return": {}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "virtio0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "virtio0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "virtio0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "virtio0"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "virtio0"}}
 
 === Performing Live Snapshot 2 ===
 
diff --git a/tests/qemu-iotests/156 b/tests/qemu-iotests/156
index e75dc4d743..0a9a09802e 100755
--- a/tests/qemu-iotests/156
+++ b/tests/qemu-iotests/156
@@ -119,7 +119,7 @@ _send_qemu_cmd $QEMU_HANDLE \
 
 _send_qemu_cmd $QEMU_HANDLE \
     '' \
-    'BLOCK_JOB_COMPLETED'
+    '"status": "null"'
 
 # Remove the source images
 rm -f "$TEST_IMG{,.backing,.overlay}"
diff --git a/tests/qemu-iotests/156.out b/tests/qemu-iotests/156.out
index f96a564c1d..34c057b626 100644
--- a/tests/qemu-iotests/156.out
+++ b/tests/qemu-iotests/156.out
@@ -12,13 +12,20 @@ wrote 131072/131072 bytes at offset 131072
 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": ""}
 Formatting 'TEST_DIR/t.IMGFMT.target.overlay', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT.target
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "source"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "source"}}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "source"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "source", "len": 131072, "offset": 131072, "speed": 0, "type": "mirror"}}
 wrote 65536/65536 bytes at offset 196608
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": ""}
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "source"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "source"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "source", "len": 196608, "offset": 196608, "speed": 0, "type": "mirror"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "source"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "source"}}
 
 read 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
diff --git a/tests/qemu-iotests/185 b/tests/qemu-iotests/185
index 298d88d04e..9a4f54d57a 100755
--- a/tests/qemu-iotests/185
+++ b/tests/qemu-iotests/185
@@ -118,8 +118,10 @@ _send_qemu_cmd $h \
                       'speed': 65536 } }" \
     "return"
 
+# Ignore the JOB_STATUS_CHANGE events while shutting down the VM. Depending on
+# the timing, jobs may or may not transition through a paused state.
 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
-wait=1 _cleanup_qemu
+wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE'
 
 echo
 echo === Start active commit job and exit qemu ===
@@ -138,7 +140,7 @@ _send_qemu_cmd $h \
     "return"
 
 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
-wait=1 _cleanup_qemu
+wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE'
 
 echo
 echo === Start mirror job and exit qemu ===
@@ -163,7 +165,7 @@ _send_qemu_cmd $h \
 sleep 0.5
 
 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
-wait=1 _cleanup_qemu
+wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE'
 
 echo
 echo === Start backup job and exit qemu ===
@@ -184,7 +186,7 @@ _send_qemu_cmd $h \
     "return"
 
 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
-wait=1 _cleanup_qemu
+wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE'
 
 echo
 echo === Start streaming job and exit qemu ===
@@ -202,7 +204,7 @@ _send_qemu_cmd $h \
     "return"
 
 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return"
-wait=1 _cleanup_qemu
+wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE'
 
 _check_test_img
 
diff --git a/tests/qemu-iotests/185.out b/tests/qemu-iotests/185.out
index 992162f418..dd3b8c719d 100644
--- a/tests/qemu-iotests/185.out
+++ b/tests/qemu-iotests/185.out
@@ -17,6 +17,8 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q
 
 === Start commit job and exit qemu ===
 
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -25,6 +27,8 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q
 === Start active commit job and exit qemu ===
 
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -35,6 +39,8 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q
 
 {"return": {}}
 Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -45,6 +51,8 @@ Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 l
 
 {"return": {}}
 Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -53,6 +61,8 @@ Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 l
 === Start streaming job and exit qemu ===
 
 {"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
diff --git a/tests/qemu-iotests/191 b/tests/qemu-iotests/191
index dfad6555e4..b3629ff6e8 100755
--- a/tests/qemu-iotests/191
+++ b/tests/qemu-iotests/191
@@ -83,7 +83,7 @@ _send_qemu_cmd $h \
                       'device': 'top',
                       'base':'$TEST_IMG.base',
                       'top': '$TEST_IMG.mid' } }" \
-    "BLOCK_JOB_COMPLETED"
+    '"status": "null"'
 _send_qemu_cmd $h "" "^}"
 
 echo
@@ -131,7 +131,7 @@ _send_qemu_cmd $h \
                       'device': 'top',
                       'base':'$TEST_IMG.base',
                       'top': '$TEST_IMG.mid' } }" \
-    "BLOCK_JOB_COMPLETED"
+    '"status": "null"'
 _send_qemu_cmd $h "" "^}"
 
 echo
diff --git a/tests/qemu-iotests/191.out b/tests/qemu-iotests/191.out
index 190c5f049a..31a0c7d4c4 100644
--- a/tests/qemu-iotests/191.out
+++ b/tests/qemu-iotests/191.out
@@ -16,6 +16,28 @@ wrote 65536/65536 bytes at offset 1048576
 === Perform commit job ===
 
 {
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "created",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "running",
+        "id": "commit0"
+    }
+}
+{
     "return": {
     }
 }
@@ -24,6 +46,28 @@ wrote 65536/65536 bytes at offset 1048576
         "seconds":  TIMESTAMP,
         "microseconds":  TIMESTAMP
     },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "waiting",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "pending",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
     "event": "BLOCK_JOB_COMPLETED",
     "data": {
         "device": "commit0",
@@ -33,6 +77,28 @@ wrote 65536/65536 bytes at offset 1048576
         "type": "commit"
     }
 }
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "concluded",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "null",
+        "id": "commit0"
+    }
+}
 
 === Check that both top and top2 point to base now ===
 
@@ -356,6 +422,28 @@ wrote 65536/65536 bytes at offset 1048576
 === Perform commit job ===
 
 {
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "created",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "running",
+        "id": "commit0"
+    }
+}
+{
     "return": {
     }
 }
@@ -364,6 +452,28 @@ wrote 65536/65536 bytes at offset 1048576
         "seconds":  TIMESTAMP,
         "microseconds":  TIMESTAMP
     },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "waiting",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "pending",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
     "event": "BLOCK_JOB_COMPLETED",
     "data": {
         "device": "commit0",
@@ -373,6 +483,28 @@ wrote 65536/65536 bytes at offset 1048576
         "type": "commit"
     }
 }
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "concluded",
+        "id": "commit0"
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "JOB_STATUS_CHANGE",
+    "data": {
+        "status": "null",
+        "id": "commit0"
+    }
+}
 
 === Check that both top and top2 point to base now ===
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (37 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 22:31   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command Kevin Wolf
                   ` (3 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This adds QMP commands that control the transition between states of the
job lifecycle.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 qapi/job.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++
 job-qmp.c     | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 MAINTAINERS   |   1 +
 Makefile.objs |   2 +-
 trace-events  |   9 ++++
 5 files changed, 263 insertions(+), 1 deletion(-)
 create mode 100644 job-qmp.c

diff --git a/qapi/job.json b/qapi/job.json
index bd88a358d0..7b84158292 100644
--- a/qapi/job.json
+++ b/qapi/job.json
@@ -63,3 +63,115 @@
 { 'event': 'JOB_STATUS_CHANGE',
   'data': { 'id': 'str',
             'status': 'JobStatus' } }
+
+##
+# @job-pause:
+#
+# Pause an active job.
+#
+# This command returns immediately after marking the active job for pausing.
+# Pausing an already paused job has no cumulative effect; a single job-resume
+# command will resume the job.
+#
+# The job will pause as soon as possible, which means transitioning into the
+# PAUSED state if it was RUNNING, or into STANDBY if it was READY. The
+# corresponding JOB_STATUS_CHANGE event will be emitted.
+#
+# Cancelling a paused job automatically resumes it.
+#
+# @id: The job identifier.
+#
+# Since: 2.13
+##
+{ 'command': 'job-pause', 'data': { 'id': 'str' } }
+
+##
+# @job-resume:
+#
+# Resume a paused job.
+#
+# This command returns immediately after resuming a paused job. Resuming an
+# already running job is an error.
+#
+# @id : The job identifier.
+#
+# Since: 2.13
+##
+{ 'command': 'job-resume', 'data': { 'id': 'str' } }
+
+##
+# @job-cancel:
+#
+# Instruct an active background job to cancel at the next opportunity.
+# This command returns immediately after marking the active job for
+# cancellation.
+#
+# The job will cancel as soon as possible and then emit a JOB_STATUS_CHANGE
+# event. Usually, the status will change to ABORTING, but it is possible that
+# a job successfully completes (e.g. because it was almost done and there was
+# no opportunity to cancel earlier than completing the job) and transitions to
+# PENDING instead.
+#
+# Note that if you issue 'job-cancel' after a mirror block job has indicated
+# (via the event BLOCK_JOB_READY, and by transitioning into the READY state)
+# that the source and destination are synchronized, then the job always
+# completes successfully and transitions to PENDING as well as triggering the
+# event BLOCK_JOB_COMPLETED, to indicate that the mirroring has ended and the
+# destination now has a point-in-time copy tied to the time of the
+# cancellation.
+#
+# @id: The job identifier.
+#
+# @force: If true, and the job is already in the READY state, abandon the job
+#         immediately (even if it is paused) instead of waiting for the
+#         destination to complete its final synchronization
+#
+# Since: 2.13
+##
+{ 'command': 'job-cancel', 'data': { 'id': 'str', '*force': 'bool' } }
+
+
+##
+# @job-complete:
+#
+# Manually trigger completion of an active job in the READY state.
+#
+# @id: The job identifier.
+#
+# Since: 2.13
+##
+{ 'command': 'job-complete', 'data': { 'id': 'str' } }
+
+##
+# @job-dismiss:
+#
+# Deletes a job that is in the CONCLUDED state. This command only needs to be
+# run explicitly for jobs that don't have automatic dismiss enabled.
+#
+# This command will refuse to operate on any job that has not yet reached its
+# terminal state, JOB_STATUS_CONCLUDED. For jobs that make use of JOB_READY
+# event, job-cancel or job-complete will still need to be used as appropriate.
+#
+# @id: The job identifier.
+#
+# Since: 2.13
+##
+{ 'command': 'job-dismiss', 'data': { 'id': 'str' } }
+
+##
+# @job-finalize:
+#
+# Instructs all jobs in a transaction (or a single job if it is not part of any
+# transaction) to finalize any graph changes and do any necessary cleanup. This
+# command requires that all involved jobs are in the PENDING state.
+#
+# For jobs in a transaction, instructing one job to finalize will force
+# ALL jobs in the transaction to finalize, so it is only necessary to instruct
+# a single member job to finalize.
+#
+# @id: The identifier of any job in the transaction, or of a job that is not
+#      part of any transaction.
+#
+# Since: 2.13
+##
+{ 'command': 'job-finalize', 'data': { 'id': 'str' } }
diff --git a/job-qmp.c b/job-qmp.c
new file mode 100644
index 0000000000..f32cb8b73e
--- /dev/null
+++ b/job-qmp.c
@@ -0,0 +1,140 @@
+/*
+ * QMP interface for background jobs
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012, 2018 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/job.h"
+#include "qapi/qapi-commands-job.h"
+#include "qapi/error.h"
+#include "trace-root.h"
+
+/* Get a job using its ID and acquire its AioContext */
+static Job *find_job(const char *id, AioContext **aio_context, Error **errp)
+{
+    Job *job;
+
+    *aio_context = NULL;
+
+    job = job_get(id);
+    if (!job) {
+        error_setg(errp, "Job not found");
+        return NULL;
+    }
+
+    *aio_context = job->aio_context;
+    aio_context_acquire(*aio_context);
+
+    return job;
+}
+
+void qmp_job_cancel(const char *id, bool has_force, bool force, Error **errp)
+{
+    AioContext *aio_context;
+    Job *job = find_job(id, &aio_context, errp);
+
+    if (!job) {
+        return;
+    }
+
+    if (job_user_paused(job) && !force) {
+        error_setg(errp, "Job is currently paused");
+        goto out;
+    }
+
+    trace_qmp_job_cancel(job);
+    job_user_cancel(job, force, errp);
+out:
+    aio_context_release(aio_context);
+}
+
+void qmp_job_pause(const char *id, Error **errp)
+{
+    AioContext *aio_context;
+    Job *job = find_job(id, &aio_context, errp);
+
+    if (!job) {
+        return;
+    }
+
+    trace_qmp_job_pause(job);
+    job_user_pause(job, errp);
+    aio_context_release(aio_context);
+}
+
+void qmp_job_resume(const char *id, Error **errp)
+{
+    AioContext *aio_context;
+    Job *job = find_job(id, &aio_context, errp);
+
+    if (!job) {
+        return;
+    }
+
+    trace_qmp_job_resume(job);
+    job_user_resume(job, errp);
+    aio_context_release(aio_context);
+}
+
+void qmp_job_complete(const char *id, Error **errp)
+{
+    AioContext *aio_context;
+    Job *job = find_job(id, &aio_context, errp);
+
+    if (!job) {
+        return;
+    }
+
+    trace_qmp_job_complete(job);
+    job_complete(job, errp);
+    aio_context_release(aio_context);
+}
+
+void qmp_job_finalize(const char *id, Error **errp)
+{
+    AioContext *aio_context;
+    Job *job = find_job(id, &aio_context, errp);
+
+    if (!job) {
+        return;
+    }
+
+    trace_qmp_job_finalize(job);
+    job_finalize(job, errp);
+    aio_context_release(aio_context);
+}
+
+void qmp_job_dismiss(const char *id, Error **errp)
+{
+    AioContext *aio_context;
+    Job *job = find_job(id, &aio_context, errp);
+
+    if (!job) {
+        return;
+    }
+
+    trace_qmp_job_dismiss(job);
+    job_dismiss(&job, errp);
+    aio_context_release(aio_context);
+}
diff --git a/MAINTAINERS b/MAINTAINERS
index a97f60d104..778c6692d6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1370,6 +1370,7 @@ S: Supported
 F: blockjob.c
 F: include/block/blockjob.h
 F: job.c
+F: job-qmp.c
 F: include/block/job.h
 F: block/backup.c
 F: block/commit.c
diff --git a/Makefile.objs b/Makefile.objs
index 3df8d58e49..253e0356f3 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -66,7 +66,7 @@ chardev-obj-y = chardev/
 # block-obj-y is code used by both qemu system emulation and qemu-img
 
 block-obj-y += nbd/
-block-obj-y += block.o blockjob.o job.o
+block-obj-y += block.o blockjob.o job.o job-qmp.o
 block-obj-y += block/ scsi/
 block-obj-y += qemu-io-cmds.o
 block-obj-$(CONFIG_REPLICATION) += replication.o
diff --git a/trace-events b/trace-events
index ef7579a285..c445f54773 100644
--- a/trace-events
+++ b/trace-events
@@ -109,6 +109,15 @@ job_state_transition(void *job,  int ret, const char *legal, const char *s0, con
 job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
 job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d"
 
+# job-qmp.c
+qmp_job_cancel(void *job) "job %p"
+qmp_job_pause(void *job) "job %p"
+qmp_job_resume(void *job) "job %p"
+qmp_job_complete(void *job) "job %p"
+qmp_job_finalize(void *job) "job %p"
+qmp_job_dismiss(void *job) "job %p"
+
+
 ### Guest events, keep at bottom
 
 
-- 
2.13.6

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

* [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (38 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 23:09   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 41/42] iotests: Move qmp_to_opts() to VM Kevin Wolf
                   ` (2 subsequent siblings)
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This adds a minimal query-jobs implementation that shouldn't pose many
design questions. It can later be extended to expose more information,
and especially job-specific information.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 qapi/block-core.json | 18 ----------------
 qapi/job.json        | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/qemu/job.h   |  3 +++
 job-qmp.c            | 54 +++++++++++++++++++++++++++++++++++++++++++++++
 job.c                |  2 +-
 5 files changed, 117 insertions(+), 19 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index f2da7d696d..d38dfa7836 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1051,24 +1051,6 @@
   'data': ['top', 'full', 'none', 'incremental'] }
 
 ##
-# @JobType:
-#
-# Type of a background job.
-#
-# @commit: block commit job type, see "block-commit"
-#
-# @stream: block stream job type, see "block-stream"
-#
-# @mirror: drive mirror job type, see "drive-mirror"
-#
-# @backup: drive backup job type, see "drive-backup"
-#
-# Since: 1.7
-##
-{ 'enum': 'JobType',
-  'data': ['commit', 'stream', 'mirror', 'backup'] }
-
-##
 # @JobVerb:
 #
 # Represents command verbs that can be applied to a job.
diff --git a/qapi/job.json b/qapi/job.json
index 7b84158292..742fa97768 100644
--- a/qapi/job.json
+++ b/qapi/job.json
@@ -5,6 +5,24 @@
 ##
 
 ##
+# @JobType:
+#
+# Type of a background job.
+#
+# @commit: block commit job type, see "block-commit"
+#
+# @stream: block stream job type, see "block-stream"
+#
+# @mirror: drive mirror job type, see "drive-mirror"
+#
+# @backup: drive backup job type, see "drive-backup"
+#
+# Since: 1.7
+##
+{ 'enum': 'JobType',
+  'data': ['commit', 'stream', 'mirror', 'backup'] }
+
+##
 # @JobStatus:
 #
 # Indicates the present state of a given job in its lifetime.
@@ -175,3 +193,44 @@
 # Since: 2.13
 ##
 { 'command': 'job-finalize', 'data': { 'id': 'str' } }
+
+##
+# @JobInfo:
+#
+# Information about a job.
+#
+# @id:                  The job identifier
+#
+# @type:                The job type
+#
+# @status:              Current job state/status
+#
+# @current-progress:    The current progress value
+#
+# @total-progress:      The maximum progress value
+#
+# @error:               If this field is present, the job failed; if it is
+#                       still missing in the CONCLUDED state, this indicates
+#                       successful completion.
+#
+#                       The value is a human-readable error message to describe
+#                       the reason for the job failure. It should not be parsed
+#                       by applications.
+#
+# Since: 2.13
+##
+{ 'struct': 'JobInfo',
+  'data': { 'id': 'str', 'type': 'JobType', 'status': 'JobStatus',
+            'current-progress': 'int', 'total-progress': 'int',
+            '*error': 'str' } }
+
+##
+# @query-jobs:
+#
+# Return information about jobs.
+#
+# Returns: a list with a @JobInfo for each active job
+#
+# Since: 2.13
+##
+{ 'command': 'query-jobs', 'returns': ['JobInfo'] }
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 9e61663f3a..da1c56f515 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -391,6 +391,9 @@ JobType job_type(Job *job);
 /** Returns the enum string for the JobType of a given Job. */
 const char *job_type_str(Job *job);
 
+/** Returns true if the job should not be visible to the management layer. */
+bool job_is_internal(Job *job);
+
 /** Returns whether the job is scheduled for cancellation. */
 bool job_is_cancelled(Job *job);
 
diff --git a/job-qmp.c b/job-qmp.c
index f32cb8b73e..7425a2a490 100644
--- a/job-qmp.c
+++ b/job-qmp.c
@@ -138,3 +138,57 @@ void qmp_job_dismiss(const char *id, Error **errp)
     job_dismiss(&job, errp);
     aio_context_release(aio_context);
 }
+
+static JobInfo *job_query_single(Job *job, Error **errp)
+{
+    JobInfo *info;
+    const char *errmsg = NULL;
+
+    assert (!job_is_internal(job));
+
+    if (job->ret < 0) {
+        errmsg = strerror(-job->ret);
+    }
+
+    info = g_new(JobInfo, 1);
+    *info = (JobInfo) {
+        .id                 = g_strdup(job->id),
+        .type               = job_type(job),
+        .status             = job->status,
+        .current_progress   = job->progress_current,
+        .total_progress     = job->progress_total,
+        .has_error          = !!errmsg,
+        .error              = g_strdup(errmsg),
+    };
+
+    return info;
+}
+
+JobInfoList *qmp_query_jobs(Error **errp)
+{
+    JobInfoList *head = NULL, **p_next = &head;
+    Job *job;
+
+    for (job = job_next(NULL); job; job = job_next(job)) {
+        JobInfoList *elem;
+        AioContext *aio_context;
+
+        if (job_is_internal(job)) {
+            continue;
+        }
+        elem = g_new0(JobInfoList, 1);
+        aio_context = job->aio_context;
+        aio_context_acquire(aio_context);
+        elem->value = job_query_single(job, errp);
+        aio_context_release(aio_context);
+        if (!elem->value) {
+            g_free(elem);
+            qapi_free_JobInfoList(head);
+            return NULL;
+        }
+        *p_next = elem;
+        p_next = &elem->next;
+    }
+
+    return head;
+}
diff --git a/job.c b/job.c
index 4dc45648e9..178d540dc7 100644
--- a/job.c
+++ b/job.c
@@ -158,7 +158,7 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)
     return rc;
 }
 
-static bool job_is_internal(Job *job)
+bool job_is_internal(Job *job)
 {
     return (job->id == NULL);
 }
-- 
2.13.6

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

* [Qemu-devel] [PATCH 41/42] iotests: Move qmp_to_opts() to VM
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (39 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 23:11   ` Max Reitz
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs Kevin Wolf
  2018-05-15 14:15 ` [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

qmp_to_opts() used to be a method of QMPTestCase, but recently we
started to add more Python test cases that don't make use of
QMPTestCase. In order to make the method usable there, move it to VM.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 tests/qemu-iotests/041        |  6 +++---
 tests/qemu-iotests/155        |  2 +-
 tests/qemu-iotests/iotests.py | 45 ++++++++++++++++++++++---------------------
 3 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
index e94587950c..c20ac7da87 100755
--- a/tests/qemu-iotests/041
+++ b/tests/qemu-iotests/041
@@ -1030,9 +1030,9 @@ class TestOrphanedSource(iotests.QMPTestCase):
                  'read-only': 'on' }
 
         self.vm = iotests.VM()
-        self.vm.add_blockdev(self.qmp_to_opts(blk0))
-        self.vm.add_blockdev(self.qmp_to_opts(blk1))
-        self.vm.add_blockdev(self.qmp_to_opts(blk2))
+        self.vm.add_blockdev(self.vm.qmp_to_opts(blk0))
+        self.vm.add_blockdev(self.vm.qmp_to_opts(blk1))
+        self.vm.add_blockdev(self.vm.qmp_to_opts(blk2))
         self.vm.launch()
 
     def tearDown(self):
diff --git a/tests/qemu-iotests/155 b/tests/qemu-iotests/155
index 42dae04c83..63a5b5e2c0 100755
--- a/tests/qemu-iotests/155
+++ b/tests/qemu-iotests/155
@@ -63,7 +63,7 @@ class BaseClass(iotests.QMPTestCase):
                     'driver': iotests.imgfmt,
                     'file': {'driver': 'file',
                              'filename': source_img}}
-        self.vm.add_blockdev(self.qmp_to_opts(blockdev))
+        self.vm.add_blockdev(self.vm.qmp_to_opts(blockdev))
         self.vm.add_device('virtio-blk,id=qdev0,drive=source')
         self.vm.launch()
 
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index b25d48a91b..7040f71b57 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -363,6 +363,27 @@ class VM(qtest.QEMUQtestMachine):
         return self.qmp('human-monitor-command',
                         command_line='qemu-io %s "%s"' % (drive, cmd))
 
+    def flatten_qmp_object(self, obj, output=None, basestr=''):
+        if output is None:
+            output = dict()
+        if isinstance(obj, list):
+            for i in range(len(obj)):
+                self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
+        elif isinstance(obj, dict):
+            for key in obj:
+                self.flatten_qmp_object(obj[key], output, basestr + key + '.')
+        else:
+            output[basestr[:-1]] = obj # Strip trailing '.'
+        return output
+
+    def qmp_to_opts(self, obj):
+        obj = self.flatten_qmp_object(obj)
+        output_list = list()
+        for key in obj:
+            output_list += [key + '=' + obj[key]]
+        return ','.join(output_list)
+
+
 
 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
 
@@ -390,26 +411,6 @@ class QMPTestCase(unittest.TestCase):
                     self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
         return d
 
-    def flatten_qmp_object(self, obj, output=None, basestr=''):
-        if output is None:
-            output = dict()
-        if isinstance(obj, list):
-            for i in range(len(obj)):
-                self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
-        elif isinstance(obj, dict):
-            for key in obj:
-                self.flatten_qmp_object(obj[key], output, basestr + key + '.')
-        else:
-            output[basestr[:-1]] = obj # Strip trailing '.'
-        return output
-
-    def qmp_to_opts(self, obj):
-        obj = self.flatten_qmp_object(obj)
-        output_list = list()
-        for key in obj:
-            output_list += [key + '=' + obj[key]]
-        return ','.join(output_list)
-
     def assert_qmp_absent(self, d, path):
         try:
             result = self.dictpath(d, path)
@@ -444,8 +445,8 @@ class QMPTestCase(unittest.TestCase):
         '''Asserts that the given filename is a json: filename and that its
            content is equal to the given reference object'''
         self.assertEqual(json_filename[:5], 'json:')
-        self.assertEqual(self.flatten_qmp_object(json.loads(json_filename[5:])),
-                         self.flatten_qmp_object(reference))
+        self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
+                         self.vm.flatten_qmp_object(reference))
 
     def cancel_and_wait(self, drive='drive0', force=False, resume=False):
         '''Cancel a block job and wait for it to finish, returning the event'''
-- 
2.13.6

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

* [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (40 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 41/42] iotests: Move qmp_to_opts() to VM Kevin Wolf
@ 2018-05-09 16:26 ` Kevin Wolf
  2018-05-14 23:44   ` Max Reitz
  2018-05-15 14:15 ` [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-09 16:26 UTC (permalink / raw)
  To: qemu-block; +Cc: kwolf, mreitz, eblake, jsnow, armbru, jcody, qemu-devel

This adds a test case that tests the new job-* QMP commands with
mirror and backup block jobs.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 tests/qemu-iotests/219     | 201 ++++++++++++++++++++++++++++
 tests/qemu-iotests/219.out | 327 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 529 insertions(+)
 create mode 100755 tests/qemu-iotests/219
 create mode 100644 tests/qemu-iotests/219.out

diff --git a/tests/qemu-iotests/219 b/tests/qemu-iotests/219
new file mode 100755
index 0000000000..6cfe54b4db
--- /dev/null
+++ b/tests/qemu-iotests/219
@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Creator/Owner: Kevin Wolf <kwolf@redhat.com>
+#
+# Check using the job-* QMP commands with block jobs
+
+import iotests
+
+iotests.verify_image_format(supported_fmts=['qcow2'])
+
+def pause_wait(vm, job_id):
+    with iotests.Timeout(3, "Timeout waiting for job to pause"):
+        while True:
+            result = vm.qmp('query-jobs')
+            for job in result['return']:
+                if job['id'] == job_id and job['status'] in ['paused', 'standby']:
+                    return job
+
+# Test that block-job-pause/resume and job-pause/resume can be mixed
+def test_pause_resume(vm):
+    for pause_cmd, pause_arg in [('block-job-pause', 'device'),
+                                 ('job-pause', 'id')]:
+        for resume_cmd, resume_arg in [('block-job-resume', 'device'),
+                                       ('job-resume', 'id')]:
+            iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd))
+
+            iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'}))
+            pause_wait(vm, 'job0')
+            iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+            iotests.log(vm.qmp('query-jobs'))
+
+            iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'}))
+            iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+            iotests.log(vm.qmp('query-jobs'))
+
+def test_job_lifecycle(vm, job, job_args, has_ready=False):
+    iotests.log('')
+    iotests.log('')
+    iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' %
+                (job,
+                 job_args.get('auto-finalize', True),
+                 job_args.get('auto-dismiss', True)))
+    iotests.log(vm.qmp(job, job_id='job0', **job_args))
+    iotests.log(vm.qmp('query-jobs'))
+
+    # undefined -> created -> running
+    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+
+    # RUNNING state:
+    # pause/resume should work, complete/finalize/dismiss should error out
+    iotests.log('')
+    iotests.log('Pause/resume in RUNNING')
+    test_pause_resume(vm)
+
+    iotests.log(vm.qmp('job-complete', id='job0'))
+    iotests.log(vm.qmp('job-finalize', id='job0'))
+    iotests.log(vm.qmp('job-dismiss', id='job0'))
+
+    iotests.log(vm.qmp('block-job-complete', device='job0'))
+    iotests.log(vm.qmp('block-job-finalize', id='job0'))
+    iotests.log(vm.qmp('block-job-dismiss', id='job0'))
+
+    # Let the job complete (or transition to READY if it supports that)
+    iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0))
+    if has_ready:
+        iotests.log('')
+        iotests.log('Waiting for READY state...')
+        vm.event_wait('BLOCK_JOB_READY')
+        iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+        iotests.log(vm.qmp('query-jobs'))
+
+        # READY state:
+        # pause/resume/complete should work, finalize/dismiss should error out
+        iotests.log('')
+        iotests.log('Pause/resume in READY')
+        test_pause_resume(vm)
+
+        iotests.log(vm.qmp('job-finalize', id='job0'))
+        iotests.log(vm.qmp('job-dismiss', id='job0'))
+
+        iotests.log(vm.qmp('block-job-finalize', id='job0'))
+        iotests.log(vm.qmp('block-job-dismiss', id='job0'))
+
+        # Transition to WAITING
+        iotests.log(vm.qmp('job-complete', id='job0'))
+
+    # Move to WAITING and PENDING state
+    iotests.log('')
+    iotests.log('Waiting for PENDING state...')
+    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+
+    if not job_args.get('auto-finalize', True):
+        # PENDING state:
+        # finalize should work, pause/complete/dismiss should error out
+        iotests.log(vm.qmp('query-jobs'))
+
+        iotests.log(vm.qmp('job-pause', id='job0'))
+        iotests.log(vm.qmp('job-complete', id='job0'))
+        iotests.log(vm.qmp('job-dismiss', id='job0'))
+
+        iotests.log(vm.qmp('block-job-pause', device='job0'))
+        iotests.log(vm.qmp('block-job-complete', device='job0'))
+        iotests.log(vm.qmp('block-job-dismiss', id='job0'))
+
+        # Transition to CONCLUDED
+        iotests.log(vm.qmp('job-finalize', id='job0'))
+
+
+    # Move to CONCLUDED state
+    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+
+    if not job_args.get('auto-dismiss', True):
+        # CONCLUDED state:
+        # dismiss should work, pause/complete/finalize should error out
+        iotests.log(vm.qmp('query-jobs'))
+
+        iotests.log(vm.qmp('job-pause', id='job0'))
+        iotests.log(vm.qmp('job-complete', id='job0'))
+        iotests.log(vm.qmp('job-finalize', id='job0'))
+
+        iotests.log(vm.qmp('block-job-pause', device='job0'))
+        iotests.log(vm.qmp('block-job-complete', device='job0'))
+        iotests.log(vm.qmp('block-job-finalize', id='job0'))
+
+        # Transition to NULL
+        iotests.log(vm.qmp('job-dismiss', id='job0'))
+
+    # Move to NULL state
+    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
+    iotests.log(vm.qmp('query-jobs'))
+
+
+with iotests.FilePath('disk.img') as disk_path, \
+     iotests.FilePath('copy.img') as copy_path, \
+     iotests.VM() as vm:
+
+    img_size = '4M'
+    iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size)
+    iotests.qemu_io('-c', 'write 0 %s' % (img_size),
+                    '-f', iotests.imgfmt, disk_path)
+
+    iotests.log('Launching VM...')
+    vm.add_blockdev(vm.qmp_to_opts({
+        'driver': iotests.imgfmt,
+        'node-name': 'drive0-node',
+        'file': {
+            'driver': 'file',
+            'filename': disk_path,
+        },
+    }))
+    vm.launch()
+
+    # In order to keep things deterministic (especially progress in query-job,
+    # but related to this also automatic state transitions like job
+    # completion), but still get pause points often enough to avoid making this
+    # test veey slow, it's important to have the right ratio between speed and
+    # buf_size.
+    #
+    # For backup, buf_size is hard-coded to the source image cluser size (64k),
+    # so we'll pick the same for mirror. The slice time, i.e. the granularity
+    # of the rate limiting is 100ms. With a speed of 256k per second, we can
+    # get four pause points per second. This gives us 250ms per iteration,
+    # which should be enough to stay deterministic.
+
+    test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={
+        'device': 'drive0-node',
+        'target': copy_path,
+        'sync': 'full',
+        'speed': 262144,
+        'buf_size': 65536,
+    })
+
+    for auto_finalize in [True, False]:
+        for auto_dismiss in [True, False]:
+            test_job_lifecycle(vm, 'drive-backup', job_args={
+                'device': 'drive0-node',
+                'target': copy_path,
+                'sync': 'full',
+                'speed': 262144,
+                'auto-finalize': auto_finalize,
+                'auto-dismiss': auto_dismiss,
+            })
+
+    vm.shutdown()
diff --git a/tests/qemu-iotests/219.out b/tests/qemu-iotests/219.out
new file mode 100644
index 0000000000..e244be9ce8
--- /dev/null
+++ b/tests/qemu-iotests/219.out
@@ -0,0 +1,327 @@
+Launching VM...
+
+
+Starting block job: drive-mirror (auto-finalize: True; auto-dismiss: True)
+{u'return': {}}
+{u'return': [{u'status': u'running', u'current-progress': 0, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'created', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+
+Pause/resume in RUNNING
+=== Testing block-job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 65536, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+=== Testing block-job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+=== Testing job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+=== Testing job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 327680, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+
+Waiting for READY state...
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+
+Pause/resume in READY
+=== Testing block-job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+=== Testing block-job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+=== Testing job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+=== Testing job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+
+Waiting for PENDING state...
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': []}
+
+
+Starting block job: drive-backup (auto-finalize: True; auto-dismiss: True)
+{u'return': {}}
+{u'return': [{u'status': u'running', u'current-progress': 0, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'created', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+
+Pause/resume in RUNNING
+=== Testing block-job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 65536, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing block-job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 327680, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+
+Waiting for PENDING state...
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': []}
+
+
+Starting block job: drive-backup (auto-finalize: True; auto-dismiss: False)
+{u'return': {}}
+{u'return': [{u'status': u'running', u'current-progress': 0, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'created', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+
+Pause/resume in RUNNING
+=== Testing block-job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 65536, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing block-job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 327680, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+
+Waiting for PENDING state...
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'concluded', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'finalize'"}}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': []}
+
+
+Starting block job: drive-backup (auto-finalize: False; auto-dismiss: True)
+{u'return': {}}
+{u'return': [{u'status': u'running', u'current-progress': 0, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'created', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+
+Pause/resume in RUNNING
+=== Testing block-job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 65536, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing block-job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 327680, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+
+Waiting for PENDING state...
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'pending', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': []}
+
+
+Starting block job: drive-backup (auto-finalize: False; auto-dismiss: False)
+{u'return': {}}
+{u'return': [{u'status': u'running', u'current-progress': 0, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'created', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+
+Pause/resume in RUNNING
+=== Testing block-job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 65536, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing block-job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 131072, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/block-job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 196608, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+=== Testing job-pause/job-resume ===
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'paused', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'paused', u'current-progress': 262144, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'running', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'running', u'current-progress': 327680, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'running' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+
+Waiting for PENDING state...
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'pending', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'dismiss'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'pending' cannot accept command verb 'dismiss'"}}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': [{u'status': u'concluded', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'backup'}]}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'finalize'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'pause'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'complete'"}}
+{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'concluded' cannot accept command verb 'finalize'"}}
+{u'return': {}}
+{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
+{u'return': []}
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 5daef24020..9e100f761f 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -213,3 +213,4 @@
 212 rw auto quick
 213 rw auto quick
 218 rw auto quick
+219 rw auto
-- 
2.13.6

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

* Re: [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize()
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
@ 2018-05-09 17:28   ` Eric Blake
  2018-05-11 21:55   ` Max Reitz
  2018-05-11 22:29   ` John Snow
  2 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-09 17:28 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block
  Cc: mreitz, jsnow, armbru, jcody, qemu-devel, qemu-stable

On 05/09/2018 11:25 AM, Kevin Wolf wrote:
> Every job gets a non-NULL job->txn on creation, but it doesn't
> necessarily keep it until it is decommissioned: Finalising a job removes
> it from its transaction. Therefore, calling 'blockdev-job-finalize' a
> second time on an already concluded job causes an assertion failure.
> 
> Remove job->txn from the assertion in block_job_finalize() to fix this.
> block_job_do_finalize() still has the same assertion, but if a job is
> already removed from its transaction, block_job_apply_verb() will
> already error out before we run into that assertion.

CC: qemu-stable@nongnu.org

> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>   blockjob.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 

Reviewed-by: Eric Blake <eblake@redhat.com>

> diff --git a/blockjob.c b/blockjob.c
> index 4de48166b2..b38ed7e265 100644
> --- a/blockjob.c
> +++ b/blockjob.c
> @@ -702,7 +702,7 @@ void block_job_complete(BlockJob *job, Error **errp)
>   
>   void block_job_finalize(BlockJob *job, Error **errp)
>   {
> -    assert(job && job->id && job->txn);
> +    assert(job && job->id);
>       if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
>           return;
>       }
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all() Kevin Wolf
@ 2018-05-09 17:30   ` Eric Blake
  2018-05-11 22:30   ` Max Reitz
  1 sibling, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-09 17:30 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> Commit 81193349 removed the only use of block_job_pause/resume_all(),
> which was in bdrv_drain_all(). The functions are now unused and can be
> removed.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>   include/block/blockjob_int.h | 14 --------------
>   blockjob.c                   | 27 ---------------------------
>   2 files changed, 41 deletions(-)

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize()
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
  2018-05-09 17:28   ` Eric Blake
@ 2018-05-11 21:55   ` Max Reitz
  2018-05-11 22:29   ` John Snow
  2 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 21:55 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:25, Kevin Wolf wrote:
> Every job gets a non-NULL job->txn on creation, but it doesn't
> necessarily keep it until it is decommissioned: Finalising a job removes
> it from its transaction. Therefore, calling 'blockdev-job-finalize' a
> second time on an already concluded job causes an assertion failure.
> 
> Remove job->txn from the assertion in block_job_finalize() to fix this.
> block_job_do_finalize() still has the same assertion, but if a job is
> already removed from its transaction, block_job_apply_verb() will
> already error out before we run into that assertion.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  blockjob.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)

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


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

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

* Re: [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access Kevin Wolf
@ 2018-05-11 22:12   ` Max Reitz
  2018-05-14 10:16     ` Kevin Wolf
  2018-05-14 19:14   ` John Snow
  1 sibling, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:12 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:25, Kevin Wolf wrote:
> Block job drivers are not expected to mess with the internals of the
> BlockJob object, so provide wrapper functions for one of the cases where
> they still do it: Updating the progress counter.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>
> ---
>  include/block/blockjob.h | 19 +++++++++++++++++++
>  block/backup.c           | 22 +++++++++++++---------
>  block/commit.c           | 16 ++++++++--------
>  block/mirror.c           | 11 +++++------
>  block/stream.c           | 14 ++++++++------
>  blockjob.c               | 10 ++++++++++
>  6 files changed, 63 insertions(+), 29 deletions(-)
> 

[...]

> diff --git a/block/backup.c b/block/backup.c
> index 453cd62c24..5d95805472 100644
> --- a/block/backup.c
> +++ b/block/backup.c
>

[...]

> @@ -420,8 +421,9 @@ static void backup_incremental_init_copy_bitmap(BackupBlockJob *job)
>          bdrv_set_dirty_iter(dbi, next_cluster * job->cluster_size);
>      }
>  
> -    job->common.offset = job->common.len -
> -                         hbitmap_count(job->copy_bitmap) * job->cluster_size;
> +    /* TODO block_job_progress_set_remaining() would make more sense */

Extremely true, especially considering that at least there was an
assignment before.

> +    block_job_progress_update(&job->common,
> +        job->len - hbitmap_count(job->copy_bitmap) * job->cluster_size);

Now, with an incremental update, you have to know that the progress was
0 before this call to make any sense of it.

I could ask: Why don't you just resolve the TODO immediately with

    block_job_progress_set_remaining(&job->common,
        hbitmap_count(job->copy_bitmap) * job->cluster_size);

?

I suppose one possible answer is that this series has 42 patches as it
is, but I have to say that it took me more time to figure this hunk out
than it would have taken me to acknowledge the above change.

Considering that job->len and job->common.len are now separate after
this patch, and that there is only a single other
block_job_progress_update() call in this file, I can't see any side effects.

>  
>      bdrv_dirty_iter_free(dbi);
>  }

[...]

> diff --git a/block/mirror.c b/block/mirror.c
> index 99da9c0858..77ee9b1791 100644
> --- a/block/mirror.c
> +++ b/block/mirror.c

[...]

> @@ -792,11 +792,10 @@ static void coroutine_fn mirror_run(void *opaque)
>          block_job_pause_point(&s->common);
>  
>          cnt = bdrv_get_dirty_count(s->dirty_bitmap);
> -        /* s->common.offset contains the number of bytes already processed so
> -         * far, cnt is the number of dirty bytes remaining and
> -         * s->bytes_in_flight is the number of bytes currently being
> -         * processed; together those are the current total operation length */
> -        s->common.len = s->common.offset + s->bytes_in_flight + cnt;
> +        /* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
> +         * the number of bytes currently being processed; together those are
> +         * the current total operation length */

No, together, those are the current remaining operation length.

With that fixed:

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

> +        block_job_progress_set_remaining(&s->common, s->bytes_in_flight + cnt);
>  
>          /* Note that even when no rate limit is applied we need to yield
>           * periodically with no pending I/O so that bdrv_drain_all() returns.


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

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

* Re: [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob Kevin Wolf
@ 2018-05-11 22:14   ` Max Reitz
  2018-05-14 19:19   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:14 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:25, Kevin Wolf wrote:
> Every block job has a RateLimit, and they all do the exact same thing
> with it, so it should be common infrastructure. Move the struct field
> for a start.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>
> ---
>  include/block/blockjob.h | 4 ++++
>  block/backup.c           | 5 ++---
>  block/commit.c           | 5 ++---
>  block/mirror.c           | 6 +++---
>  block/stream.c           | 5 ++---
>  5 files changed, 13 insertions(+), 12 deletions(-)

Instead of finally getting rid of block job throttling, you make it
central functionality?  Bah, I say, bah! ;-)

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


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

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

* Re: [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally Kevin Wolf
@ 2018-05-11 22:19   ` Max Reitz
  2018-05-14 19:27   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:19 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:25, Kevin Wolf wrote:
> All block job drivers support .set_speed and all of them duplicate the
> same code to implement it. Move that code to blockjob.c and remove the
> now useless callback.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>
> ---
>  include/block/blockjob.h     |  2 ++
>  include/block/blockjob_int.h |  3 ---
>  block/backup.c               | 13 -------------
>  block/commit.c               | 14 --------------
>  block/mirror.c               | 14 --------------
>  block/stream.c               | 14 --------------
>  blockjob.c                   | 12 ++++--------
>  7 files changed, 6 insertions(+), 66 deletions(-)
> 
> diff --git a/include/block/blockjob.h b/include/block/blockjob.h
> index 22bf418209..5aa8a6aaec 100644
> --- a/include/block/blockjob.h
> +++ b/include/block/blockjob.h
> @@ -29,6 +29,8 @@
>  #include "block/block.h"
>  #include "qemu/ratelimit.h"
>  
> +#define SLICE_TIME 100000000ULL /* ns */
> +
>  typedef struct BlockJobDriver BlockJobDriver;
>  typedef struct BlockJobTxn BlockJobTxn;

SLICE_TIME can be anything.  I don't like something that can be anything
to be in a header file.  I can see that you still need it in mirror, so
it needs to be in a header; but maybe rename it to...
THROTTLE_SLICE_TIME?  At least JOB_SLICE_TIME?

Apart from that:

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


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

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

* Re: [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay() Kevin Wolf
@ 2018-05-11 22:25   ` Max Reitz
  2018-05-14 19:36   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:25 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This gets us rid of more direct accesses to BlockJob fields from the
> job drivers.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>
> ---
>  include/block/blockjob_int.h |  8 ++++++++
>  block/backup.c               | 18 +++++++-----------
>  block/commit.c               |  4 ++--
>  block/mirror.c               |  5 +----
>  block/stream.c               |  4 ++--
>  blockjob.c                   |  9 +++++++++
>  6 files changed, 29 insertions(+), 19 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver() Kevin Wolf
@ 2018-05-11 22:28   ` Max Reitz
  2018-05-14 19:43   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:28 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> The backup block job directly accesses the driver field in BlockJob. Add
> a wrapper for getting it.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>
> ---
>  include/block/blockjob.h | 7 +++++++
>  block/backup.c           | 8 +++++---
>  blockjob.c               | 5 +++++
>  3 files changed, 17 insertions(+), 3 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize()
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
  2018-05-09 17:28   ` Eric Blake
  2018-05-11 21:55   ` Max Reitz
@ 2018-05-11 22:29   ` John Snow
  2 siblings, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-11 22:29 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:25 PM, Kevin Wolf wrote:
> Every job gets a non-NULL job->txn on creation, but it doesn't
> necessarily keep it until it is decommissioned: Finalising a job removes
> it from its transaction. Therefore, calling 'blockdev-job-finalize' a
> second time on an already concluded job causes an assertion failure.
> 
> Remove job->txn from the assertion in block_job_finalize() to fix this.
> block_job_do_finalize() still has the same assertion, but if a job is
> already removed from its transaction, block_job_apply_verb() will
> already error out before we run into that assertion.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

> ---
>  blockjob.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/blockjob.c b/blockjob.c
> index 4de48166b2..b38ed7e265 100644
> --- a/blockjob.c
> +++ b/blockjob.c
> @@ -702,7 +702,7 @@ void block_job_complete(BlockJob *job, Error **errp)
>  
>  void block_job_finalize(BlockJob *job, Error **errp)
>  {
> -    assert(job && job->id && job->txn);
> +    assert(job && job->id);
>      if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
>          return;
>      }
> 

Oh, ack. Good catch.

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

* Re: [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all() Kevin Wolf
  2018-05-09 17:30   ` Eric Blake
@ 2018-05-11 22:30   ` Max Reitz
  2018-05-14 19:44     ` John Snow
  2018-05-14 19:45     ` Eric Blake
  1 sibling, 2 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:30 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> Commit 81193349 removed the only use of block_job_pause/resume_all(),
> which was in bdrv_drain_all(). The functions are now unused and can be
> removed.

I have a strange liking for all-digit commit hash prefixes.

> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob_int.h | 14 --------------
>  blockjob.c                   | 27 ---------------------------
>  2 files changed, 41 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create() Kevin Wolf
@ 2018-05-11 22:46   ` Max Reitz
  2018-05-14 12:58     ` Kevin Wolf
  2018-05-14 19:57   ` John Snow
  1 sibling, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:46 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This is the first step towards creating an infrastructure for generic
> background jobs that aren't tied to a block device. For now, Job only
> stores its ID and JobDriver, the rest stays in BlockJob.
> 
> The following patches will move over more parts of BlockJob to Job if
> they are meaningful outside the context of a block job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  9 +++----
>  include/block/blockjob_int.h |  4 +--
>  include/qemu/job.h           | 60 ++++++++++++++++++++++++++++++++++++++++++++
>  block/backup.c               |  4 ++-
>  block/commit.c               |  4 ++-
>  block/mirror.c               | 10 +++++---
>  block/stream.c               |  4 ++-
>  blockjob.c                   | 46 ++++++++++++++++-----------------
>  job.c                        | 48 +++++++++++++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  4 ++-
>  tests/test-blockjob-txn.c    |  4 ++-
>  tests/test-blockjob.c        | 12 ++++++---
>  MAINTAINERS                  |  2 ++
>  Makefile.objs                |  2 +-
>  14 files changed, 169 insertions(+), 44 deletions(-)
>  create mode 100644 include/qemu/job.h
>  create mode 100644 job.c
> 
> diff --git a/include/block/blockjob.h b/include/block/blockjob.h
> index 0b57d53084..8acc1a236a 100644
> --- a/include/block/blockjob.h
> +++ b/include/block/blockjob.h

[...]

> @@ -40,6 +41,9 @@ typedef struct BlockJobTxn BlockJobTxn;
>   * Long-running operation on a BlockDriverState.
>   */
>  typedef struct BlockJob {
> +    /** Data belonging to the generic Job infrastructure */
> +    Job job;
> +
>      /** The job type, including the job vtable.  */
>      const BlockJobDriver *driver;

Any reason why you keep this field around?  Shouldn't it be just
DO_UPCAST(const BlockJobDriver, job_driver, job.driver)?

>  
> @@ -47,11 +51,6 @@ typedef struct BlockJob {
>      BlockBackend *blk;
>  
>      /**
> -     * The ID of the block job. May be NULL for internal jobs.
> -     */
> -    char *id;
> -
> -    /**
>       * The coroutine that executes the job.  If not NULL, it is
>       * reentered when busy is false and the job is cancelled.
>       */

[...]

> diff --git a/blockjob.c b/blockjob.c
> index 9943218a34..35d604d05a 100644
> --- a/blockjob.c
> +++ b/blockjob.c

[...]

> @@ -247,7 +247,7 @@ void block_job_unref(BlockJob *job)
>                                          block_job_detach_aio_context, job);
>          blk_unref(job->blk);
>          error_free(job->blocker);
> -        g_free(job->id);
> +        g_free(job->job.id);

Err.  OK.  I put my faith in patch 11.

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

Although I do wonder about BlockJob.driver.  It seems to stay even after
this series...

>          assert(!timer_pending(&job->sleep_timer));
>          g_free(job);
>      }

[...]

> diff --git a/MAINTAINERS b/MAINTAINERS
> index 459e3594e1..a97f60d104 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1369,6 +1369,8 @@ L: qemu-block@nongnu.org

(Cut off here:

M: Jeff Cody <jcody@redhat.com>

Clever!)

>  S: Supported
>  F: blockjob.c
>  F: include/block/blockjob.h
> +F: job.c
> +F: include/block/job.h
>  F: block/backup.c
>  F: block/commit.c
>  F: block/stream.c


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

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

* Re: [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType Kevin Wolf
@ 2018-05-11 22:48   ` Max Reitz
  2018-05-14 20:05   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:48 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> QAPI types aren't externally visible, so we can rename them without
> causing problems. Before we add a job type to Job, rename the enum
> so it can be used for more than just block jobs.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>
> ---
>  qapi/block-core.json         | 14 +++++++-------
>  include/block/blockjob_int.h |  2 +-
>  block/backup.c               |  2 +-
>  block/commit.c               |  2 +-
>  block/mirror.c               |  4 ++--
>  block/stream.c               |  2 +-
>  blockjob.c                   |  6 +++---
>  7 files changed, 16 insertions(+), 16 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type Kevin Wolf
@ 2018-05-11 22:53   ` Max Reitz
  2018-05-14 11:31     ` Kevin Wolf
  2018-05-14 20:12     ` John Snow
  0 siblings, 2 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:53 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the job_type field from BlockJobDriver to JobDriver.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob_int.h |  3 ---
>  include/qemu/job.h           | 11 +++++++++++
>  block/backup.c               |  2 +-
>  block/commit.c               |  2 +-
>  block/mirror.c               |  4 ++--
>  block/stream.c               |  2 +-
>  blockjob.c                   | 16 +++++++---------
>  job.c                        | 10 ++++++++++
>  8 files changed, 33 insertions(+), 17 deletions(-)
> 

[...]

> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index b4b49f19e1..c87e951c8a 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h

[...]

> @@ -57,4 +62,10 @@ struct JobDriver {
>   */
>  void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
>  
> +/** Returns the JobType of a given Job. */
> +JobType job_type(Job *job);
> +
> +/** Returns the enum string for the JobType of a given Job. */
> +const char *job_type_str(Job *job);
> +

Is there a good reason for these not to take a const Job *?

Depending on the answer:

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

>  #endif


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

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

* Re: [Qemu-devel] [PATCH 11/42] job: Add job_delete()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 11/42] job: Add job_delete() Kevin Wolf
@ 2018-05-11 22:56   ` Max Reitz
  2018-05-14 20:15   ` John Snow
  2018-05-16 17:54   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-11 22:56 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves freeing the Job object and its fields from block_job_unref()
> to job_delete().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/qemu/job.h | 3 +++
>  blockjob.c         | 3 +--
>  job.c              | 6 ++++++
>  3 files changed, 10 insertions(+), 2 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access
  2018-05-11 22:12   ` Max Reitz
@ 2018-05-14 10:16     ` Kevin Wolf
  0 siblings, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-14 10:16 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 12.05.2018 um 00:12 hat Max Reitz geschrieben:
> On 2018-05-09 18:25, Kevin Wolf wrote:
> > Block job drivers are not expected to mess with the internals of the
> > BlockJob object, so provide wrapper functions for one of the cases where
> > they still do it: Updating the progress counter.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > Reviewed-by: Eric Blake <eblake@redhat.com>
> > ---
> >  include/block/blockjob.h | 19 +++++++++++++++++++
> >  block/backup.c           | 22 +++++++++++++---------
> >  block/commit.c           | 16 ++++++++--------
> >  block/mirror.c           | 11 +++++------
> >  block/stream.c           | 14 ++++++++------
> >  blockjob.c               | 10 ++++++++++
> >  6 files changed, 63 insertions(+), 29 deletions(-)
> > 
> 
> [...]
> 
> > diff --git a/block/backup.c b/block/backup.c
> > index 453cd62c24..5d95805472 100644
> > --- a/block/backup.c
> > +++ b/block/backup.c
> >
> 
> [...]
> 
> > @@ -420,8 +421,9 @@ static void backup_incremental_init_copy_bitmap(BackupBlockJob *job)
> >          bdrv_set_dirty_iter(dbi, next_cluster * job->cluster_size);
> >      }
> >  
> > -    job->common.offset = job->common.len -
> > -                         hbitmap_count(job->copy_bitmap) * job->cluster_size;
> > +    /* TODO block_job_progress_set_remaining() would make more sense */
> 
> Extremely true, especially considering that at least there was an
> assignment before.
> 
> > +    block_job_progress_update(&job->common,
> > +        job->len - hbitmap_count(job->copy_bitmap) * job->cluster_size);
> 
> Now, with an incremental update, you have to know that the progress was
> 0 before this call to make any sense of it.
> 
> I could ask: Why don't you just resolve the TODO immediately with
> 
>     block_job_progress_set_remaining(&job->common,
>         hbitmap_count(job->copy_bitmap) * job->cluster_size);
> 
> ?
> 
> I suppose one possible answer is that this series has 42 patches as it
> is, but I have to say that it took me more time to figure this hunk out
> than it would have taken me to acknowledge the above change.
> 
> Considering that job->len and job->common.len are now separate after
> this patch, and that there is only a single other
> block_job_progress_update() call in this file, I can't see any side effects.

Basically just because I tried to make the naive change whenever I had
to touch something that isn't what the patch changes as its main
purpose. The old code changed offset rather than len, so I used the
function that does the same.

If I reduced len instead of increasing offset, I suppose that at least a
few test cases would have to be updated etc. and who knows what else
(QMP clients shouldn't rely on the current way, but do they?).

I'd rather not make such a change as a side effect of a patch that tries
to do something quite different.

> >  
> >      bdrv_dirty_iter_free(dbi);
> >  }
> 
> [...]
> 
> > diff --git a/block/mirror.c b/block/mirror.c
> > index 99da9c0858..77ee9b1791 100644
> > --- a/block/mirror.c
> > +++ b/block/mirror.c
> 
> [...]
> 
> > @@ -792,11 +792,10 @@ static void coroutine_fn mirror_run(void *opaque)
> >          block_job_pause_point(&s->common);
> >  
> >          cnt = bdrv_get_dirty_count(s->dirty_bitmap);
> > -        /* s->common.offset contains the number of bytes already processed so
> > -         * far, cnt is the number of dirty bytes remaining and
> > -         * s->bytes_in_flight is the number of bytes currently being
> > -         * processed; together those are the current total operation length */
> > -        s->common.len = s->common.offset + s->bytes_in_flight + cnt;
> > +        /* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
> > +         * the number of bytes currently being processed; together those are
> > +         * the current total operation length */
> 
> No, together, those are the current remaining operation length.

Thanks, will fix.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type
  2018-05-11 22:53   ` Max Reitz
@ 2018-05-14 11:31     ` Kevin Wolf
  2018-05-14 20:12     ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-14 11:31 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 12.05.2018 um 00:53 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > This moves the job_type field from BlockJobDriver to JobDriver.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> >  include/block/blockjob_int.h |  3 ---
> >  include/qemu/job.h           | 11 +++++++++++
> >  block/backup.c               |  2 +-
> >  block/commit.c               |  2 +-
> >  block/mirror.c               |  4 ++--
> >  block/stream.c               |  2 +-
> >  blockjob.c                   | 16 +++++++---------
> >  job.c                        | 10 ++++++++++
> >  8 files changed, 33 insertions(+), 17 deletions(-)
> > 
> 
> [...]
> 
> > diff --git a/include/qemu/job.h b/include/qemu/job.h
> > index b4b49f19e1..c87e951c8a 100644
> > --- a/include/qemu/job.h
> > +++ b/include/qemu/job.h
> 
> [...]
> 
> > @@ -57,4 +62,10 @@ struct JobDriver {
> >   */
> >  void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
> >  
> > +/** Returns the JobType of a given Job. */
> > +JobType job_type(Job *job);
> > +
> > +/** Returns the enum string for the JobType of a given Job. */
> > +const char *job_type_str(Job *job);
> > +
> 
> Is there a good reason for these not to take a const Job *?
> 
> Depending on the answer:
> 
> Reviewed-by: Max Reitz <mreitz@redhat.com>

Not really. I'll change it and take your R-b.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create()
  2018-05-11 22:46   ` Max Reitz
@ 2018-05-14 12:58     ` Kevin Wolf
  0 siblings, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-14 12:58 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 12.05.2018 um 00:46 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > This is the first step towards creating an infrastructure for generic
> > background jobs that aren't tied to a block device. For now, Job only
> > stores its ID and JobDriver, the rest stays in BlockJob.
> > 
> > The following patches will move over more parts of BlockJob to Job if
> > they are meaningful outside the context of a block job.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> >  include/block/blockjob.h     |  9 +++----
> >  include/block/blockjob_int.h |  4 +--
> >  include/qemu/job.h           | 60 ++++++++++++++++++++++++++++++++++++++++++++
> >  block/backup.c               |  4 ++-
> >  block/commit.c               |  4 ++-
> >  block/mirror.c               | 10 +++++---
> >  block/stream.c               |  4 ++-
> >  blockjob.c                   | 46 ++++++++++++++++-----------------
> >  job.c                        | 48 +++++++++++++++++++++++++++++++++++
> >  tests/test-bdrv-drain.c      |  4 ++-
> >  tests/test-blockjob-txn.c    |  4 ++-
> >  tests/test-blockjob.c        | 12 ++++++---
> >  MAINTAINERS                  |  2 ++
> >  Makefile.objs                |  2 +-
> >  14 files changed, 169 insertions(+), 44 deletions(-)
> >  create mode 100644 include/qemu/job.h
> >  create mode 100644 job.c
> > 
> > diff --git a/include/block/blockjob.h b/include/block/blockjob.h
> > index 0b57d53084..8acc1a236a 100644
> > --- a/include/block/blockjob.h
> > +++ b/include/block/blockjob.h
> 
> [...]
> 
> > @@ -40,6 +41,9 @@ typedef struct BlockJobTxn BlockJobTxn;
> >   * Long-running operation on a BlockDriverState.
> >   */
> >  typedef struct BlockJob {
> > +    /** Data belonging to the generic Job infrastructure */
> > +    Job job;
> > +
> >      /** The job type, including the job vtable.  */
> >      const BlockJobDriver *driver;
> 
> Any reason why you keep this field around?  Shouldn't it be just
> DO_UPCAST(const BlockJobDriver, job_driver, job.driver)?

I left it around to avoid unnecessary churn at this point. However, I
intended to remove it at the end of the series, which I seem to have
forgotten. I'll add a patch to this effect.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs Kevin Wolf
@ 2018-05-14 13:59   ` Max Reitz
  2018-05-14 20:42   ` John Snow
  2018-05-16 18:03   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 13:59 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the job list from BlockJob to Job. Now we can check for
> duplicate IDs in job_create().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  3 ---
>  include/qemu/job.h       | 19 +++++++++++++++++++
>  blockjob.c               | 47 +++++++++++++++++++++++++----------------------
>  job.c                    | 31 +++++++++++++++++++++++++++++++
>  4 files changed, 75 insertions(+), 25 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job Kevin Wolf
@ 2018-05-14 14:20   ` Max Reitz
  2018-05-14 20:58   ` John Snow
  2018-05-16 18:11   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 14:20 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves BlockJob.status and the closely related functions
> (block_)job_state_transition() and (block_)job_apply_verb to Job. The
> two QAPI enums are renamed to JobStatus and JobVerb.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  qapi/block-core.json     |  14 +++----
>  include/block/blockjob.h |   3 --
>  include/qemu/job.h       |  13 ++++++
>  blockjob.c               | 102 +++++++++++------------------------------------
>  job.c                    |  56 ++++++++++++++++++++++++++
>  tests/test-blockjob.c    |  39 +++++++++---------
>  block/trace-events       |   2 -
>  trace-events             |   4 ++
>  8 files changed, 122 insertions(+), 111 deletions(-)

block-job-dismiss's documentation still mentions
BLOCK_JOB_STATUS_CONCLUDED, but that's cleaned up in patch 39, so I
don't mind giving a:

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


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

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

* Re: [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 14/42] job: Add reference counting Kevin Wolf
@ 2018-05-14 14:29   ` Max Reitz
  2018-05-14 21:34   ` John Snow
  2018-05-16 18:17   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 14:29 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves reference counting from BlockJob to Job.
> 
> In order to keep calling the BlockJob cleanup code when the job is
> deleted via job_unref(), introduce a new JobDriver.free callback. Every
> block job must use block_job_free() for this callback, this is asserted
> in block_job_create().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     | 21 -------------------
>  include/block/blockjob_int.h |  7 +++++++
>  include/qemu/job.h           | 19 ++++++++++++++++--
>  block/backup.c               |  1 +
>  block/commit.c               |  1 +
>  block/mirror.c               |  2 ++
>  block/stream.c               |  1 +
>  blockjob.c                   | 48 +++++++++++++++++++-------------------------
>  job.c                        | 22 ++++++++++++++++----
>  qemu-img.c                   |  4 ++--
>  tests/test-bdrv-drain.c      |  1 +
>  tests/test-blockjob-txn.c    |  1 +
>  tests/test-blockjob.c        |  6 ++++--
>  13 files changed, 76 insertions(+), 58 deletions(-)
Reviewed-by: Max Reitz <mreitz@redhat.com>


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

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

* Re: [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job Kevin Wolf
@ 2018-05-14 14:39   ` Max Reitz
  2018-05-14 21:53   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 14:39 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> We cannot yet move the whole logic around job cancelling to Job because
> it depends on quite a few other things that are still only in BlockJob,
> but we can move the cancelled field at least.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  8 --------
>  include/block/blockjob_int.h |  8 --------
>  include/qemu/job.h           | 11 +++++++++++
>  block/backup.c               |  6 +++---
>  block/commit.c               |  4 ++--
>  block/mirror.c               | 20 ++++++++++----------
>  block/stream.c               |  4 ++--
>  blockjob.c                   | 28 +++++++++++++---------------
>  job.c                        |  5 +++++
>  tests/test-blockjob-txn.c    |  6 +++---
>  tests/test-blockjob.c        |  2 +-
>  11 files changed, 50 insertions(+), 52 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context Kevin Wolf
@ 2018-05-14 15:20   ` Max Reitz
  2018-05-14 22:02   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 15:20 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> When block jobs need an AioContext, they just take it from their main
> block node. Generic jobs don't have a main block node, so we need to
> assign them an AioContext explicitly.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/qemu/job.h | 7 ++++++-
>  blockjob.c         | 5 ++++-
>  job.c              | 4 +++-
>  3 files changed, 13 insertions(+), 3 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job Kevin Wolf
@ 2018-05-14 15:52   ` Max Reitz
  2018-05-15 12:17     ` Kevin Wolf
  2018-05-14 22:33   ` John Snow
  2018-05-16 18:37   ` Eric Blake
  2 siblings, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-14 15:52 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  5 ----
>  include/block/blockjob_int.h | 19 ---------------
>  include/qemu/job.h           | 20 ++++++++++++++++
>  block/backup.c               |  7 +++---
>  block/commit.c               | 11 +++++----
>  block/mirror.c               | 15 ++++++------
>  block/stream.c               | 14 +++++------
>  blockjob.c                   | 57 ++++----------------------------------------
>  job.c                        | 33 +++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  7 +++---
>  tests/test-blockjob-txn.c    | 13 +++++-----
>  tests/test-blockjob.c        |  7 +++---
>  12 files changed, 98 insertions(+), 110 deletions(-)

[...]

> diff --git a/block/commit.c b/block/commit.c
> index 85baea8f92..d326766e4d 100644
> --- a/block/commit.c
> +++ b/block/commit.c
> @@ -72,9 +72,10 @@ typedef struct {
>      int ret;
>  } CommitCompleteData;
>  
> -static void commit_complete(BlockJob *job, void *opaque)
> +static void commit_complete(Job *job, void *opaque)
>  {
> -    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
> +    CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);

Now this is just abuse.

...but it's not the first time someone packs two container_of() into
one, it appears.  So, whatever, I guess.

> +    BlockJob *bjob = &s->common;
>      CommitCompleteData *data = opaque;
>      BlockDriverState *top = blk_bs(s->top);
>      BlockDriverState *base = blk_bs(s->base);

[...]

> diff --git a/blockjob.c b/blockjob.c
> index d44f5c2e50..6021d885be 100644
> --- a/blockjob.c
> +++ b/blockjob.c

[...]

> @@ -1159,50 +1159,3 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
>      }
>      return action;
>  }
> -
> -typedef struct {
> -    BlockJob *job;
> -    AioContext *aio_context;
> -    BlockJobDeferToMainLoopFn *fn;
> -    void *opaque;
> -} BlockJobDeferToMainLoopData;
> -
> -static void block_job_defer_to_main_loop_bh(void *opaque)
> -{
> -    BlockJobDeferToMainLoopData *data = opaque;
> -    AioContext *aio_context;
> -
> -    /* Prevent race with block_job_defer_to_main_loop() */
> -    aio_context_acquire(data->aio_context);
> -
> -    /* Fetch BDS AioContext again, in case it has changed */
> -    aio_context = blk_get_aio_context(data->job->blk);
> -    if (aio_context != data->aio_context) {
> -        aio_context_acquire(aio_context);
> -    }
> -
> -    data->fn(data->job, data->opaque);
> -
> -    if (aio_context != data->aio_context) {
> -        aio_context_release(aio_context);
> -    }
> -
> -    aio_context_release(data->aio_context);
> -
> -    g_free(data);
> -}
> -
> -void block_job_defer_to_main_loop(BlockJob *job,
> -                                  BlockJobDeferToMainLoopFn *fn,
> -                                  void *opaque)
> -{
> -    BlockJobDeferToMainLoopData *data = g_malloc(sizeof(*data));
> -    data->job = job;
> -    data->aio_context = blk_get_aio_context(job->blk);
> -    data->fn = fn;
> -    data->opaque = opaque;
> -    job->deferred_to_main_loop = true;
> -
> -    aio_bh_schedule_oneshot(qemu_get_aio_context(),
> -                            block_job_defer_to_main_loop_bh, data);
> -}
> diff --git a/job.c b/job.c
> index 6f97a4317e..b074b3ffd7 100644
> --- a/job.c
> +++ b/job.c
> @@ -28,6 +28,7 @@
>  #include "qapi/error.h"
>  #include "qemu/job.h"
>  #include "qemu/id.h"
> +#include "qemu/main-loop.h"
>  #include "trace-root.h"
>  
>  static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
> @@ -170,3 +171,35 @@ void job_unref(Job *job)
>          g_free(job);
>      }
>  }
> +
> +typedef struct {
> +    Job *job;
> +    JobDeferToMainLoopFn *fn;
> +    void *opaque;
> +} JobDeferToMainLoopData;
> +
> +static void job_defer_to_main_loop_bh(void *opaque)
> +{
> +    JobDeferToMainLoopData *data = opaque;
> +    Job *job = data->job;
> +    AioContext *aio_context = job->aio_context;
> +
> +    /* Prevent race with job_defer_to_main_loop() */
> +    aio_context_acquire(aio_context);

I don't have a good feeling about this.  The original code had this
comment above an aio_context_acquire() for a context that might
decidedly not have anything to do with the BB's context;
block_job_defer_to_main_loop()'s description was that it would acquire
the latter, so why did it acquire the former at all?

We wouldn't need this comment here at all, because acquiring this
AioContext is part of the interface.  That's why I don't have a good
feeling.

The best explanation I can come up with is that the original code
acquired the AioContext both of the block device at the time of the BH
(because that needs to be done), and at the time of
block_job_defer_to_main_loop() -- because the latter is probably the
context the block_job_defer_to_main_loop() call came from, so it should
be (b)locked.

But if that's the case, then the same should be done here.  The context
of the job may change between scheduling the BH and the BH being
executed, so we might lock a different context here than the one
job_defer_to_main_loop() ran in (i.e., job->aio_context at the time of
job_defer_to_main_loop() running).  And maybe we should lock that old
context, too -- just like block_job_defer_to_main_loop_bh() did.

Max

> +    data->fn(data->job, data->opaque);
> +    aio_context_release(aio_context);
> +
> +    g_free(data);
> +}
> +
> +void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque)
> +{
> +    JobDeferToMainLoopData *data = g_malloc(sizeof(*data));
> +    data->job = job;
> +    data->fn = fn;
> +    data->opaque = opaque;
> +    job->deferred_to_main_loop = true;
> +
> +    aio_bh_schedule_oneshot(qemu_get_aio_context(),
> +                            job_defer_to_main_loop_bh, data);
> +}


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

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

* Re: [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code " Kevin Wolf
@ 2018-05-14 16:47   ` Max Reitz
  2018-05-14 23:02   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 16:47 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This commit moves some core functions for dealing with the job coroutine
> from BlockJob to Job. This includes primarily entering the coroutine
> (both for the first and reentering) and yielding explicitly and at pause
> points.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  40 ---------
>  include/block/blockjob_int.h |  26 ------
>  include/qemu/job.h           |  76 ++++++++++++++++
>  block/backup.c               |   2 +-
>  block/commit.c               |   4 +-
>  block/mirror.c               |  22 ++---
>  block/replication.c          |   2 +-
>  block/stream.c               |   4 +-
>  blockdev.c                   |   8 +-
>  blockjob.c                   | 201 +++++++------------------------------------
>  job.c                        | 137 +++++++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  38 ++++----
>  tests/test-blockjob-txn.c    |  12 +--
>  tests/test-blockjob.c        |  14 +--
>  14 files changed, 296 insertions(+), 290 deletions(-)

[...]

> diff --git a/job.c b/job.c
> index b074b3ffd7..b4cece1a82 100644
> --- a/job.c
> +++ b/job.c

[...]

> @@ -172,6 +205,110 @@ void job_unref(Job *job)
>      }
>  }
>  
> +void job_enter_cond(Job *job, bool(*fn)(Job *job))
> +{
> +    if (!job_started(job)) {
> +        return;
> +    }
> +    if (job->deferred_to_main_loop) {
> +        return;
> +    }
> +
> +    job_lock();
> +    if (job->busy) {
> +        job_unlock();
> +        return;
> +    }
> +
> +    if (fn && !fn(job)) {
> +        job_unlock();
> +        return;
> +    }
> +
> +    assert(!job->deferred_to_main_loop);
> +    timer_del(&job->sleep_timer);
> +    job->busy = true;
> +    job_unlock();
> +    aio_co_wake(job->co);
> +}
> +
> +/* Yield, and schedule a timer to reenter the coroutine after @ns nanoseconds.
> + * Reentering the job coroutine with block_job_enter() before the timer has

There is no job_enter() yet, but you do reference it below, so this
should probably be adjusted here...

> + * expired is allowed and cancels the timer.
> + *
> + * If @ns is (uint64_t) -1, no timer is scheduled and block_job_enter() must be

...and here.  Or maybe you want to change the job_enter below to a
block_job_enter until there is a job_enter().

With that done (or not, because I don't really care about mid-series
comments);

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

> + * called explicitly. */
> +void coroutine_fn job_do_yield(Job *job, uint64_t ns)
> +{
> +    job_lock();
> +    if (ns != -1) {
> +        timer_mod(&job->sleep_timer, ns);
> +    }
> +    job->busy = false;
> +    job_unlock();
> +    qemu_coroutine_yield();
> +
> +    /* Set by job_enter before re-entering the coroutine.  */
> +    assert(job->busy);
> +}


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

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

* Re: [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns() Kevin Wolf
@ 2018-05-14 16:57   ` Max Reitz
  2018-05-14 23:10   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 16:57 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> There is nothing block layer specific about block_job_sleep_ns(), so
> move the function to Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob_int.h | 11 -----------
>  include/qemu/job.h           | 17 +++++++++++++++++
>  block/backup.c               |  2 +-
>  block/commit.c               |  2 +-
>  block/mirror.c               |  4 ++--
>  block/stream.c               |  2 +-
>  blockjob.c                   | 27 ---------------------------
>  job.c                        | 32 ++++++++++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  8 ++++----
>  tests/test-blockjob-txn.c    |  2 +-
>  tests/test-blockjob.c        |  2 +-
>  11 files changed, 60 insertions(+), 49 deletions(-)

The Job.sleep_timer documentation should be adjusted.  With that done:

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


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

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

* Re: [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job Kevin Wolf
@ 2018-05-14 17:13   ` Max Reitz
  2018-05-14 23:23   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 17:13 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> While we already moved the state related to job pausing to Job, the
> functions to do were still BlockJob only. This commit moves them over to
> Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     | 32 -------------------
>  include/block/blockjob_int.h |  7 +++++
>  include/qemu/job.h           | 37 ++++++++++++++++++++++
>  block/backup.c               |  1 +
>  block/commit.c               |  1 +
>  block/mirror.c               |  2 ++
>  block/stream.c               |  1 +
>  blockdev.c                   |  6 ++--
>  blockjob.c                   | 73 ++++++++++----------------------------------
>  job.c                        | 51 +++++++++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  1 +
>  tests/test-blockjob-txn.c    |  1 +
>  tests/test-blockjob.c        |  6 ++--
>  13 files changed, 125 insertions(+), 94 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed() Kevin Wolf
@ 2018-05-14 17:33   ` Max Reitz
  2018-05-14 23:43   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 17:33 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> Since we introduced an explicit status to block job, BlockJob.completed
> is redundant because it can be derived from the status. Remove the field
> from BlockJob and add a function to derive it from the status at the Job
> level.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  3 ---
>  include/qemu/job.h       |  3 +++
>  blockjob.c               | 16 +++++++---------
>  job.c                    | 22 ++++++++++++++++++++++
>  qemu-img.c               |  4 ++--
>  5 files changed, 34 insertions(+), 14 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job Kevin Wolf
@ 2018-05-14 17:50   ` Max Reitz
  2018-05-16 19:13   ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 17:50 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This renames the BlockJobCreateFlags constants, moves a few JOB_INTERNAL
> checks to job_create() and the auto_{finalize,dismiss} fields from
> BlockJob to Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     | 17 -----------------
>  include/block/blockjob_int.h |  3 +--
>  include/qemu/job.h           | 20 +++++++++++++++++++-
>  block/commit.c               |  2 +-
>  block/mirror.c               |  2 +-
>  block/replication.c          |  4 ++--
>  block/stream.c               |  2 +-
>  blockdev.c                   | 14 +++++++-------
>  blockjob.c                   | 27 +++++++--------------------
>  job.c                        | 11 ++++++++++-
>  qemu-img.c                   |  2 +-
>  tests/test-blockjob-txn.c    |  2 +-
>  tests/test-blockjob.c        |  4 ++--
>  13 files changed, 53 insertions(+), 57 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending() Kevin Wolf
@ 2018-05-14 17:56   ` Max Reitz
  2018-05-16 19:15   ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 17:56 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> block_job_event_pending() doesn't only send a QMP event, but it also
> transitions to the PENDING state. Split the function so that we get one
> part only sending the event (like other block_job_event_* functions) and
> another part than does the state transition.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  blockjob.c | 27 ++++++++++++++++++---------
>  1 file changed, 18 insertions(+), 9 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 24/42] job: Add job_event_*()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 24/42] job: Add job_event_*() Kevin Wolf
@ 2018-05-14 18:09   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 18:09 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> Go through the Job layer in order to send QMP events. For the moment,
> these functions only call a notifier in the BlockJob layer that sends
> the existing commands.
> 
> This uses notifiers rather than JobDriver callbacks because internal
> users of jobs won't receive QMP events, but might still be interested
> in getting notified for the events.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  9 +++++++++
>  include/qemu/job.h       | 18 ++++++++++++++++++
>  blockjob.c               | 42 ++++++++++++++++++++++++++++--------------
>  job.c                    | 19 +++++++++++++++++++
>  4 files changed, 74 insertions(+), 14 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 25/42] job: Move single job finalisation to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 25/42] job: Move single job finalisation to Job Kevin Wolf
@ 2018-05-14 18:46   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 18:46 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the finalisation of a single job from BlockJob to Job.
> 
> Some part of this code depends on job transactions, and job transactions
> call this code, we introduce some temporary calls from Job functions to
> BlockJob ones. This will be fixed once transactions move to Job, too.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |   9 ---
>  include/block/blockjob_int.h |  36 -----------
>  include/qemu/job.h           |  53 +++++++++++++++-
>  block/backup.c               |  22 +++----
>  block/commit.c               |   2 +-
>  block/mirror.c               |   2 +-
>  blockjob.c                   | 142 ++++++++-----------------------------------
>  job.c                        | 100 +++++++++++++++++++++++++++++-
>  qemu-img.c                   |   2 +-
>  tests/test-blockjob.c        |  10 +--
>  10 files changed, 194 insertions(+), 184 deletions(-)

[...]

> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index 1b4397f9a1..12edb822d8 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h
> @@ -29,6 +29,7 @@
>  #include "qapi/qapi-types-block-core.h"
>  #include "qemu/queue.h"
>  #include "qemu/coroutine.h"
> +#include "block/aio.h"
>  
>  typedef struct JobDriver JobDriver;
>  
> @@ -105,6 +106,15 @@ typedef struct Job {
>      /** True if this job should automatically dismiss itself */
>      bool auto_dismiss;
>  
> +    /** ret code passed to block_job_completed. */
> +    int ret;

While there is no such function yet, you might not want to mention a
block_job_* function here anyway.

Apart from that, as far as I can see, this comment remains unchanged
even later in this series when block_job_completed() is removed.

So something needs to be changed somewhere, so here is an anticipating

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


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

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

* Re: [Qemu-devel] [PATCH 26/42] job: Convert block_job_cancel_async() to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 26/42] job: Convert block_job_cancel_async() " Kevin Wolf
@ 2018-05-14 19:00   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 19:00 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> block_job_cancel_async() did two things that were still block job
> specific:
> 
> * Setting job->force. This field makes sense on the Job level, so we can
>   just move it. While at it, rename it to job->force_cancel to make its
>   purpose more obvious.
> 
> * Resetting the I/O status. This can't be moved because generic Jobs
>   don't have an I/O status. What the function really implements is a
>   user resume, except without entering the coroutine. Consequently, it
>   makes sense to call the .user_resume driver callback here which
>   already resets the I/O status.
> 
>   The old block_job_cancel_async() has two separate if statements that
>   check job->iostatus != BLOCK_DEVICE_IO_STATUS_OK and job->user_paused.
>   However, the former condition always implies the latter (as is
>   asserted in block_job_iostatus_reset()), so changing the explicit call
>   of block_job_iostatus_reset() on the former condition with the
>   .user_resume callback on the latter condition is equivalent and
>   doesn't need to access any BlockJob specific state.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  6 ------
>  include/qemu/job.h       |  6 ++++++
>  block/mirror.c           |  4 ++--
>  blockjob.c               | 25 +++++++++++++------------
>  4 files changed, 21 insertions(+), 20 deletions(-)

I'm not quite sure why you keep this function in blockjob.c, when you've
previously moved such static functions over to job.c and made them
temporarily public (e.g. job_state_transition()).

But I don't really care either way, in fact keeping the function in the
same file makes reviewing easier for me, so:

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


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

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

* Re: [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access Kevin Wolf
  2018-05-11 22:12   ` Max Reitz
@ 2018-05-14 19:14   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:14 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:25 PM, Kevin Wolf wrote:
> Block job drivers are not expected to mess with the internals of the
> BlockJob object, so provide wrapper functions for one of the cases where
> they still do it: Updating the progress counter.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>

The backup code takes a hot second to read, but it seems correct. Having
both a common len and a backup len is a bit awful, but only in this
patch diff where you have to deal with both.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob Kevin Wolf
  2018-05-11 22:14   ` Max Reitz
@ 2018-05-14 19:19   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:19 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:25 PM, Kevin Wolf wrote:
> Every block job has a RateLimit, and they all do the exact same thing
> with it, so it should be common infrastructure. Move the struct field
> for a start.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 27/42] job: Add job_drain()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 27/42] job: Add job_drain() Kevin Wolf
@ 2018-05-14 19:26   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 19:26 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> block_job_drain() contains a blk_drain() call which cannot be moved to
> Job, so add a new JobDriver callback JobDriver.drain which has a common
> implementation for all BlockJobs. In addition to this we keep the
> existing BlockJobDriver.drain callback that is called by the common
> drain implementation for all block jobs.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob_int.h | 12 ++++++++++++
>  include/qemu/job.h           | 13 +++++++++++++
>  block/backup.c               |  1 +
>  block/commit.c               |  1 +
>  block/mirror.c               |  2 ++
>  block/stream.c               |  1 +
>  blockjob.c                   | 20 ++++++++++----------
>  job.c                        | 11 +++++++++++
>  tests/test-bdrv-drain.c      |  1 +
>  tests/test-blockjob-txn.c    |  1 +
>  tests/test-blockjob.c        |  2 ++
>  11 files changed, 55 insertions(+), 10 deletions(-)
> 
> diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
> index bf2b762808..38fe22d7e0 100644
> --- a/include/block/blockjob_int.h
> +++ b/include/block/blockjob_int.h
> @@ -65,6 +65,10 @@ struct BlockJobDriver {
>       * If the callback is not NULL, it will be invoked when the job has to be
>       * synchronously cancelled or completed; it should drain BlockDriverStates
>       * as required to ensure progress.
> +     *
> +     * Block jobs must use the default implementation for job_driver.drain,
> +     * which will in turn call this callback after doing generic block job
> +     * stuff.
>       */
>      void (*drain)(BlockJob *job);

I don't really see the point of having two drain callbacks for block
jobs.  Well, it allows an assert() that block_job_drain() is called at
some point, but still.  I'd like block jobs to be not very special, but
this makes them a bit more special than they need to be.

Maybe I'd like it a bit more if there was a macro to automatically set
these mandatory values for block jobs...

But mostly a question of style, so I'll grudgingly give a:

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

>  };


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

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

* Re: [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally
  2018-05-09 16:25 ` [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally Kevin Wolf
  2018-05-11 22:19   ` Max Reitz
@ 2018-05-14 19:27   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:27 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:25 PM, Kevin Wolf wrote:
> All block job drivers support .set_speed and all of them duplicate the
> same code to implement it. Move that code to blockjob.c and remove the
> now useless callback.

🎊

> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay() Kevin Wolf
  2018-05-11 22:25   ` Max Reitz
@ 2018-05-14 19:36   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:36 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This gets us rid of more direct accesses to BlockJob fields from the
> job drivers.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

> ---
>  include/block/blockjob_int.h |  8 ++++++++
>  block/backup.c               | 18 +++++++-----------
>  block/commit.c               |  4 ++--
>  block/mirror.c               |  5 +----
>  block/stream.c               |  4 ++--
>  blockjob.c                   |  9 +++++++++
>  6 files changed, 29 insertions(+), 19 deletions(-)
> 

[...]

> diff --git a/block/backup.c b/block/backup.c
> index 8468fd9f94..cfdb6ecdf5 100644
> --- a/block/backup.c
> +++ b/block/backup.c
> @@ -325,21 +325,17 @@ static void backup_complete(BlockJob *job, void *opaque)
>  
>  static bool coroutine_fn yield_and_check(BackupBlockJob *job)
>  {
> +    uint64_t delay_ns;
> +
>      if (block_job_is_cancelled(&job->common)) {
>          return true;
>      }
>  
> -    /* we need to yield so that bdrv_drain_all() returns.
> -     * (without, VM does not reboot)
> -     */

I'll kind of miss this terse comment. "without, VM does not reboot" is
very direct and to the point. :)

> -    if (job->common.speed) {
> -        uint64_t delay_ns = ratelimit_calculate_delay(&job->common.limit,
> -                                                      job->bytes_read);
> -        job->bytes_read = 0;
> -        block_job_sleep_ns(&job->common, delay_ns);
> -    } else {
> -        block_job_sleep_ns(&job->common, 0);
> -    }
> +    /* We need to yield even for delay_ns = 0 so that bdrv_drain_all() can
> +     * return. Without a yield, the VM would not reboot. */
> +    delay_ns = block_job_ratelimit_get_delay(&job->common, job->bytes_read);
> +    job->bytes_read = 0;
> +    block_job_sleep_ns(&job->common, delay_ns);
>  
>      if (block_job_is_cancelled(&job->common)) {
>          return true;

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

* Re: [Qemu-devel] [PATCH 28/42] job: Move .complete callback to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 28/42] job: Move .complete callback to Job Kevin Wolf
@ 2018-05-14 19:37   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 19:37 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the .complete callback that tells a READY job to complete
> from BlockJobDriver to JobDriver. The wrapper function job_complete()
> doesn't require anything block job specific any more and can be moved
> to Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     | 10 ----------
>  include/block/blockjob_int.h |  6 ------
>  include/qemu/job.h           |  8 ++++++++
>  block/mirror.c               | 10 +++++-----
>  blockdev.c                   |  2 +-
>  blockjob.c                   | 23 +++++------------------
>  job.c                        | 16 ++++++++++++++++
>  tests/test-bdrv-drain.c      |  6 +++---
>  tests/test-blockjob.c        | 10 +++++-----
>  9 files changed, 43 insertions(+), 48 deletions(-)
Reviewed-by: Max Reitz <mreitz@redhat.com>


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

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

* Re: [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver() Kevin Wolf
  2018-05-11 22:28   ` Max Reitz
@ 2018-05-14 19:43   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:43 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> The backup block job directly accesses the driver field in BlockJob. Add
> a wrapper for getting it.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>

I'm not too far into this series yet but I suppose it turns out that
it's more useful to get the driver than to ask the job system what the
job type is.

Probably there's some QAPI reasons for why that's true, too...

Reviewed-by: John Snow <jsnow@redhat.com>.

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

* Re: [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all()
  2018-05-11 22:30   ` Max Reitz
@ 2018-05-14 19:44     ` John Snow
  2018-05-14 19:45     ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:44 UTC (permalink / raw)
  To: Max Reitz, Kevin Wolf, qemu-block; +Cc: jcody, armbru, qemu-devel



On 05/11/2018 06:30 PM, Max Reitz wrote:
> On 2018-05-09 18:26, Kevin Wolf wrote:
>> Commit 81193349 removed the only use of block_job_pause/resume_all(),
>> which was in bdrv_drain_all(). The functions are now unused and can be
>> removed.
> 
> I have a strange liking for all-digit commit hash prefixes.
> 
>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>> ---
>>  include/block/blockjob_int.h | 14 --------------
>>  blockjob.c                   | 27 ---------------------------
>>  2 files changed, 41 deletions(-)
> 

And all deletion diffstats.

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

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all()
  2018-05-11 22:30   ` Max Reitz
  2018-05-14 19:44     ` John Snow
@ 2018-05-14 19:45     ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-14 19:45 UTC (permalink / raw)
  To: Max Reitz, Kevin Wolf, qemu-block; +Cc: jsnow, armbru, jcody, qemu-devel

On 05/11/2018 05:30 PM, Max Reitz wrote:
> On 2018-05-09 18:26, Kevin Wolf wrote:
>> Commit 81193349 removed the only use of block_job_pause/resume_all(),
>> which was in bdrv_drain_all(). The functions are now unused and can be
>> removed.
> 
> I have a strange liking for all-digit commit hash prefixes.

Especially when the first digit is 0, but then the rest includes an 8 or 
9.  (in the past, I've actually had a script transiently break when 
parsing commit ids, and it took me quite a while to figure out why the 
problem disappeared after I rebased, until I finally realized that it 
was because the script was choking on an attempt to parse an invalid 
octal number, only when I got lucky enough to land on a problematic 
commit id)

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 29/42] job: Move job_finish_sync() to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 29/42] job: Move job_finish_sync() " Kevin Wolf
@ 2018-05-14 19:49   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 19:49 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> block_job_finish_sync() doesn't contain anything block job specific any
> more, so it can be moved to Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/qemu/job.h |  9 +++++++++
>  blockjob.c         | 55 +++++++++---------------------------------------------
>  job.c              | 28 +++++++++++++++++++++++++++
>  3 files changed, 46 insertions(+), 46 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create() Kevin Wolf
  2018-05-11 22:46   ` Max Reitz
@ 2018-05-14 19:57   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 19:57 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This is the first step towards creating an infrastructure for generic
> background jobs that aren't tied to a block device. For now, Job only
> stores its ID and JobDriver, the rest stays in BlockJob.
> 
> The following patches will move over more parts of BlockJob to Job if
> they are meaningful outside the context of a block job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

I think I'll be reviewing this series in a manner that trusts that all
of the obvious cleanups get handled later, and only yelp if something
looks distinctly wrong.

Anything that simply doesn't get cleaned up is something we can fix
later, so unless it looks like it's painting us into a corner, it
probably doesn't need to be addressed right away.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 30/42] job: Switch transactions to JobTxn
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 30/42] job: Switch transactions to JobTxn Kevin Wolf
@ 2018-05-14 20:00   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 20:00 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This doesn't actually move any transaction code to Job yet, but it
> renames the type for transactions from BlockJobTxn to JobTxn and makes
> them contain Jobs rather than BlockJobs
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/block_int.h    |  2 +-
>  include/block/blockjob.h     | 11 ++++----
>  include/block/blockjob_int.h |  2 +-
>  include/qemu/job.h           |  3 +++
>  block/backup.c               |  2 +-
>  blockdev.c                   | 14 +++++------
>  blockjob.c                   | 60 +++++++++++++++++++++++---------------------
>  tests/test-blockjob-txn.c    |  8 +++---
>  8 files changed, 54 insertions(+), 48 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType Kevin Wolf
  2018-05-11 22:48   ` Max Reitz
@ 2018-05-14 20:05   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 20:05 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> QAPI types aren't externally visible, so we can rename them without
> causing problems. Before we add a job type to Job, rename the enum
> so it can be used for more than just block jobs.
> 

So we will be registering all jobs in a central place.

> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Eric Blake <eblake@redhat.com>

Seems fine.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type
  2018-05-11 22:53   ` Max Reitz
  2018-05-14 11:31     ` Kevin Wolf
@ 2018-05-14 20:12     ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 20:12 UTC (permalink / raw)
  To: Max Reitz, Kevin Wolf, qemu-block; +Cc: jcody, armbru, qemu-devel



On 05/11/2018 06:53 PM, Max Reitz wrote:
> On 2018-05-09 18:26, Kevin Wolf wrote:
>> This moves the job_type field from BlockJobDriver to JobDriver.
>>
>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

>> ---
>>  include/block/blockjob_int.h |  3 ---
>>  include/qemu/job.h           | 11 +++++++++++
>>  block/backup.c               |  2 +-
>>  block/commit.c               |  2 +-
>>  block/mirror.c               |  4 ++--
>>  block/stream.c               |  2 +-
>>  blockjob.c                   | 16 +++++++---------
>>  job.c                        | 10 ++++++++++
>>  8 files changed, 33 insertions(+), 17 deletions(-)
>>
> 
> [...]
> 
>> diff --git a/include/qemu/job.h b/include/qemu/job.h
>> index b4b49f19e1..c87e951c8a 100644
>> --- a/include/qemu/job.h
>> +++ b/include/qemu/job.h
> 
> [...]
> 
>> @@ -57,4 +62,10 @@ struct JobDriver {
>>   */
>>  void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
>>  
>> +/** Returns the JobType of a given Job. */
>> +JobType job_type(Job *job);
>> +
>> +/** Returns the enum string for the JobType of a given Job. */
>> +const char *job_type_str(Job *job);
>> +
> 
> Is there a good reason for these not to take a const Job *?
> 
> Depending on the answer:
> 
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> 
>>  #endif
> 

Max is just very excited to talk about Rust some more, I can tell.

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

* Re: [Qemu-devel] [PATCH 11/42] job: Add job_delete()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 11/42] job: Add job_delete() Kevin Wolf
  2018-05-11 22:56   ` Max Reitz
@ 2018-05-14 20:15   ` John Snow
  2018-05-16 17:54   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 20:15 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This moves freeing the Job object and its fields from block_job_unref()
> to job_delete().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

I guess the reference counting comes later.

*peeks*

Oh, soon!

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

* Re: [Qemu-devel] [PATCH 31/42] job: Move transactions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 31/42] job: Move transactions to Job Kevin Wolf
@ 2018-05-14 20:28   ` Max Reitz
  2018-05-14 21:17   ` Max Reitz
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 20:28 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the logic that implements job transactions from BlockJob to
> Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  54 ----------
>  include/block/blockjob_int.h |  10 --
>  include/qemu/job.h           |  71 +++++++++++--
>  blockdev.c                   |   6 +-
>  blockjob.c                   | 238 +------------------------------------------
>  job.c                        | 235 ++++++++++++++++++++++++++++++++++++++++--
>  tests/test-blockjob-txn.c    |  12 +--
>  tests/test-blockjob.c        |   2 +-
>  8 files changed, 304 insertions(+), 324 deletions(-)

[...]

> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index 614a2dea92..84a9eb7980 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h

[...]

> @@ -227,20 +242,52 @@ typedef enum JobCreateFlags {

[...]

> +/**
> + * @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()

*job_txn_unref()

(and maybe even job_completed() in preparation for the next patches)

> + * to release the reference that is automatically grabbed here.
> + *
> + * If @txn is NULL, the function does nothing.
> + */
> +void job_txn_add_job(JobTxn *txn, Job *job);

[...]

> diff --git a/job.c b/job.c
> index 49dce57c9e..2d782859ac 100644
> --- a/job.c
> +++ b/job.c

[...]

> @@ -80,6 +93,71 @@ static void __attribute__((__constructor__)) job_init(void)

[...]

> +static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)

(“6.7.6.3. (8) A declaration of a parameter as ‘function returning type’
shall be adjusted to ‘pointer to function returning type’, as in 6.3.2.1.”

Interesting.  Didn't know that worked.)

[...]

> @@ -542,12 +632,141 @@ int job_finalize_single(Job *job)

[...]

> +static int job_prepare(Job *job)
> +{
> +    if (job->ret == 0 && job->driver->prepare) {
> +        job->ret = job->driver->prepare(job);
> +    }
> +    return job->ret;
> +}

I'd have put this above job_commit() and the like, but it's not like it
matters functionally...

Well, you know me.  With the comment fixed:

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


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

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

* Re: [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs Kevin Wolf
  2018-05-14 13:59   ` Max Reitz
@ 2018-05-14 20:42   ` John Snow
  2018-05-16 18:03   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 20:42 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This moves the job list from BlockJob to Job. Now we can check for
> duplicate IDs in job_create().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation " Kevin Wolf
@ 2018-05-14 20:53   ` Max Reitz
  2018-05-15 12:59     ` Kevin Wolf
  0 siblings, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-14 20:53 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the top-level job completion and cancellation functions from
> BlockJob to Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     | 55 -------------------------------
>  include/block/blockjob_int.h | 18 ----------
>  include/qemu/job.h           | 58 ++++++++++++++++++++++++++++----
>  block.c                      |  2 +-
>  block/backup.c               |  3 +-
>  block/commit.c               |  8 ++---
>  block/mirror.c               |  6 ++--
>  block/replication.c          |  4 +--
>  block/stream.c               |  2 +-
>  blockdev.c                   |  8 ++---
>  blockjob.c                   | 76 ------------------------------------------
>  job.c                        | 78 +++++++++++++++++++++++++++++++++++++++++---
>  qemu-img.c                   |  2 +-
>  tests/test-bdrv-drain.c      |  5 ++-
>  tests/test-blockjob-txn.c    | 14 ++++----
>  tests/test-blockjob.c        | 21 ++++++------
>  block/trace-events           |  3 --
>  trace-events                 |  1 +
>  18 files changed, 162 insertions(+), 202 deletions(-)

After this patch, there are a couple of places left that mention
block_job_enter(), those should be fixed.  Well, and those two
block_job_completed() mentions.

[...]

> diff --git a/block.c b/block.c
> index 676e57f562..7a149bfea9 100644
> --- a/block.c
> +++ b/block.c
> @@ -3362,7 +3362,7 @@ static void bdrv_close(BlockDriverState *bs)
>  
>  void bdrv_close_all(void)
>  {
> -    block_job_cancel_sync_all();
> +    job_cancel_sync_all();

Do we really want to cancel jobs that might have nothing to do with the
block layer?

>      nbd_export_close_all();
>  
>      /* Drop references from requests still in flight, such as canceled block

[...]

> diff --git a/block/commit.c b/block/commit.c
> index 02a8af9127..56c3810bad 100644
> --- a/block/commit.c
> +++ b/block/commit.c
> @@ -112,12 +112,12 @@ static void commit_complete(Job *job, void *opaque)
>      blk_unref(s->top);
>  
>      /* If there is more than one reference to the job (e.g. if called from
> -     * block_job_finish_sync()), block_job_completed() won't free it and
> -     * therefore the blockers on the intermediate nodes remain. This would
> -     * cause bdrv_set_backing_hd() to fail. */
> +     * block_job_finish_sync()), job_completed() won't free it and therefore

Nice start, but there is more to do in this line. :-)

(Though probably in some other patch.)

Max

> +     * the blockers on the intermediate nodes remain. This would cause
> +     * bdrv_set_backing_hd() to fail. */
>      block_job_remove_all_bdrv(bjob);
>  
> -    block_job_completed(&s->common, ret);
> +    job_completed(job, ret);
>      g_free(data);
>  
>      /* If bdrv_drop_intermediate() didn't already do that, remove the commit


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

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

* Re: [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job Kevin Wolf
  2018-05-14 14:20   ` Max Reitz
@ 2018-05-14 20:58   ` John Snow
  2018-05-16 18:11   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 20:58 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This moves BlockJob.status and the closely related functions
> (block_)job_state_transition() and (block_)job_apply_verb to Job. The
> two QAPI enums are renamed to JobStatus and JobVerb.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

In good faith that the TODOs dissolve by the end of the series:

Reviewed-by: John Snow <jsnow@redhat.com>

(As a bonus, removing 'BLOCK' out of all of those enum names made the
table a little thinner column-wise. nice!)

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

* Re: [Qemu-devel] [PATCH 33/42] job: Add job_yield()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 33/42] job: Add job_yield() Kevin Wolf
@ 2018-05-14 20:59   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 20:59 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves block_job_yield() to the Job layer.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob_int.h |  8 --------
>  include/qemu/job.h           |  9 +++++++--
>  block/backup.c               |  2 +-
>  block/mirror.c               |  2 +-
>  blockjob.c                   | 16 ----------------
>  job.c                        | 20 ++++++++++++++++++--
>  tests/test-blockjob-txn.c    |  2 +-
>  7 files changed, 28 insertions(+), 31 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 34/42] job: Add job_dismiss()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 34/42] job: Add job_dismiss() Kevin Wolf
@ 2018-05-14 21:06   ` Max Reitz
  2018-05-14 22:26   ` Max Reitz
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 21:06 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves block_job_dismiss() to the Job layer.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  9 ---------
>  include/qemu/job.h       |  7 ++++++-
>  blockdev.c               |  8 +++++---
>  blockjob.c               | 13 -------------
>  job.c                    | 15 ++++++++++++++-
>  tests/test-blockjob.c    |  4 ++--
>  6 files changed, 27 insertions(+), 29 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 35/42] job: Add job_is_ready()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 35/42] job: Add job_is_ready() Kevin Wolf
@ 2018-05-14 21:11   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 21:11 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> Instead of having a 'bool ready' in BlockJob, add a function that
> derives its value from the job status.
> 
> At the same time, this fixes the behaviour to match what the QAPI
> documentation promises for query-block-job: 'true if the job may be
> completed'. When the ready flag was introduced in commit ef6dbf1e46e,
> the flag never had to be reset to match the description because after
> being ready, the jobs would immediately complete and disappear.
> 
> Job transactions and manual job finalisation were introduced only later.
> With these changes, jobs may stay around even after having completed
> (and they are not ready to be completed a second time), however their
> patches forgot to reset the ready flag.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  5 -----
>  include/qemu/job.h       |  3 +++
>  blockjob.c               |  3 +--
>  job.c                    | 22 ++++++++++++++++++++++
>  qemu-img.c               |  2 +-
>  tests/test-blockjob.c    |  2 +-
>  6 files changed, 28 insertions(+), 9 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 31/42] job: Move transactions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 31/42] job: Move transactions to Job Kevin Wolf
  2018-05-14 20:28   ` Max Reitz
@ 2018-05-14 21:17   ` Max Reitz
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 21:17 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves the logic that implements job transactions from BlockJob to
> Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  54 ----------
>  include/block/blockjob_int.h |  10 --
>  include/qemu/job.h           |  71 +++++++++++--
>  blockdev.c                   |   6 +-
>  blockjob.c                   | 238 +------------------------------------------
>  job.c                        | 235 ++++++++++++++++++++++++++++++++++++++++--
>  tests/test-blockjob-txn.c    |  12 +--
>  tests/test-blockjob.c        |   2 +-
>  8 files changed, 304 insertions(+), 324 deletions(-)

[...]

> diff --git a/job.c b/job.c
> index 49dce57c9e..2d782859ac 100644
> --- a/job.c
> +++ b/job.c

[...]

> @@ -263,9 +353,10 @@ void job_event_completed(Job *job)
>      notifier_list_notify(&job->on_finalize_completed, job);
>  }
>  
> -void job_event_pending(Job *job)
> +static int job_event_pending(Job *job)
>  {
>      notifier_list_notify(&job->on_pending, job);
> +    return 0;
>  }
>  
>  void job_enter_cond(Job *job, bool(*fn)(Job *job))

On second thought, I don't know why this hunk is necessary.

Max


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

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

* Re: [Qemu-devel] [PATCH 36/42] job: Add job_transition_to_ready()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 36/42] job: Add job_transition_to_ready() Kevin Wolf
@ 2018-05-14 21:22   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 21:22 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> The transition to the READY state was still performed in the BlockJob
> layer, in the same function that sent the BLOCK_JOB_READY QMP event.
> 
> This patch brings the state transition to the Job layer and implements
> the QMP event using a notifier called from the Job layer, like we
> already do for other events related to state transitions.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  3 +++
>  include/block/blockjob_int.h |  8 --------
>  include/qemu/job.h           |  9 ++++++---
>  block/mirror.c               |  6 +++---
>  blockjob.c                   | 36 +++++++++++++++++++-----------------
>  job.c                        | 16 +++++++++++++---
>  tests/test-bdrv-drain.c      |  2 +-
>  tests/test-blockjob.c        |  2 +-
>  8 files changed, 46 insertions(+), 36 deletions(-)

[...]

> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index fb81cc7c09..20b48926d9 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h

[...]

> @@ -522,7 +528,4 @@ void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
>   */
>  int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp);
>  
> -/* TODO To be removed from the public interface */
> -void job_state_transition(Job *job, JobStatus s1);
> -

\o/

>  #endif

[...]

> diff --git a/blockjob.c b/blockjob.c
> index 0512b41901..27f3199a20 100644
> --- a/blockjob.c
> +++ b/blockjob.c

[...]

> @@ -387,13 +403,14 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
>      job->finalize_cancelled_notifier.notify = block_job_event_cancelled;
>      job->finalize_completed_notifier.notify = block_job_event_completed;
>      job->pending_notifier.notify = block_job_event_pending;
> +    job->ready_notifier.notify = block_job_event_ready;
>  
>      notifier_list_add(&job->job.on_finalize_cancelled,
>                        &job->finalize_cancelled_notifier);
>      notifier_list_add(&job->job.on_finalize_completed,
>                        &job->finalize_completed_notifier);
> -    notifier_list_add(&job->job.on_pending,
> -                      &job->pending_notifier);
> +    notifier_list_add(&job->job.on_pending, &job->pending_notifier);

Do you want to move this to the patch that introduced this line?

If you do:

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

> +    notifier_list_add(&job->job.on_ready, &job->ready_notifier);
>  
>      error_setg(&job->blocker, "block device is in use by block job: %s",
>                 job_type_str(&job->job));


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

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

* Re: [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job Kevin Wolf
@ 2018-05-14 21:33   ` Max Reitz
  2018-05-16 21:23   ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 21:33 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> BlockJob has fields .offset and .len, which are actually misnomers today
> because they are no longer tied to block device sizes, but just progress
> counters. As such they make a lot of sense in generic Jobs.
> 
> This patch moves the fields to Job and renames them to .progress_current
> and .progress_total to describe their function better.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h | 25 -------------------------
>  include/qemu/job.h       | 27 +++++++++++++++++++++++++++
>  block/backup.c           |  8 ++++----
>  block/commit.c           |  4 ++--
>  block/mirror.c           |  4 ++--
>  block/stream.c           |  4 ++--
>  blockjob.c               | 26 ++++++++------------------
>  job.c                    | 10 ++++++++++
>  qemu-img.c               |  8 ++++++--
>  9 files changed, 61 insertions(+), 55 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 14/42] job: Add reference counting Kevin Wolf
  2018-05-14 14:29   ` Max Reitz
@ 2018-05-14 21:34   ` John Snow
  2018-05-15  9:06     ` Kevin Wolf
  2018-05-16 18:17   ` Eric Blake
  2 siblings, 1 reply; 146+ messages in thread
From: John Snow @ 2018-05-14 21:34 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This moves reference counting from BlockJob to Job.
> 
> In order to keep calling the BlockJob cleanup code when the job is
> deleted via job_unref(), introduce a new JobDriver.free callback. Every
> block job must use block_job_free() for this callback, this is asserted
> in block_job_create().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

So far so good, though it does look a little silly that presently every
last job has the exact same free callback.

Also, I forgot to reply to #13 with this, but I suppose that the
approach you're taking -- of a fairly straightforward mechanical
refactor -- means we don't really have the opportunity to change any of
the pretty dumb names or existing peculiarities of design we've evolved.

I had hoped we'd be able to change a few things, but certainly keeping
them as-is makes the whole re-factoring process an awful lot simpler...

...Or maybe I'm getting ahead of myself, there's a lot of series left to go.

Ah, anyway:

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job Kevin Wolf
  2018-05-14 14:39   ` Max Reitz
@ 2018-05-14 21:53   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 21:53 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> We cannot yet move the whole logic around job cancelling to Job because
> it depends on quite a few other things that are still only in BlockJob,
> but we can move the cancelled field at least.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context Kevin Wolf
  2018-05-14 15:20   ` Max Reitz
@ 2018-05-14 22:02   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 22:02 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> When block jobs need an AioContext, they just take it from their main
> block node. Generic jobs don't have a main block node, so we need to
> assign them an AioContext explicitly.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Nothing uses the field yet, but so far so good.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event Kevin Wolf
@ 2018-05-14 22:11   ` Max Reitz
  2018-05-16 16:15     ` Kevin Wolf
  2018-05-16 19:26   ` Eric Blake
  1 sibling, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-14 22:11 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This adds a QMP event that is emitted whenever a job transitions from
> one status to another. For the event, a new qapi/job.json schema file is
> created which will contain all job-related definitions that aren't tied
> to the block layer.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  qapi/block-core.json       |  47 +-----------
>  qapi/job.json              |  65 +++++++++++++++++
>  qapi/qapi-schema.json      |   1 +
>  job.c                      |  10 +++
>  Makefile                   |   9 +++
>  Makefile.objs              |   4 +
>  tests/qemu-iotests/030     |   6 +-
>  tests/qemu-iotests/040     |   2 +
>  tests/qemu-iotests/041     |  17 ++++-
>  tests/qemu-iotests/095     |   2 +-
>  tests/qemu-iotests/095.out |   6 ++
>  tests/qemu-iotests/109     |   2 +-
>  tests/qemu-iotests/109.out | 178 +++++++++++++++++++++++++++++++++++++++------
>  tests/qemu-iotests/124     |   8 ++
>  tests/qemu-iotests/127.out |   7 ++
>  tests/qemu-iotests/141     |  10 +--
>  tests/qemu-iotests/141.out |  29 ++++++++
>  tests/qemu-iotests/144     |   2 +-
>  tests/qemu-iotests/144.out |   7 ++
>  tests/qemu-iotests/156     |   2 +-
>  tests/qemu-iotests/156.out |   7 ++
>  tests/qemu-iotests/185     |  12 +--
>  tests/qemu-iotests/185.out |  10 +++
>  tests/qemu-iotests/191     |   4 +-
>  tests/qemu-iotests/191.out | 132 +++++++++++++++++++++++++++++++++
>  25 files changed, 492 insertions(+), 87 deletions(-)
>  create mode 100644 qapi/job.json

Your effort to keep this series in 42 patches in Ehren, but I think it
would make sense to have a separate patch that creates qapi/job.json. ;-)

[...]

> diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030
> index 640a6dfd10..03aea460c9 100755
> --- a/tests/qemu-iotests/030
> +++ b/tests/qemu-iotests/030
> @@ -304,7 +304,7 @@ class TestParallelOps(iotests.QMPTestCase):
>          result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6')
>          self.assert_qmp(result, 'error/class', 'GenericError')
>  
> -        event = self.vm.get_qmp_event(wait=True)
> +        event = self.vm.event_wait(name='BLOCK_JOB_READY')
>          self.assertEqual(event['event'], 'BLOCK_JOB_READY')

This assertion is a bit useless, now, but whatever.

>          self.assert_qmp(event, 'data/device', 'commit-drive0')
>          self.assert_qmp(event, 'data/type', 'commit')
> @@ -751,7 +751,9 @@ class TestStreamStop(iotests.QMPTestCase):
>  
>          time.sleep(0.1)
>          events = self.vm.get_qmp_events(wait=False)
> -        self.assertEqual(events, [], 'unexpected QMP event: %s' % events)
> +        for e in events:
> +            if e['event'] != 'JOB_STATUS_CHANGE':
> +                self.assertEqual(events, [], 'unexpected QMP event: %s' % events)

self.fail() would be nicer to read.

>  
>          self.cancel_and_wait(resume=True)
>  

[...]

> diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
> index a860a31e9a..e94587950c 100755
> --- a/tests/qemu-iotests/041
> +++ b/tests/qemu-iotests/041
> @@ -445,6 +445,8 @@ new_state = "1"
>                      self.assert_qmp(event, 'data/device', 'drive0')
>                      self.assert_qmp(event, 'data/error', 'Input/output error')
>                      completed = True
> +                elif event['event'] == 'JOB_STATUS_CHANGE':
> +                    self.assert_qmp(event, 'data/id', 'drive0')

There were plenty of such loops in 030.  Why did you leave them as they
are and add this check here?

(I can understand it in the previous hunk, because that one has an else
branch, but this one does not.)

((And if you decide to always do this check, iotests.py needs it, too.))

>  
>          self.assert_no_active_block_jobs()
>          self.vm.shutdown()
> @@ -457,6 +459,10 @@ new_state = "1"
>          self.assert_qmp(result, 'return', {})
>  
>          event = self.vm.get_qmp_event(wait=True)
> +        while event['event'] == 'JOB_STATUS_CHANGE':
> +            self.assert_qmp(event, 'data/id', 'drive0')
> +            event = self.vm.get_qmp_event(wait=True)
> +
>          self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')

Wouldn't this be simpler with
event = self.vm.event_wait(name='BLOCK_JOB_ERROR')?

Same in other hunks in this file.

(And technically in all cases (like 056) that use event_wait already.)

(Sure, if you want to check @id...  But then you'd have to do the same
in 030.  And I don't quite see the point of checking JOB_STATUS_CHANGE
just sporadically, and not even checking the target status.)

>          self.assert_qmp(event, 'data/device', 'drive0')
>          self.assert_qmp(event, 'data/operation', 'read')

[...]

> diff --git a/tests/qemu-iotests/141 b/tests/qemu-iotests/141
> index 2f9d7b9bc2..9ae23a6c63 100755
> --- a/tests/qemu-iotests/141
> +++ b/tests/qemu-iotests/141

[...]

> @@ -179,7 +179,7 @@ test_blockjob \
>                      'device': 'drv0',
>                      'speed': 1}}" \
>      'return' \
> -    'BLOCK_JOB_CANCELLED'
> +    '"status": "null"'
>  

The comment above this hunk says that "no event other than
BLOCK_JOB_CANCELLED will be emitted".  It would make sense to change it
to e.g. "no block job event other than [...]".

>  _cleanup_qemu
>  

[...]

You forgot to adjust 094, and although I cannot prove it (I don't have
an nfs setup handy right now), I have a hunch that this patch breaks 173
as well.

Max


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

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

* Re: [Qemu-devel] [PATCH 34/42] job: Add job_dismiss()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 34/42] job: Add job_dismiss() Kevin Wolf
  2018-05-14 21:06   ` Max Reitz
@ 2018-05-14 22:26   ` Max Reitz
  1 sibling, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 22:26 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This moves block_job_dismiss() to the Job layer.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  9 ---------
>  include/qemu/job.h       |  7 ++++++-
>  blockdev.c               |  8 +++++---
>  blockjob.c               | 13 -------------
>  job.c                    | 15 ++++++++++++++-
>  tests/test-blockjob.c    |  4 ++--
>  6 files changed, 27 insertions(+), 29 deletions(-)

[...]

> diff --git a/blockdev.c b/blockdev.c
> index 31319a6d5a..15753d9719 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -3915,14 +3915,16 @@ void qmp_block_job_finalize(const char *id, Error **errp)
>  void qmp_block_job_dismiss(const char *id, Error **errp)
>  {
>      AioContext *aio_context;
> -    BlockJob *job = find_block_job(id, &aio_context, errp);
> +    BlockJob *bjob = find_block_job(id, &aio_context, errp);
> +    Job *job;

clang (probably gcc as well if I had enabled tracing in that build...)
complains that this should be initialized to NULL because of...

>  
> -    if (!job) {
> +    if (!bjob) {
>          return;
>      }
>  
>      trace_qmp_block_job_dismiss(job);

...this.

Max

> -    block_job_dismiss(&job, errp);
> +    job = &bjob->job;
> +    job_dismiss(&job, errp);
>      aio_context_release(aio_context);
>  }
>  


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

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

* Re: [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands Kevin Wolf
@ 2018-05-14 22:31   ` Max Reitz
  2018-05-15 14:08     ` Kevin Wolf
  0 siblings, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-14 22:31 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This adds QMP commands that control the transition between states of the
> job lifecycle.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  qapi/job.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++
>  job-qmp.c     | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  MAINTAINERS   |   1 +
>  Makefile.objs |   2 +-
>  trace-events  |   9 ++++
>  5 files changed, 263 insertions(+), 1 deletion(-)
>  create mode 100644 job-qmp.c
> 
> diff --git a/qapi/job.json b/qapi/job.json
> index bd88a358d0..7b84158292 100644
> --- a/qapi/job.json
> +++ b/qapi/job.json
> @@ -63,3 +63,115 @@
>  { 'event': 'JOB_STATUS_CHANGE',
>    'data': { 'id': 'str',
>              'status': 'JobStatus' } }
> +
> +##
> +# @job-pause:
> +#
> +# Pause an active job.
> +#
> +# This command returns immediately after marking the active job for pausing.
> +# Pausing an already paused job has no cumulative effect; a single job-resume
> +# command will resume the job.

Pausing an already paused job is, in fact, an error.

(Which should be noted here instead of making it appear like it'd be
idempotent.)

> +#
> +# The job will pause as soon as possible, which means transitioning into the
> +# PAUSED state if it was RUNNING, or into STANDBY if it was READY. The
> +# corresponding JOB_STATUS_CHANGE event will be emitted.
> +#
> +# Cancelling a paused job automatically resumes it.
> +#
> +# @id: The job identifier.
> +#
> +# Since: 2.13
> +##
> +{ 'command': 'job-pause', 'data': { 'id': 'str' } }

[...]

> +##
> +# @job-cancel:
> +#
> +# Instruct an active background job to cancel at the next opportunity.
> +# This command returns immediately after marking the active job for
> +# cancellation.
> +#
> +# The job will cancel as soon as possible and then emit a JOB_STATUS_CHANGE
> +# event. Usually, the status will change to ABORTING, but it is possible that
> +# a job successfully completes (e.g. because it was almost done and there was
> +# no opportunity to cancel earlier than completing the job) and transitions to
> +# PENDING instead.
> +#
> +# Note that if you issue 'job-cancel' after a mirror block job has indicated
> +# (via the event BLOCK_JOB_READY, and by transitioning into the READY state)
> +# that the source and destination are synchronized, then the job always
> +# completes successfully and transitions to PENDING as well as triggering the
> +# event BLOCK_JOB_COMPLETED, to indicate that the mirroring has ended and the
> +# destination now has a point-in-time copy tied to the time of the
> +# cancellation.
> +#
> +# @id: The job identifier.
> +#
> +# @force: If true, and the job is already in the READY state, abandon the job
> +#         immediately (even if it is paused) instead of waiting for the
> +#         destination to complete its final synchronization

The note on "final synchronization" is extremely mirror-specific.  I see
three choices here:

(1) If mirror stays the only job with this special cancel semantics,
then we are free to note that this is a mirror-specific flag here.

(2) Even if some other job might come along at some point where use of
@force may make sense, that doesn't stop us from now noting that only
mirror supports this, which helps readers understand what "destination"
and "final synchronization" mean.

(Yes, so (1) and (2) are basically the same.)

(3) We try to find some general description and drop the last part.
Like "If a job would normally decide to complete instead of actually
aborting, this flag can be used to convince it otherwise."  But that's
so handwavy, I'd rather just mark it as a special mirror flag for now.

> +#
> +# Since: 2.13
> +##
> +{ 'command': 'job-cancel', 'data': { 'id': 'str', '*force': 'bool' } }

[...]

> diff --git a/Makefile.objs b/Makefile.objs
> index 3df8d58e49..253e0356f3 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -66,7 +66,7 @@ chardev-obj-y = chardev/
>  # block-obj-y is code used by both qemu system emulation and qemu-img
>  
>  block-obj-y += nbd/
> -block-obj-y += block.o blockjob.o job.o
> +block-obj-y += block.o blockjob.o job.o job-qmp.o

Shouldn't this be in common-obj-y like blockdev?

Max

>  block-obj-y += block/ scsi/
>  block-obj-y += qemu-io-cmds.o
>  block-obj-$(CONFIG_REPLICATION) += replication.o


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

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job Kevin Wolf
  2018-05-14 15:52   ` Max Reitz
@ 2018-05-14 22:33   ` John Snow
  2018-05-15 12:22     ` Kevin Wolf
  2018-05-16 18:37   ` Eric Blake
  2 siblings, 1 reply; 146+ messages in thread
From: John Snow @ 2018-05-14 22:33 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Hmm, this one is a bit more than just code motion due to the way the
aio_context acquisition has changed. I think at a minimum a good commit
message is warranted.


> ---
>  include/block/blockjob.h     |  5 ----
>  include/block/blockjob_int.h | 19 ---------------
>  include/qemu/job.h           | 20 ++++++++++++++++
>  block/backup.c               |  7 +++---
>  block/commit.c               | 11 +++++----
>  block/mirror.c               | 15 ++++++------
>  block/stream.c               | 14 +++++------
>  blockjob.c                   | 57 ++++----------------------------------------
>  job.c                        | 33 +++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  7 +++---
>  tests/test-blockjob-txn.c    | 13 +++++-----
>  tests/test-blockjob.c        |  7 +++---
>  12 files changed, 98 insertions(+), 110 deletions(-)
> 
> diff --git a/include/block/blockjob.h b/include/block/blockjob.h
> index 04efc94ffc..90942826f5 100644
> --- a/include/block/blockjob.h
> +++ b/include/block/blockjob.h
> @@ -92,11 +92,6 @@ typedef struct BlockJob {
>       */
>      bool ready;
>  
> -    /**
> -     * Set to true when the job has deferred work to the main loop.
> -     */
> -    bool deferred_to_main_loop;
> -
>      /** Status that is published by the query-block-jobs QMP API */
>      BlockDeviceIoStatus iostatus;
>  
> diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
> index d64f30e6b0..0c2f8de381 100644
> --- a/include/block/blockjob_int.h
> +++ b/include/block/blockjob_int.h
> @@ -233,23 +233,4 @@ void block_job_event_ready(BlockJob *job);
>  BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
>                                          int is_read, int error);
>  
> -typedef void BlockJobDeferToMainLoopFn(BlockJob *job, void *opaque);
> -
> -/**
> - * block_job_defer_to_main_loop:
> - * @job: The job
> - * @fn: The function to run in the main loop
> - * @opaque: The opaque value that is passed to @fn
> - *
> - * This function must be called by the main job coroutine just before it
> - * returns.  @fn is executed in the main loop with the BlockDriverState
> - * AioContext acquired.  Block jobs must call bdrv_unref(), bdrv_close(), and
> - * anything that uses bdrv_drain_all() in the main loop.
> - *
> - * The @job AioContext is held while @fn executes.
> - */
> -void block_job_defer_to_main_loop(BlockJob *job,
> -                                  BlockJobDeferToMainLoopFn *fn,
> -                                  void *opaque);
> -
>  #endif
> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index 1821f9ebd7..1a20534235 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h
> @@ -58,6 +58,9 @@ typedef struct Job {
>       */
>      bool cancelled;
>  
> +    /** Set to true when the job has deferred work to the main loop. */
> +    bool deferred_to_main_loop;
> +
>      /** Element of the list of jobs */
>      QLIST_ENTRY(Job) job_list;
>  } Job;
> @@ -131,6 +134,23 @@ Job *job_get(const char *id);
>   */
>  int job_apply_verb(Job *job, JobVerb bv, Error **errp);
>  
> +typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
> +
> +/**
> + * @job: The job
> + * @fn: The function to run in the main loop
> + * @opaque: The opaque value that is passed to @fn
> + *
> + * This function must be called by the main job coroutine just before it
> + * returns.  @fn is executed in the main loop with the job AioContext acquired.
> + *
> + * Block jobs must call bdrv_unref(), bdrv_close(), and anything that uses
> + * bdrv_drain_all() in the main loop.
> + *
> + * The @job AioContext is held while @fn executes.
> + */
> +void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
> +
>  /* TODO To be removed from the public interface */
>  void job_state_transition(Job *job, JobStatus s1);
>  
> diff --git a/block/backup.c b/block/backup.c
> index ef0aa0e24e..22dd368c90 100644
> --- a/block/backup.c
> +++ b/block/backup.c
> @@ -317,11 +317,12 @@ typedef struct {
>      int ret;
>  } BackupCompleteData;
>  
> -static void backup_complete(BlockJob *job, void *opaque)
> +static void backup_complete(Job *job, void *opaque)
>  {
> +    BlockJob *bjob = container_of(job, BlockJob, job);
>      BackupCompleteData *data = opaque;
>  
> -    block_job_completed(job, data->ret);
> +    block_job_completed(bjob, data->ret);
>      g_free(data);
>  }
>  
> @@ -519,7 +520,7 @@ static void coroutine_fn backup_run(void *opaque)
>  
>      data = g_malloc(sizeof(*data));
>      data->ret = ret;
> -    block_job_defer_to_main_loop(&job->common, backup_complete, data);
> +    job_defer_to_main_loop(&job->common.job, backup_complete, data);
>  }
>  
>  static const BlockJobDriver backup_job_driver = {
> diff --git a/block/commit.c b/block/commit.c
> index 85baea8f92..d326766e4d 100644
> --- a/block/commit.c
> +++ b/block/commit.c
> @@ -72,9 +72,10 @@ typedef struct {
>      int ret;
>  } CommitCompleteData;
>  
> -static void commit_complete(BlockJob *job, void *opaque)
> +static void commit_complete(Job *job, void *opaque)
>  {
> -    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
> +    CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
> +    BlockJob *bjob = &s->common;
>      CommitCompleteData *data = opaque;
>      BlockDriverState *top = blk_bs(s->top);
>      BlockDriverState *base = blk_bs(s->base);
> @@ -90,7 +91,7 @@ static void commit_complete(BlockJob *job, void *opaque)
>       * the normal backing chain can be restored. */
>      blk_unref(s->base);
>  
> -    if (!job_is_cancelled(&s->common.job) && ret == 0) {
> +    if (!job_is_cancelled(job) && ret == 0) {
>          /* success */
>          ret = bdrv_drop_intermediate(s->commit_top_bs, base,
>                                       s->backing_file_str);
> @@ -114,7 +115,7 @@ static void commit_complete(BlockJob *job, void *opaque)
>       * block_job_finish_sync()), block_job_completed() won't free it and
>       * therefore the blockers on the intermediate nodes remain. This would
>       * cause bdrv_set_backing_hd() to fail. */
> -    block_job_remove_all_bdrv(job);
> +    block_job_remove_all_bdrv(bjob);
>  
>      block_job_completed(&s->common, ret);
>      g_free(data);
> @@ -211,7 +212,7 @@ out:
>  
>      data = g_malloc(sizeof(*data));
>      data->ret = ret;
> -    block_job_defer_to_main_loop(&s->common, commit_complete, data);
> +    job_defer_to_main_loop(&s->common.job, commit_complete, data);
>  }
>  
>  static const BlockJobDriver commit_job_driver = {
> diff --git a/block/mirror.c b/block/mirror.c
> index 163d83e34a..4929191b81 100644
> --- a/block/mirror.c
> +++ b/block/mirror.c
> @@ -484,9 +484,10 @@ typedef struct {
>      int ret;
>  } MirrorExitData;
>  
> -static void mirror_exit(BlockJob *job, void *opaque)
> +static void mirror_exit(Job *job, void *opaque)
>  {
> -    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
> +    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
> +    BlockJob *bjob = &s->common;
>      MirrorExitData *data = opaque;
>      AioContext *replace_aio_context = NULL;
>      BlockDriverState *src = s->source;
> @@ -568,7 +569,7 @@ static void mirror_exit(BlockJob *job, void *opaque)
>       * the blockers on the intermediate nodes so that the resulting state is
>       * valid. Also give up permissions on mirror_top_bs->backing, which might
>       * block the removal. */
> -    block_job_remove_all_bdrv(job);
> +    block_job_remove_all_bdrv(bjob);
>      bdrv_child_try_set_perm(mirror_top_bs->backing, 0, BLK_PERM_ALL,
>                              &error_abort);
>      bdrv_replace_node(mirror_top_bs, backing_bs(mirror_top_bs), &error_abort);
> @@ -576,9 +577,9 @@ static void mirror_exit(BlockJob *job, void *opaque)
>      /* We just changed the BDS the job BB refers to (with either or both of the
>       * bdrv_replace_node() calls), so switch the BB back so the cleanup does
>       * the right thing. We don't need any permissions any more now. */
> -    blk_remove_bs(job->blk);
> -    blk_set_perm(job->blk, 0, BLK_PERM_ALL, &error_abort);
> -    blk_insert_bs(job->blk, mirror_top_bs, &error_abort);
> +    blk_remove_bs(bjob->blk);
> +    blk_set_perm(bjob->blk, 0, BLK_PERM_ALL, &error_abort);
> +    blk_insert_bs(bjob->blk, mirror_top_bs, &error_abort);
>  
>      block_job_completed(&s->common, data->ret);
>  
> @@ -901,7 +902,7 @@ immediate_exit:
>      if (need_drain) {
>          bdrv_drained_begin(bs);
>      }
> -    block_job_defer_to_main_loop(&s->common, mirror_exit, data);
> +    job_defer_to_main_loop(&s->common.job, mirror_exit, data);
>  }
>  
>  static void mirror_complete(BlockJob *job, Error **errp)
> diff --git a/block/stream.c b/block/stream.c
> index 22c71ae100..0bba81678c 100644
> --- a/block/stream.c
> +++ b/block/stream.c
> @@ -58,16 +58,16 @@ typedef struct {
>      int ret;
>  } StreamCompleteData;
>  
> -static void stream_complete(BlockJob *job, void *opaque)
> +static void stream_complete(Job *job, void *opaque)
>  {
> -    StreamBlockJob *s = container_of(job, StreamBlockJob, common);
> +    StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
> +    BlockJob *bjob = &s->common;
>      StreamCompleteData *data = opaque;
> -    BlockDriverState *bs = blk_bs(job->blk);
> +    BlockDriverState *bs = blk_bs(bjob->blk);
>      BlockDriverState *base = s->base;
>      Error *local_err = NULL;
>  
> -    if (!job_is_cancelled(&s->common.job) && bs->backing &&
> -        data->ret == 0) {
> +    if (!job_is_cancelled(job) && bs->backing && data->ret == 0) {
>          const char *base_id = NULL, *base_fmt = NULL;
>          if (base) {
>              base_id = s->backing_file_str;
> @@ -88,7 +88,7 @@ out:
>      /* Reopen the image back in read-only mode if necessary */
>      if (s->bs_flags != bdrv_get_flags(bs)) {
>          /* Give up write permissions before making it read-only */
> -        blk_set_perm(job->blk, 0, BLK_PERM_ALL, &error_abort);
> +        blk_set_perm(bjob->blk, 0, BLK_PERM_ALL, &error_abort);
>          bdrv_reopen(bs, s->bs_flags, NULL);
>      }
>  
> @@ -205,7 +205,7 @@ out:
>      /* Modify backing chain and close BDSes in main loop */
>      data = g_malloc(sizeof(*data));
>      data->ret = ret;
> -    block_job_defer_to_main_loop(&s->common, stream_complete, data);
> +    job_defer_to_main_loop(&s->common.job, stream_complete, data);
>  }
>  
>  static const BlockJobDriver stream_job_driver = {
> diff --git a/blockjob.c b/blockjob.c
> index d44f5c2e50..6021d885be 100644
> --- a/blockjob.c
> +++ b/blockjob.c
> @@ -347,7 +347,7 @@ static void block_job_decommission(BlockJob *job)
>      job->completed = true;
>      job->busy = false;
>      job->paused = false;
> -    job->deferred_to_main_loop = true;
> +    job->job.deferred_to_main_loop = true;
>      block_job_txn_del_job(job);
>      job_state_transition(&job->job, JOB_STATUS_NULL);
>      job_unref(&job->job);
> @@ -502,7 +502,7 @@ static int block_job_finish_sync(BlockJob *job,
>      /* block_job_drain calls block_job_enter, and it should be enough to
>       * induce progress until the job completes or moves to the main thread.
>      */
> -    while (!job->deferred_to_main_loop && !job->completed) {
> +    while (!job->job.deferred_to_main_loop && !job->completed) {
>          block_job_drain(job);
>      }
>      while (!job->completed) {
> @@ -722,7 +722,7 @@ void block_job_cancel(BlockJob *job, bool force)
>      block_job_cancel_async(job, force);
>      if (!block_job_started(job)) {
>          block_job_completed(job, -ECANCELED);
> -    } else if (job->deferred_to_main_loop) {
> +    } else if (job->job.deferred_to_main_loop) {
>          block_job_completed_txn_abort(job);
>      } else {
>          block_job_enter(job);
> @@ -1038,7 +1038,7 @@ static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job))
>      if (!block_job_started(job)) {
>          return;
>      }
> -    if (job->deferred_to_main_loop) {
> +    if (job->job.deferred_to_main_loop) {
>          return;
>      }
>  
> @@ -1053,7 +1053,7 @@ static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job))
>          return;
>      }
>  
> -    assert(!job->deferred_to_main_loop);
> +    assert(!job->job.deferred_to_main_loop);
>      timer_del(&job->sleep_timer);
>      job->busy = true;
>      block_job_unlock();
> @@ -1159,50 +1159,3 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err,
>      }
>      return action;
>  }
> -
> -typedef struct {
> -    BlockJob *job;
> -    AioContext *aio_context;
> -    BlockJobDeferToMainLoopFn *fn;
> -    void *opaque;
> -} BlockJobDeferToMainLoopData;
> -
> -static void block_job_defer_to_main_loop_bh(void *opaque)
> -{
> -    BlockJobDeferToMainLoopData *data = opaque;
> -    AioContext *aio_context;
> -
> -    /* Prevent race with block_job_defer_to_main_loop() */
> -    aio_context_acquire(data->aio_context);
> -
> -    /* Fetch BDS AioContext again, in case it has changed */
> -    aio_context = blk_get_aio_context(data->job->blk);
> -    if (aio_context != data->aio_context) {
> -        aio_context_acquire(aio_context);
> -    }
> -
> -    data->fn(data->job, data->opaque);
> -
> -    if (aio_context != data->aio_context) {
> -        aio_context_release(aio_context);
> -    }
> -
> -    aio_context_release(data->aio_context);
> -
> -    g_free(data);
> -}
> -
> -void block_job_defer_to_main_loop(BlockJob *job,
> -                                  BlockJobDeferToMainLoopFn *fn,
> -                                  void *opaque)
> -{
> -    BlockJobDeferToMainLoopData *data = g_malloc(sizeof(*data));
> -    data->job = job;
> -    data->aio_context = blk_get_aio_context(job->blk);
> -    data->fn = fn;
> -    data->opaque = opaque;
> -    job->deferred_to_main_loop = true;
> -
> -    aio_bh_schedule_oneshot(qemu_get_aio_context(),
> -                            block_job_defer_to_main_loop_bh, data);
> -}
> diff --git a/job.c b/job.c
> index 6f97a4317e..b074b3ffd7 100644
> --- a/job.c
> +++ b/job.c
> @@ -28,6 +28,7 @@
>  #include "qapi/error.h"
>  #include "qemu/job.h"
>  #include "qemu/id.h"
> +#include "qemu/main-loop.h"
>  #include "trace-root.h"
>  
>  static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
> @@ -170,3 +171,35 @@ void job_unref(Job *job)
>          g_free(job);
>      }
>  }
> +
> +typedef struct {
> +    Job *job;
> +    JobDeferToMainLoopFn *fn;
> +    void *opaque;
> +} JobDeferToMainLoopData;
> +
> +static void job_defer_to_main_loop_bh(void *opaque)
> +{
> +    JobDeferToMainLoopData *data = opaque;
> +    Job *job = data->job;
> +    AioContext *aio_context = job->aio_context;
> +
> +    /* Prevent race with job_defer_to_main_loop() */
> +    aio_context_acquire(aio_context);
> +    data->fn(data->job, data->opaque);
> +    aio_context_release(aio_context);
> +
> +    g_free(data);
> +}
> +

This function showed up in '14, with dec7d42 from Stefan. His first
draft looked like Kevin's, until:

https://lists.gnu.org/archive/html/qemu-devel/2014-10/msg00111.html

Max, from 2014:

"I'm not so sure whether it'd be possible to change the BDS's AIO
context (in another thread) after the call to bdrv_get_aio_context() in
block_job_defer_to_main_loop() and before
block_job_defer_to_main_loop_bh() is run. If so, this might create race
conditions."

Err, I dunno either.

> +void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque)
> +{
> +    JobDeferToMainLoopData *data = g_malloc(sizeof(*data));
> +    data->job = job;
> +    data->fn = fn;
> +    data->opaque = opaque;
> +    job->deferred_to_main_loop = true;
> +
> +    aio_bh_schedule_oneshot(qemu_get_aio_context(),
> +                            job_defer_to_main_loop_bh, data);
> +}
> diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
> index f9e37d479c..4f8cba8377 100644
> --- a/tests/test-bdrv-drain.c
> +++ b/tests/test-bdrv-drain.c
> @@ -496,9 +496,10 @@ typedef struct TestBlockJob {
>      bool should_complete;
>  } TestBlockJob;
>  
> -static void test_job_completed(BlockJob *job, void *opaque)
> +static void test_job_completed(Job *job, void *opaque)
>  {
> -    block_job_completed(job, 0);
> +    BlockJob *bjob = container_of(job, BlockJob, job);
> +    block_job_completed(bjob, 0);
>  }
>  
>  static void coroutine_fn test_job_start(void *opaque)
> @@ -510,7 +511,7 @@ static void coroutine_fn test_job_start(void *opaque)
>          block_job_sleep_ns(&s->common, 100000);
>      }
>  
> -    block_job_defer_to_main_loop(&s->common, test_job_completed, NULL);
> +    job_defer_to_main_loop(&s->common.job, test_job_completed, NULL);
>  }
>  
>  static void test_job_complete(BlockJob *job, Error **errp)
> diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c
> index 26b4bbb230..c03f9662d8 100644
> --- a/tests/test-blockjob-txn.c
> +++ b/tests/test-blockjob-txn.c
> @@ -24,16 +24,17 @@ typedef struct {
>      int *result;
>  } TestBlockJob;
>  
> -static void test_block_job_complete(BlockJob *job, void *opaque)
> +static void test_block_job_complete(Job *job, void *opaque)
>  {
> -    BlockDriverState *bs = blk_bs(job->blk);
> +    BlockJob *bjob = container_of(job, BlockJob, job);
> +    BlockDriverState *bs = blk_bs(bjob->blk);
>      int rc = (intptr_t)opaque;
>  
> -    if (job_is_cancelled(&job->job)) {
> +    if (job_is_cancelled(job)) {
>          rc = -ECANCELED;
>      }
>  
> -    block_job_completed(job, rc);
> +    block_job_completed(bjob, rc);
>      bdrv_unref(bs);
>  }
>  
> @@ -54,8 +55,8 @@ static void coroutine_fn test_block_job_run(void *opaque)
>          }
>      }
>  
> -    block_job_defer_to_main_loop(job, test_block_job_complete,
> -                                 (void *)(intptr_t)s->rc);
> +    job_defer_to_main_loop(&job->job, test_block_job_complete,
> +                           (void *)(intptr_t)s->rc);
>  }
>  
>  typedef struct {
> diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
> index fa31481537..5f43bd72a4 100644
> --- a/tests/test-blockjob.c
> +++ b/tests/test-blockjob.c
> @@ -161,11 +161,12 @@ typedef struct CancelJob {
>      bool completed;
>  } CancelJob;
>  
> -static void cancel_job_completed(BlockJob *job, void *opaque)
> +static void cancel_job_completed(Job *job, void *opaque)
>  {
> +    BlockJob *bjob = container_of(job, BlockJob, job);
>      CancelJob *s = opaque;
>      s->completed = true;
> -    block_job_completed(job, 0);
> +    block_job_completed(bjob, 0);
>  }
>  
>  static void cancel_job_complete(BlockJob *job, Error **errp)
> @@ -191,7 +192,7 @@ static void coroutine_fn cancel_job_start(void *opaque)
>      }
>  
>   defer:
> -    block_job_defer_to_main_loop(&s->common, cancel_job_completed, s);
> +    job_defer_to_main_loop(&s->common.job, cancel_job_completed, s);
>  }
>  
>  static const BlockJobDriver test_cancel_driver = {
> 

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

* Re: [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code " Kevin Wolf
  2018-05-14 16:47   ` Max Reitz
@ 2018-05-14 23:02   ` John Snow
  2018-05-16 16:50     ` Kevin Wolf
  1 sibling, 1 reply; 146+ messages in thread
From: John Snow @ 2018-05-14 23:02 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> This commit moves some core functions for dealing with the job coroutine
> from BlockJob to Job. This includes primarily entering the coroutine
> (both for the first and reentering) and yielding explicitly and at pause
> points.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h     |  40 ---------
>  include/block/blockjob_int.h |  26 ------
>  include/qemu/job.h           |  76 ++++++++++++++++
>  block/backup.c               |   2 +-
>  block/commit.c               |   4 +-
>  block/mirror.c               |  22 ++---
>  block/replication.c          |   2 +-
>  block/stream.c               |   4 +-
>  blockdev.c                   |   8 +-
>  blockjob.c                   | 201 +++++++------------------------------------
>  job.c                        | 137 +++++++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  38 ++++----
>  tests/test-blockjob-txn.c    |  12 +--
>  tests/test-blockjob.c        |  14 +--
>  14 files changed, 296 insertions(+), 290 deletions(-)
> 

[...]

>  
>  /* Assumes the block_job_mutex is held */
> -static bool block_job_timer_pending(BlockJob *job)
> +static bool job_timer_pending(Job *job)

Is this intentionally left behind in blockjob.c, even once you've
changed the name (and moved the state to job.c?)

[...]

> +void job_start(Job *job)
> +{
> +    assert(job && !job_started(job) && job->paused &&
> +           job->driver && job->driver->start);
> +    job->co = qemu_coroutine_create(job_co_entry, job);
> +    job->pause_count--;
> +    job->busy = true;
> +    job->paused = false;
> +    job_state_transition(job, JOB_STATUS_RUNNING);
> +    aio_co_enter(job->aio_context, job->co);

I suppose patch 16 was the time to ask this, but are there any
detriments to setting the AIO Context at creation time and then using
that consistently instead of just fetching the AIO context of the BDS
whenever we need it?

I guess if the context changes then we've got it covered in
block_job_attached_aio_context.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command Kevin Wolf
@ 2018-05-14 23:09   ` Max Reitz
  2018-05-16 11:21     ` Kevin Wolf
  0 siblings, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-14 23:09 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This adds a minimal query-jobs implementation that shouldn't pose many
> design questions. It can later be extended to expose more information,
> and especially job-specific information.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  qapi/block-core.json | 18 ----------------
>  qapi/job.json        | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  include/qemu/job.h   |  3 +++
>  job-qmp.c            | 54 +++++++++++++++++++++++++++++++++++++++++++++++
>  job.c                |  2 +-
>  5 files changed, 117 insertions(+), 19 deletions(-)
> 
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index f2da7d696d..d38dfa7836 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -1051,24 +1051,6 @@
>    'data': ['top', 'full', 'none', 'incremental'] }
>  
>  ##
> -# @JobType:
> -#
> -# Type of a background job.
> -#
> -# @commit: block commit job type, see "block-commit"
> -#
> -# @stream: block stream job type, see "block-stream"
> -#
> -# @mirror: drive mirror job type, see "drive-mirror"
> -#
> -# @backup: drive backup job type, see "drive-backup"
> -#
> -# Since: 1.7
> -##
> -{ 'enum': 'JobType',
> -  'data': ['commit', 'stream', 'mirror', 'backup'] }
> -
> -##
>  # @JobVerb:
>  #
>  # Represents command verbs that can be applied to a job.
> diff --git a/qapi/job.json b/qapi/job.json
> index 7b84158292..742fa97768 100644
> --- a/qapi/job.json
> +++ b/qapi/job.json
> @@ -5,6 +5,24 @@
>  ##
>  
>  ##
> +# @JobType:
> +#
> +# Type of a background job.
> +#
> +# @commit: block commit job type, see "block-commit"
> +#
> +# @stream: block stream job type, see "block-stream"
> +#
> +# @mirror: drive mirror job type, see "drive-mirror"
> +#
> +# @backup: drive backup job type, see "drive-backup"
> +#
> +# Since: 1.7
> +##
> +{ 'enum': 'JobType',
> +  'data': ['commit', 'stream', 'mirror', 'backup'] }
> +
> +##

FWIW, I'd put this code movement into the hypothetical commit which
specifically adds job.json.

(Same as JobVerb, which is not moved in this series, but which very
likely should be.)

>  # @JobStatus:
>  #
>  # Indicates the present state of a given job in its lifetime.
> @@ -175,3 +193,44 @@
>  # Since: 2.13
>  ##
>  { 'command': 'job-finalize', 'data': { 'id': 'str' } }
> +
> +##
> +# @JobInfo:
> +#
> +# Information about a job.
> +#
> +# @id:                  The job identifier
> +#
> +# @type:                The job type

I'm not really happy with this description that effectively provides no
information that one cannot already get from the field name, but I
cannot come up with something better, so I'd rather stop typing now.

(OK, my issue here is that "job type" can be anything.  That could mean
e.g. "Is this a block job?".   Maybe an explicit reference to JobType
might help here, although that is already part of the documentation.  On
that note, maybe a quote from the documentation might help make my point
that this description is not very useful:

       "type: JobType"
           The job type

Maybe "The kind of job that is being performed"?)

> +# @status:              Current job state/status

Why the "state/status"?  Forgive me my incompetence, but I don't see a
real difference here.  But in any case, one ought to be enough, no?

(OK, full disclosure: My gut tells me "status" is what we now call the
"progress", and this field should be called "state".  But it's called
"status" now and it doesn't really make a difference, so it's fine to
describe it as either.)

> +#
> +# @current-progress:    The current progress value
> +#
> +# @total-progress:      The maximum progress value

Hm, not really.  This makes it sound like at any point, this will be the
maximum even in the future, but that's not true.

Maybe "estimated progress maximum"?  Or be even more verbose (no, that
doesn't hurt): "This is an estimation of the value @current-progress
needs to reach for the job to complete."

(Actually, I find it important to note that it is an estimation, maybe
we event want to be really explicit about the fact that this value may
change all the time, in any direction.)

> +#
> +# @error:               If this field is present, the job failed; if it is
> +#                       still missing in the CONCLUDED state, this indicates
> +#                       successful completion.
> +#
> +#                       The value is a human-readable error message to describe
> +#                       the reason for the job failure. It should not be parsed
> +#                       by applications.
> +#
> +# Since: 2.13
> +##
> +{ 'struct': 'JobInfo',
> +  'data': { 'id': 'str', 'type': 'JobType', 'status': 'JobStatus',
> +            'current-progress': 'int', 'total-progress': 'int',
> +            '*error': 'str' } }
> +
> +##
> +# @query-jobs:
> +#
> +# Return information about jobs.
> +#
> +# Returns: a list with a @JobInfo for each active job
> +#
> +# Since: 2.13
> +##
> +{ 'command': 'query-jobs', 'returns': ['JobInfo'] }

[...]

> diff --git a/job-qmp.c b/job-qmp.c
> index f32cb8b73e..7425a2a490 100644
> --- a/job-qmp.c
> +++ b/job-qmp.c
> @@ -138,3 +138,57 @@ void qmp_job_dismiss(const char *id, Error **errp)
>      job_dismiss(&job, errp);
>      aio_context_release(aio_context);
>  }
> +
> +static JobInfo *job_query_single(Job *job, Error **errp)
> +{
> +    JobInfo *info;
> +    const char *errmsg = NULL;
> +
> +    assert (!job_is_internal(job));

One stray space.

Max

> +
> +    if (job->ret < 0) {
> +        errmsg = strerror(-job->ret);
> +    }
> +
> +    info = g_new(JobInfo, 1);
> +    *info = (JobInfo) {
> +        .id                 = g_strdup(job->id),
> +        .type               = job_type(job),
> +        .status             = job->status,
> +        .current_progress   = job->progress_current,
> +        .total_progress     = job->progress_total,
> +        .has_error          = !!errmsg,
> +        .error              = g_strdup(errmsg),
> +    };
> +
> +    return info;
> +}
> +
> +JobInfoList *qmp_query_jobs(Error **errp)
> +{
> +    JobInfoList *head = NULL, **p_next = &head;
> +    Job *job;
> +
> +    for (job = job_next(NULL); job; job = job_next(job)) {
> +        JobInfoList *elem;
> +        AioContext *aio_context;
> +
> +        if (job_is_internal(job)) {
> +            continue;
> +        }
> +        elem = g_new0(JobInfoList, 1);
> +        aio_context = job->aio_context;
> +        aio_context_acquire(aio_context);
> +        elem->value = job_query_single(job, errp);
> +        aio_context_release(aio_context);
> +        if (!elem->value) {
> +            g_free(elem);
> +            qapi_free_JobInfoList(head);
> +            return NULL;
> +        }
> +        *p_next = elem;
> +        p_next = &elem->next;
> +    }
> +
> +    return head;
> +}


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

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

* Re: [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns() Kevin Wolf
  2018-05-14 16:57   ` Max Reitz
@ 2018-05-14 23:10   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 23:10 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> There is nothing block layer specific about block_job_sleep_ns(), so
> move the function to Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob_int.h | 11 -----------
>  include/qemu/job.h           | 17 +++++++++++++++++
>  block/backup.c               |  2 +-
>  block/commit.c               |  2 +-
>  block/mirror.c               |  4 ++--
>  block/stream.c               |  2 +-
>  blockjob.c                   | 27 ---------------------------
>  job.c                        | 32 ++++++++++++++++++++++++++++++++
>  tests/test-bdrv-drain.c      |  8 ++++----
>  tests/test-blockjob-txn.c    |  2 +-
>  tests/test-blockjob.c        |  2 +-
>  11 files changed, 60 insertions(+), 49 deletions(-)
> 
> diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
> index 0a614a89b8..8937f5b163 100644
> --- a/include/block/blockjob_int.h
> +++ b/include/block/blockjob_int.h
> @@ -134,17 +134,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
>  void block_job_free(Job *job);
>  
>  /**
> - * block_job_sleep_ns:
> - * @job: The job that calls the function.
> - * @ns: How many nanoseconds to stop for.
> - *
> - * Put the job to sleep (assuming that it wasn't canceled) for @ns
> - * %QEMU_CLOCK_REALTIME nanoseconds.  Canceling the job will immediately
> - * interrupt the wait.
> - */
> -void block_job_sleep_ns(BlockJob *job, int64_t ns);
> -
> -/**
>   * block_job_yield:
>   * @job: The job that calls the function.
>   *
> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index 00c7cda9a3..ddfa824315 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h
> @@ -168,6 +168,13 @@ void job_enter_cond(Job *job, bool(*fn)(Job *job));
>  void job_start(Job *job);
>  
>  /**
> + * @job: The job to enter.
> + *
> + * Continue the specified job by entering the coroutine.
> + */
> +void job_enter(Job *job);
> +

Is this a holdout from #18?

Eh, either way.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 41/42] iotests: Move qmp_to_opts() to VM
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 41/42] iotests: Move qmp_to_opts() to VM Kevin Wolf
@ 2018-05-14 23:11   ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-14 23:11 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> qmp_to_opts() used to be a method of QMPTestCase, but recently we
> started to add more Python test cases that don't make use of
> QMPTestCase. In order to make the method usable there, move it to VM.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  tests/qemu-iotests/041        |  6 +++---
>  tests/qemu-iotests/155        |  2 +-
>  tests/qemu-iotests/iotests.py | 45 ++++++++++++++++++++++---------------------
>  3 files changed, 27 insertions(+), 26 deletions(-)

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


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

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

* Re: [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job Kevin Wolf
  2018-05-14 17:13   ` Max Reitz
@ 2018-05-14 23:23   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 23:23 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> While we already moved the state related to job pausing to Job, the
> functions to do were still BlockJob only. This commit moves them over to
> Job.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

I'm slightly sad about the new callback, because it still seems silly to
have to specify something that's enforced with an assertion to be
ubiquitously true, but I think any other way of fixing that is probably
uglier.

So OK.

Reviewed-by: John Snow <jsnow@redhat.com>

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

* Re: [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed() Kevin Wolf
  2018-05-14 17:33   ` Max Reitz
@ 2018-05-14 23:43   ` John Snow
  1 sibling, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-14 23:43 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: qemu-devel, jcody, armbru, mreitz



On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> Since we introduced an explicit status to block job, BlockJob.completed
> is redundant because it can be derived from the status. Remove the field
> from BlockJob and add a function to derive it from the status at the Job
> level.
> 

You're braver than I am.

> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  include/block/blockjob.h |  3 ---
>  include/qemu/job.h       |  3 +++
>  blockjob.c               | 16 +++++++---------
>  job.c                    | 22 ++++++++++++++++++++++
>  qemu-img.c               |  4 ++--
>  5 files changed, 34 insertions(+), 14 deletions(-)
> 
> diff --git a/include/block/blockjob.h b/include/block/blockjob.h
> index ce136ff2ac..a2d16a700d 100644
> --- a/include/block/blockjob.h
> +++ b/include/block/blockjob.h
> @@ -88,9 +88,6 @@ typedef struct BlockJob {
>      /** The opaque value that is passed to the completion function.  */
>      void *opaque;
>  
> -    /** True when job has reported completion by calling block_job_completed. */
> -    bool completed;
> -
>      /** ret code passed to block_job_completed. */
>      int ret;
>  
> diff --git a/include/qemu/job.h b/include/qemu/job.h
> index 0ba6cb3161..87b0500795 100644
> --- a/include/qemu/job.h
> +++ b/include/qemu/job.h
> @@ -214,6 +214,9 @@ const char *job_type_str(Job *job);
>  /** Returns whether the job is scheduled for cancellation. */
>  bool job_is_cancelled(Job *job);
>  
> +/** Returns whether the job is in a completed state. */
> +bool job_is_completed(Job *job);
> +
>  /**
>   * Request @job to pause at the next pause point. Must be paired with
>   * job_resume(). If the job is supposed to be resumed by user action, call
> diff --git a/blockjob.c b/blockjob.c
> index a2b6bfc975..e0a51cfb3e 100644
> --- a/blockjob.c
> +++ b/blockjob.c
> @@ -194,7 +194,7 @@ static void block_job_detach_aio_context(void *opaque)
>  
>      job_pause(&job->job);
>  
> -    while (!job->job.paused && !job->completed) {
> +    while (!job->job.paused && !job_is_completed(&job->job)) {
>          block_job_drain(job);
>      }
>  
> @@ -270,7 +270,6 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
>  static void block_job_decommission(BlockJob *job)
>  {
>      assert(job);
> -    job->completed = true;

Oops. I forgot I left this stuff in here. It's definitely safe to
remove, as you know :)

>      job->job.busy = false;
>      job->job.paused = false;
>      job->job.deferred_to_main_loop = true;
> @@ -335,7 +334,7 @@ static void block_job_clean(BlockJob *job)
>  
>  static int block_job_finalize_single(BlockJob *job)
>  {
> -    assert(job->completed);
> +    assert(job_is_completed(&job->job));
>  
>      /* Ensure abort is called for late-transactional failures */
>      block_job_update_rc(job);
> @@ -428,10 +427,10 @@ static int block_job_finish_sync(BlockJob *job,
>      /* block_job_drain calls block_job_enter, and it should be enough to
>       * induce progress until the job completes or moves to the main thread.
>      */
> -    while (!job->job.deferred_to_main_loop && !job->completed) {
> +    while (!job->job.deferred_to_main_loop && !job_is_completed(&job->job)) {
>          block_job_drain(job);
>      }
> -    while (!job->completed) {
> +    while (!job_is_completed(&job->job)) {
>          aio_poll(qemu_get_aio_context(), true);
>      }
>      ret = (job_is_cancelled(&job->job) && job->ret == 0)
> @@ -472,7 +471,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
>      while (!QLIST_EMPTY(&txn->jobs)) {
>          other_job = QLIST_FIRST(&txn->jobs);
>          ctx = blk_get_aio_context(other_job->blk);
> -        if (!other_job->completed) {
> +        if (!job_is_completed(&other_job->job)) {
>              assert(job_is_cancelled(&other_job->job));
>              block_job_finish_sync(other_job, NULL, NULL);
>          }
> @@ -514,7 +513,7 @@ static void block_job_completed_txn_success(BlockJob *job)
>       * txn.
>       */
>      QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
> -        if (!other_job->completed) {
> +        if (!job_is_completed(&other_job->job)) {
>              return;
>          }
>          assert(other_job->ret == 0);
> @@ -848,9 +847,8 @@ void block_job_early_fail(BlockJob *job)
>  
>  void block_job_completed(BlockJob *job, int ret)
>  {
> -    assert(job && job->txn && !job->completed);
> +    assert(job && job->txn && !job_is_completed(&job->job));
>      assert(blk_bs(job->blk)->job == job);
> -    job->completed = true;
>      job->ret = ret;
>      block_job_update_rc(job);
>      trace_block_job_completed(job, ret, job->ret);
> diff --git a/job.c b/job.c
> index 94ad01a51a..60ccb0640b 100644
> --- a/job.c
> +++ b/job.c
> @@ -121,6 +121,28 @@ bool job_is_cancelled(Job *job)
>      return job->cancelled;
>  }
>  
> +bool job_is_completed(Job *job)
> +{
> +    switch (job->status) {
> +    case JOB_STATUS_UNDEFINED:
> +    case JOB_STATUS_CREATED:
> +    case JOB_STATUS_RUNNING:
> +    case JOB_STATUS_PAUSED:
> +    case JOB_STATUS_READY:
> +    case JOB_STATUS_STANDBY:
> +        return false;
> +    case JOB_STATUS_WAITING:
> +    case JOB_STATUS_PENDING:
> +    case JOB_STATUS_ABORTING:
> +    case JOB_STATUS_CONCLUDED:
> +    case JOB_STATUS_NULL:

Well, I'd argue that NULL shouldn't have state that it can answer with
one way or the other, but sure. In practice it ought not matter.

Reviewed-by: John Snow <jsnow@redhat.com>

> +        return true;
> +    default:
> +        g_assert_not_reached();
> +    }
> +    return false;
> +}
> +
>  bool job_started(Job *job)
>  {
>      return job->co;
> diff --git a/qemu-img.c b/qemu-img.c
> index 82f69269ae..6a2431a653 100644
> --- a/qemu-img.c
> +++ b/qemu-img.c
> @@ -867,9 +867,9 @@ static void run_block_job(BlockJob *job, Error **errp)
>          aio_poll(aio_context, true);
>          qemu_progress_print(job->len ?
>                              ((float)job->offset / job->len * 100.f) : 0.0f, 0);
> -    } while (!job->ready && !job->completed);
> +    } while (!job->ready && !job_is_completed(&job->job));
>  
> -    if (!job->completed) {
> +    if (!job_is_completed(&job->job)) {
>          ret = block_job_complete_sync(job, errp);
>      } else {
>          ret = job->ret;
> 

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

* Re: [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs Kevin Wolf
@ 2018-05-14 23:44   ` Max Reitz
  2018-05-17 13:50     ` Kevin Wolf
  0 siblings, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-14 23:44 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-09 18:26, Kevin Wolf wrote:
> This adds a test case that tests the new job-* QMP commands with
> mirror and backup block jobs.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  tests/qemu-iotests/219     | 201 ++++++++++++++++++++++++++++
>  tests/qemu-iotests/219.out | 327 +++++++++++++++++++++++++++++++++++++++++++++
>  tests/qemu-iotests/group   |   1 +
>  3 files changed, 529 insertions(+)
>  create mode 100755 tests/qemu-iotests/219
>  create mode 100644 tests/qemu-iotests/219.out

Test looks good, but it fails (for me) on tmpfs because at the point of
the first query-jobs, they already have an offset of 65536.

> diff --git a/tests/qemu-iotests/219 b/tests/qemu-iotests/219
> new file mode 100755
> index 0000000000..6cfe54b4db
> --- /dev/null
> +++ b/tests/qemu-iotests/219
> @@ -0,0 +1,201 @@

[...]

> +with iotests.FilePath('disk.img') as disk_path, \
> +     iotests.FilePath('copy.img') as copy_path, \
> +     iotests.VM() as vm:
> +
> +    img_size = '4M'
> +    iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size)
> +    iotests.qemu_io('-c', 'write 0 %s' % (img_size),
> +                    '-f', iotests.imgfmt, disk_path)
> +
> +    iotests.log('Launching VM...')
> +    vm.add_blockdev(vm.qmp_to_opts({
> +        'driver': iotests.imgfmt,
> +        'node-name': 'drive0-node',
> +        'file': {
> +            'driver': 'file',
> +            'filename': disk_path,
> +        },
> +    }))
> +    vm.launch()
> +
> +    # In order to keep things deterministic (especially progress in query-job,
> +    # but related to this also automatic state transitions like job
> +    # completion), but still get pause points often enough to avoid making this
> +    # test veey slow, it's important to have the right ratio between speed and

s/veey/very/

(Although "veey" does have its charm.)

> +    # buf_size.
> +    #
> +    # For backup, buf_size is hard-coded to the source image cluser size (64k),

s/cluser/cluster/

> +    # so we'll pick the same for mirror. The slice time, i.e. the granularity
> +    # of the rate limiting is 100ms. With a speed of 256k per second, we can
> +    # get four pause points per second. This gives us 250ms per iteration,
> +    # which should be enough to stay deterministic.
> +
> +    test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={
> +        'device': 'drive0-node',
> +        'target': copy_path,
> +        'sync': 'full',
> +        'speed': 262144,
> +        'buf_size': 65536,
> +    })
> +
> +    for auto_finalize in [True, False]:
> +        for auto_dismiss in [True, False]:
> +            test_job_lifecycle(vm, 'drive-backup', job_args={
> +                'device': 'drive0-node',
> +                'target': copy_path,
> +                'sync': 'full',
> +                'speed': 262144,
> +                'auto-finalize': auto_finalize,
> +                'auto-dismiss': auto_dismiss,
> +            })
> +
> +    vm.shutdown()
> diff --git a/tests/qemu-iotests/219.out b/tests/qemu-iotests/219.out
> new file mode 100644
> index 0000000000..e244be9ce8
> --- /dev/null
> +++ b/tests/qemu-iotests/219.out
> @@ -0,0 +1,327 @@

[...]

> +Pause/resume in READY
> +=== Testing block-job-pause/block-job-resume ===
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +=== Testing block-job-pause/job-resume ===
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +=== Testing job-pause/block-job-resume ===
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +=== Testing job-pause/job-resume ===
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> +{u'return': {}}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}

This is really, really mean.  Don't you have any compassion with the
poor little job that just wants to have Feierabend?

It worked so hard and it's always on standby and instantly ready when
you need it.  Yet, you keep it hanging.  That's not nice.

Max

> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
> +{u'return': {}}
> +
> +Waiting for PENDING state...
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> +{u'return': []}



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

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

* Re: [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-14 21:34   ` John Snow
@ 2018-05-15  9:06     ` Kevin Wolf
  0 siblings, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-15  9:06 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-block, qemu-devel, jcody, armbru, mreitz

Am 14.05.2018 um 23:34 hat John Snow geschrieben:
> 
> 
> On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> > This moves reference counting from BlockJob to Job.
> > 
> > In order to keep calling the BlockJob cleanup code when the job is
> > deleted via job_unref(), introduce a new JobDriver.free callback. Every
> > block job must use block_job_free() for this callback, this is asserted
> > in block_job_create().
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> 
> So far so good, though it does look a little silly that presently every
> last job has the exact same free callback.
> 
> Also, I forgot to reply to #13 with this, but I suppose that the
> approach you're taking -- of a fairly straightforward mechanical
> refactor -- means we don't really have the opportunity to change any of
> the pretty dumb names or existing peculiarities of design we've evolved.
> 
> I had hoped we'd be able to change a few things, but certainly keeping
> them as-is makes the whole re-factoring process an awful lot simpler...
> 
> ...Or maybe I'm getting ahead of myself, there's a lot of series left
> to go.

No, I think you're right. My approach was that we'd separate the generic
and the block-specific parts in this series without changing much in the
structure or the names, to keep the conversion as obvious as possible
(the series is long enough already).

However, I intentionally kept the QMP interface minimal where I wasn't
quite sure that we want to keep the old one. If you see anything in the
QMP interface that you'd like to see changed, please do comment.
Anything else is an implementation detail and can still be changed on
top of this series.

Kevin

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-14 15:52   ` Max Reitz
@ 2018-05-15 12:17     ` Kevin Wolf
  2018-05-16 10:51       ` Max Reitz
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-15 12:17 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel, stefanha

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

Am 14.05.2018 um 17:52 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> >  include/block/blockjob.h     |  5 ----
> >  include/block/blockjob_int.h | 19 ---------------
> >  include/qemu/job.h           | 20 ++++++++++++++++
> >  block/backup.c               |  7 +++---
> >  block/commit.c               | 11 +++++----
> >  block/mirror.c               | 15 ++++++------
> >  block/stream.c               | 14 +++++------
> >  blockjob.c                   | 57 ++++----------------------------------------
> >  job.c                        | 33 +++++++++++++++++++++++++
> >  tests/test-bdrv-drain.c      |  7 +++---
> >  tests/test-blockjob-txn.c    | 13 +++++-----
> >  tests/test-blockjob.c        |  7 +++---
> >  12 files changed, 98 insertions(+), 110 deletions(-)
> 
> [...]
> 
> > diff --git a/block/commit.c b/block/commit.c
> > index 85baea8f92..d326766e4d 100644
> > --- a/block/commit.c
> > +++ b/block/commit.c
> > @@ -72,9 +72,10 @@ typedef struct {
> >      int ret;
> >  } CommitCompleteData;
> >  
> > -static void commit_complete(BlockJob *job, void *opaque)
> > +static void commit_complete(Job *job, void *opaque)
> >  {
> > -    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
> > +    CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
> 
> Now this is just abuse.
> 
> ...but it's not the first time someone packs two container_of() into
> one, it appears.  So, whatever, I guess.

I don't think it's abuse. Why wouldn't I directly cast to the type that
I really want instead of casting to each of the uninteresting parent
classes, too?

> > @@ -170,3 +171,35 @@ void job_unref(Job *job)
> >          g_free(job);
> >      }
> >  }
> > +
> > +typedef struct {
> > +    Job *job;
> > +    JobDeferToMainLoopFn *fn;
> > +    void *opaque;
> > +} JobDeferToMainLoopData;
> > +
> > +static void job_defer_to_main_loop_bh(void *opaque)
> > +{
> > +    JobDeferToMainLoopData *data = opaque;
> > +    Job *job = data->job;
> > +    AioContext *aio_context = job->aio_context;
> > +
> > +    /* Prevent race with job_defer_to_main_loop() */
> > +    aio_context_acquire(aio_context);
> 
> I don't have a good feeling about this.  The original code had this
> comment above an aio_context_acquire() for a context that might
> decidedly not have anything to do with the BB's context;
> block_job_defer_to_main_loop()'s description was that it would acquire
> the latter, so why did it acquire the former at all?
> 
> We wouldn't need this comment here at all, because acquiring this
> AioContext is part of the interface.  That's why I don't have a good
> feeling.

To be honest, I don't fully understand what the old code was trying to
do or what race it was talking about, because I don't see any potential
race with job_defer_to_main_loop() (neither in the old nor in the new
code).

My suspicion is that Stefan solved the race that you reported [1] (and
which was not with job_defer_to_main_loop(), but with random code that
runs between that and the BH) just by adding some more code that made
the scenario safe, but missed that this also made the existing code
redundant. In other words, I think taking data->aio_context didn't serve
a purpose even in the old code.

The only thing it could possibly protect is the access of data->job->bs,
but that's not something that changes between job_defer_to_main_loop()
and the execution of the BH.

[1] https://lists.gnu.org/archive/html/qemu-devel/2014-10/msg00260.html

> The best explanation I can come up with is that the original code
> acquired the AioContext both of the block device at the time of the BH
> (because that needs to be done), and at the time of
> block_job_defer_to_main_loop() -- because the latter is probably the
> context the block_job_defer_to_main_loop() call came from, so it should
> be (b)locked.
> 
> But if that's the case, then the same should be done here.  The context
> of the job may change between scheduling the BH and the BH being
> executed, so we might lock a different context here than the one
> job_defer_to_main_loop() ran in (i.e., job->aio_context at the time of
> job_defer_to_main_loop() running).  And maybe we should lock that old
> context, too -- just like block_job_defer_to_main_loop_bh() did.

Why should we lock the old context? We don't access anything protected
by it. Even the data->job->bs access has gone away because we now have
job->aio_context.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-14 22:33   ` John Snow
@ 2018-05-15 12:22     ` Kevin Wolf
  0 siblings, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-15 12:22 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-block, qemu-devel, jcody, armbru, mreitz

Am 15.05.2018 um 00:33 hat John Snow geschrieben:
> 
> 
> On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> 
> Hmm, this one is a bit more than just code motion due to the way the
> aio_context acquisition has changed. I think at a minimum a good commit
> message is warranted.

I know it took a while to convince myself that it's right. I wonder
where the commit message has gone...

> > @@ -170,3 +171,35 @@ void job_unref(Job *job)
> >          g_free(job);
> >      }
> >  }
> > +
> > +typedef struct {
> > +    Job *job;
> > +    JobDeferToMainLoopFn *fn;
> > +    void *opaque;
> > +} JobDeferToMainLoopData;
> > +
> > +static void job_defer_to_main_loop_bh(void *opaque)
> > +{
> > +    JobDeferToMainLoopData *data = opaque;
> > +    Job *job = data->job;
> > +    AioContext *aio_context = job->aio_context;
> > +
> > +    /* Prevent race with job_defer_to_main_loop() */
> > +    aio_context_acquire(aio_context);
> > +    data->fn(data->job, data->opaque);
> > +    aio_context_release(aio_context);
> > +
> > +    g_free(data);
> > +}
> > +
> 
> This function showed up in '14, with dec7d42 from Stefan. His first
> draft looked like Kevin's, until:
> 
> https://lists.gnu.org/archive/html/qemu-devel/2014-10/msg00111.html
> 
> Max, from 2014:
> 
> "I'm not so sure whether it'd be possible to change the BDS's AIO
> context (in another thread) after the call to bdrv_get_aio_context() in
> block_job_defer_to_main_loop() and before
> block_job_defer_to_main_loop_bh() is run. If so, this might create race
> conditions."
> 
> Err, I dunno either.

This one at least is not a problem in the new code any more because we
have job->aio_context now, which is updated when the BDS's context
changes. We read job->aio_context only in the BH, so we always get the
current one.

In contrast, the old code put it into BlockJobDeferToMainLoopData at
the time when the BH was scheduled, so it could be outdated when the BH
actually ran.

Kevin

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

* Re: [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation to Job
  2018-05-14 20:53   ` Max Reitz
@ 2018-05-15 12:59     ` Kevin Wolf
  2018-05-16 10:52       ` Max Reitz
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-15 12:59 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 14.05.2018 um 22:53 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > This moves the top-level job completion and cancellation functions from
> > BlockJob to Job.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>

> > @@ -3362,7 +3362,7 @@ static void bdrv_close(BlockDriverState *bs)
> >  
> >  void bdrv_close_all(void)
> >  {
> > -    block_job_cancel_sync_all();
> > +    job_cancel_sync_all();
> 
> Do we really want to cancel jobs that might have nothing to do with the
> block layer?

Yes, though I admit it's a bit ugly to do it here. Alternatively, I
could assert here that no jobs are running and pull the
job_cancel_sync_all() into the callers. For vl.c, this is trivial.
qemu-nbd.c uses 'atexit(bdrv_close_all);', so I'd have to introduce a
wrapper function that calls both job_cancel_sync_all() and
bdrv_close_all().

Would you prefer that?

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands
  2018-05-14 22:31   ` Max Reitz
@ 2018-05-15 14:08     ` Kevin Wolf
  2018-05-16 10:54       ` Max Reitz
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-15 14:08 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 15.05.2018 um 00:31 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > This adds QMP commands that control the transition between states of the
> > job lifecycle.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> >  qapi/job.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++
> >  job-qmp.c     | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  MAINTAINERS   |   1 +
> >  Makefile.objs |   2 +-
> >  trace-events  |   9 ++++
> >  5 files changed, 263 insertions(+), 1 deletion(-)
> >  create mode 100644 job-qmp.c
> > 
> > diff --git a/qapi/job.json b/qapi/job.json
> > index bd88a358d0..7b84158292 100644
> > --- a/qapi/job.json
> > +++ b/qapi/job.json
> > @@ -63,3 +63,115 @@
> >  { 'event': 'JOB_STATUS_CHANGE',
> >    'data': { 'id': 'str',
> >              'status': 'JobStatus' } }
> > +
> > +##
> > +# @job-pause:
> > +#
> > +# Pause an active job.
> > +#
> > +# This command returns immediately after marking the active job for pausing.
> > +# Pausing an already paused job has no cumulative effect; a single job-resume
> > +# command will resume the job.
> 
> Pausing an already paused job is, in fact, an error.
> 
> (Which should be noted here instead of making it appear like it'd be
> idempotent.)

Aye. Interestingly, I managed to fix it below in job-resume, but missed
it here. I should also stick in a patch somewhere early in the series
that fixes the documentation of block-job-pause/resume.

> > +##
> > +# @job-cancel:
> > +#
> > +# Instruct an active background job to cancel at the next opportunity.
> > +# This command returns immediately after marking the active job for
> > +# cancellation.
> > +#
> > +# The job will cancel as soon as possible and then emit a JOB_STATUS_CHANGE
> > +# event. Usually, the status will change to ABORTING, but it is possible that
> > +# a job successfully completes (e.g. because it was almost done and there was
> > +# no opportunity to cancel earlier than completing the job) and transitions to
> > +# PENDING instead.
> > +#
> > +# Note that if you issue 'job-cancel' after a mirror block job has indicated
> > +# (via the event BLOCK_JOB_READY, and by transitioning into the READY state)
> > +# that the source and destination are synchronized, then the job always
> > +# completes successfully and transitions to PENDING as well as triggering the
> > +# event BLOCK_JOB_COMPLETED, to indicate that the mirroring has ended and the
> > +# destination now has a point-in-time copy tied to the time of the
> > +# cancellation.
> > +#
> > +# @id: The job identifier.
> > +#
> > +# @force: If true, and the job is already in the READY state, abandon the job
> > +#         immediately (even if it is paused) instead of waiting for the
> > +#         destination to complete its final synchronization
> 
> The note on "final synchronization" is extremely mirror-specific.  I see
> three choices here:
> 
> (1) If mirror stays the only job with this special cancel semantics,
> then we are free to note that this is a mirror-specific flag here.
> 
> (2) Even if some other job might come along at some point where use of
> @force may make sense, that doesn't stop us from now noting that only
> mirror supports this, which helps readers understand what "destination"
> and "final synchronization" mean.
> 
> (Yes, so (1) and (2) are basically the same.)
> 
> (3) We try to find some general description and drop the last part.
> Like "If a job would normally decide to complete instead of actually
> aborting, this flag can be used to convince it otherwise."  But that's
> so handwavy, I'd rather just mark it as a special mirror flag for now.

Or how about this one?

(4) Mirror is really abusing cancel for a second completion mode and we
    don't want to have this kind of ugliness in job-cancel. Remove
    @force from the schema and internally always use force=true. For
    now, block-job-cancel does the job (no pun intended) for mirror, and
    maybe we can later introduce a way to select completion mode with
    job-complete.

This would also get us rid of that whole long paragraph above that
explains how mirror jobs have an exceptional behaviour.

> > diff --git a/Makefile.objs b/Makefile.objs
> > index 3df8d58e49..253e0356f3 100644
> > --- a/Makefile.objs
> > +++ b/Makefile.objs
> > @@ -66,7 +66,7 @@ chardev-obj-y = chardev/
> >  # block-obj-y is code used by both qemu system emulation and qemu-img
> >  
> >  block-obj-y += nbd/
> > -block-obj-y += block.o blockjob.o job.o
> > +block-obj-y += block.o blockjob.o job.o job-qmp.o
> 
> Shouldn't this be in common-obj-y like blockdev?

Seems to build with that change, so it can't be wrong...

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 00/42] Generic background jobs
  2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
                   ` (41 preceding siblings ...)
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs Kevin Wolf
@ 2018-05-15 14:15 ` Kevin Wolf
  2018-05-15 14:43   ` Eric Blake
  42 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-15 14:15 UTC (permalink / raw)
  To: qemu-block; +Cc: mreitz, eblake, jsnow, armbru, jcody, qemu-devel

Am 09.05.2018 um 18:25 hat Kevin Wolf geschrieben:
> Before we can make x-blockdev-create a background job, we need to
> generalise the job infrastructure so that it can be used without any
> associated block node.
> 
> This series extracts a Job object from the block job infrastructure,
> which should contain everything related to jobs that doesn't require the
> block layer to be involved.
> 
> Job creation with a centralised job-create command (if we even want
> this) as well as the actual conversion of x-blockdev-create to Job is
> left for another series. With 42 patches, the series must be perfect and
> I can't add any more patches.

Thanks for the review, applied patches 1-6 in the hope of making v2 at
least a little bit shorter. :-)

Kevin

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

* Re: [Qemu-devel] [PATCH 00/42] Generic background jobs
  2018-05-15 14:15 ` [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
@ 2018-05-15 14:43   ` Eric Blake
  0 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-15 14:43 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/15/2018 09:15 AM, Kevin Wolf wrote:
> Am 09.05.2018 um 18:25 hat Kevin Wolf geschrieben:
>> Before we can make x-blockdev-create a background job, we need to
>> generalise the job infrastructure so that it can be used without any
>> associated block node.
>>
>> This series extracts a Job object from the block job infrastructure,
>> which should contain everything related to jobs that doesn't require the
>> block layer to be involved.
>>
>> Job creation with a centralised job-create command (if we even want
>> this) as well as the actual conversion of x-blockdev-create to Job is
>> left for another series. With 42 patches, the series must be perfect and
>> I can't add any more patches.
> 
> Thanks for the review, applied patches 1-6 in the hope of making v2 at
> least a little bit shorter. :-)

Or now you can split/add patches until you're back up to 42 patches in v2 ;)

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-15 12:17     ` Kevin Wolf
@ 2018-05-16 10:51       ` Max Reitz
  2018-05-16 18:32         ` Eric Blake
  0 siblings, 1 reply; 146+ messages in thread
From: Max Reitz @ 2018-05-16 10:51 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel, stefanha

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

On 2018-05-15 14:17, Kevin Wolf wrote:
> Am 14.05.2018 um 17:52 hat Max Reitz geschrieben:
>> On 2018-05-09 18:26, Kevin Wolf wrote:
>>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>>> ---
>>>  include/block/blockjob.h     |  5 ----
>>>  include/block/blockjob_int.h | 19 ---------------
>>>  include/qemu/job.h           | 20 ++++++++++++++++
>>>  block/backup.c               |  7 +++---
>>>  block/commit.c               | 11 +++++----
>>>  block/mirror.c               | 15 ++++++------
>>>  block/stream.c               | 14 +++++------
>>>  blockjob.c                   | 57 ++++----------------------------------------
>>>  job.c                        | 33 +++++++++++++++++++++++++
>>>  tests/test-bdrv-drain.c      |  7 +++---
>>>  tests/test-blockjob-txn.c    | 13 +++++-----
>>>  tests/test-blockjob.c        |  7 +++---
>>>  12 files changed, 98 insertions(+), 110 deletions(-)
>>
>> [...]
>>
>>> diff --git a/block/commit.c b/block/commit.c
>>> index 85baea8f92..d326766e4d 100644
>>> --- a/block/commit.c
>>> +++ b/block/commit.c
>>> @@ -72,9 +72,10 @@ typedef struct {
>>>      int ret;
>>>  } CommitCompleteData;
>>>  
>>> -static void commit_complete(BlockJob *job, void *opaque)
>>> +static void commit_complete(Job *job, void *opaque)
>>>  {
>>> -    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
>>> +    CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
>>
>> Now this is just abuse.
>>
>> ...but it's not the first time someone packs two container_of() into
>> one, it appears.  So, whatever, I guess.
> 
> I don't think it's abuse. Why wouldn't I directly cast to the type that
> I really want instead of casting to each of the uninteresting parent
> classes, too?

Because the final parameter is called "member" and not "path". :-)

>>> @@ -170,3 +171,35 @@ void job_unref(Job *job)
>>>          g_free(job);
>>>      }
>>>  }
>>> +
>>> +typedef struct {
>>> +    Job *job;
>>> +    JobDeferToMainLoopFn *fn;
>>> +    void *opaque;
>>> +} JobDeferToMainLoopData;
>>> +
>>> +static void job_defer_to_main_loop_bh(void *opaque)
>>> +{
>>> +    JobDeferToMainLoopData *data = opaque;
>>> +    Job *job = data->job;
>>> +    AioContext *aio_context = job->aio_context;
>>> +
>>> +    /* Prevent race with job_defer_to_main_loop() */
>>> +    aio_context_acquire(aio_context);
>>
>> I don't have a good feeling about this.  The original code had this
>> comment above an aio_context_acquire() for a context that might
>> decidedly not have anything to do with the BB's context;
>> block_job_defer_to_main_loop()'s description was that it would acquire
>> the latter, so why did it acquire the former at all?
>>
>> We wouldn't need this comment here at all, because acquiring this
>> AioContext is part of the interface.  That's why I don't have a good
>> feeling.
> 
> To be honest, I don't fully understand what the old code was trying to
> do or what race it was talking about, because I don't see any potential
> race with job_defer_to_main_loop() (neither in the old nor in the new
> code).
> 
> My suspicion is that Stefan solved the race that you reported [1] (and
> which was not with job_defer_to_main_loop(), but with random code that
> runs between that and the BH) just by adding some more code that made
> the scenario safe, but missed that this also made the existing code
> redundant. In other words, I think taking data->aio_context didn't serve
> a purpose even in the old code.

Possible, yes.

Also seems more likely than any of the explanations I tried to come up with.

> The only thing it could possibly protect is the access of data->job->bs,
> but that's not something that changes between job_defer_to_main_loop()
> and the execution of the BH.
> 
> [1] https://lists.gnu.org/archive/html/qemu-devel/2014-10/msg00260.html
> 
>> The best explanation I can come up with is that the original code
>> acquired the AioContext both of the block device at the time of the BH
>> (because that needs to be done), and at the time of
>> block_job_defer_to_main_loop() -- because the latter is probably the
>> context the block_job_defer_to_main_loop() call came from, so it should
>> be (b)locked.
>>
>> But if that's the case, then the same should be done here.  The context
>> of the job may change between scheduling the BH and the BH being
>> executed, so we might lock a different context here than the one
>> job_defer_to_main_loop() ran in (i.e., job->aio_context at the time of
>> job_defer_to_main_loop() running).  And maybe we should lock that old
>> context, too -- just like block_job_defer_to_main_loop_bh() did.
> 
> Why should we lock the old context? We don't access anything protected
> by it. Even the data->job->bs access has gone away because we now have
> job->aio_context.

Because the old code did so and I don't know why. O:-)

Your explanation does make sense to me, though, so:

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


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

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

* Re: [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation to Job
  2018-05-15 12:59     ` Kevin Wolf
@ 2018-05-16 10:52       ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-16 10:52 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-15 14:59, Kevin Wolf wrote:
> Am 14.05.2018 um 22:53 hat Max Reitz geschrieben:
>> On 2018-05-09 18:26, Kevin Wolf wrote:
>>> This moves the top-level job completion and cancellation functions from
>>> BlockJob to Job.
>>>
>>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> 
>>> @@ -3362,7 +3362,7 @@ static void bdrv_close(BlockDriverState *bs)
>>>  
>>>  void bdrv_close_all(void)
>>>  {
>>> -    block_job_cancel_sync_all();
>>> +    job_cancel_sync_all();
>>
>> Do we really want to cancel jobs that might have nothing to do with the
>> block layer?
> 
> Yes, though I admit it's a bit ugly to do it here. Alternatively, I
> could assert here that no jobs are running and pull the
> job_cancel_sync_all() into the callers. For vl.c, this is trivial.
> qemu-nbd.c uses 'atexit(bdrv_close_all);', so I'd have to introduce a
> wrapper function that calls both job_cancel_sync_all() and
> bdrv_close_all().
> 
> Would you prefer that?

Yes, I'd like that.

Thanks!

Max


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

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

* Re: [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands
  2018-05-15 14:08     ` Kevin Wolf
@ 2018-05-16 10:54       ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-16 10:54 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-15 16:08, Kevin Wolf wrote:
> Am 15.05.2018 um 00:31 hat Max Reitz geschrieben:
>> On 2018-05-09 18:26, Kevin Wolf wrote:
>>> This adds QMP commands that control the transition between states of the
>>> job lifecycle.
>>>
>>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>>> ---
>>>  qapi/job.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++
>>>  job-qmp.c     | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>  MAINTAINERS   |   1 +
>>>  Makefile.objs |   2 +-
>>>  trace-events  |   9 ++++
>>>  5 files changed, 263 insertions(+), 1 deletion(-)
>>>  create mode 100644 job-qmp.c
>>>
>>> diff --git a/qapi/job.json b/qapi/job.json
>>> index bd88a358d0..7b84158292 100644
>>> --- a/qapi/job.json
>>> +++ b/qapi/job.json
>>> @@ -63,3 +63,115 @@
>>>  { 'event': 'JOB_STATUS_CHANGE',
>>>    'data': { 'id': 'str',
>>>              'status': 'JobStatus' } }
>>> +
>>> +##
>>> +# @job-pause:
>>> +#
>>> +# Pause an active job.
>>> +#
>>> +# This command returns immediately after marking the active job for pausing.
>>> +# Pausing an already paused job has no cumulative effect; a single job-resume
>>> +# command will resume the job.
>>
>> Pausing an already paused job is, in fact, an error.
>>
>> (Which should be noted here instead of making it appear like it'd be
>> idempotent.)
> 
> Aye. Interestingly, I managed to fix it below in job-resume, but missed
> it here. I should also stick in a patch somewhere early in the series
> that fixes the documentation of block-job-pause/resume.
> 
>>> +##
>>> +# @job-cancel:
>>> +#
>>> +# Instruct an active background job to cancel at the next opportunity.
>>> +# This command returns immediately after marking the active job for
>>> +# cancellation.
>>> +#
>>> +# The job will cancel as soon as possible and then emit a JOB_STATUS_CHANGE
>>> +# event. Usually, the status will change to ABORTING, but it is possible that
>>> +# a job successfully completes (e.g. because it was almost done and there was
>>> +# no opportunity to cancel earlier than completing the job) and transitions to
>>> +# PENDING instead.
>>> +#
>>> +# Note that if you issue 'job-cancel' after a mirror block job has indicated
>>> +# (via the event BLOCK_JOB_READY, and by transitioning into the READY state)
>>> +# that the source and destination are synchronized, then the job always
>>> +# completes successfully and transitions to PENDING as well as triggering the
>>> +# event BLOCK_JOB_COMPLETED, to indicate that the mirroring has ended and the
>>> +# destination now has a point-in-time copy tied to the time of the
>>> +# cancellation.
>>> +#
>>> +# @id: The job identifier.
>>> +#
>>> +# @force: If true, and the job is already in the READY state, abandon the job
>>> +#         immediately (even if it is paused) instead of waiting for the
>>> +#         destination to complete its final synchronization
>>
>> The note on "final synchronization" is extremely mirror-specific.  I see
>> three choices here:
>>
>> (1) If mirror stays the only job with this special cancel semantics,
>> then we are free to note that this is a mirror-specific flag here.
>>
>> (2) Even if some other job might come along at some point where use of
>> @force may make sense, that doesn't stop us from now noting that only
>> mirror supports this, which helps readers understand what "destination"
>> and "final synchronization" mean.
>>
>> (Yes, so (1) and (2) are basically the same.)
>>
>> (3) We try to find some general description and drop the last part.
>> Like "If a job would normally decide to complete instead of actually
>> aborting, this flag can be used to convince it otherwise."  But that's
>> so handwavy, I'd rather just mark it as a special mirror flag for now.
> 
> Or how about this one?
> 
> (4) Mirror is really abusing cancel for a second completion mode and we
>     don't want to have this kind of ugliness in job-cancel.

Yeah, well, right, that was implicit and I tried to be more diplomatic
by calling it "special semantics". :-)

>                                                             Remove
>     @force from the schema and internally always use force=true. For
>     now, block-job-cancel does the job (no pun intended) for mirror, and
>     maybe we can later introduce a way to select completion mode with
>     job-complete.

Sounds good to me.

> This would also get us rid of that whole long paragraph above that
> explains how mirror jobs have an exceptional behaviour.

Yes.

Max

>>> diff --git a/Makefile.objs b/Makefile.objs
>>> index 3df8d58e49..253e0356f3 100644
>>> --- a/Makefile.objs
>>> +++ b/Makefile.objs
>>> @@ -66,7 +66,7 @@ chardev-obj-y = chardev/
>>>  # block-obj-y is code used by both qemu system emulation and qemu-img
>>>  
>>>  block-obj-y += nbd/
>>> -block-obj-y += block.o blockjob.o job.o
>>> +block-obj-y += block.o blockjob.o job.o job-qmp.o
>>
>> Shouldn't this be in common-obj-y like blockdev?
> 
> Seems to build with that change, so it can't be wrong...
> 
> Kevin
> 



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

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

* Re: [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command
  2018-05-14 23:09   ` Max Reitz
@ 2018-05-16 11:21     ` Kevin Wolf
  2018-05-16 11:27       ` Max Reitz
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-16 11:21 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 15.05.2018 um 01:09 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > This adds a minimal query-jobs implementation that shouldn't pose many
> > design questions. It can later be extended to expose more information,
> > and especially job-specific information.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>

> > +##
> > +# @JobInfo:
> > +#
> > +# Information about a job.
> > +#
> > +# @id:                  The job identifier
> > +#
> > +# @type:                The job type
> 
> I'm not really happy with this description that effectively provides no
> information that one cannot already get from the field name, but I
> cannot come up with something better, so I'd rather stop typing now.
> 
> (OK, my issue here is that "job type" can be anything.  That could mean
> e.g. "Is this a block job?".   Maybe an explicit reference to JobType
> might help here, although that is already part of the documentation.  On
> that note, maybe a quote from the documentation might help make my point
> that this description is not very useful:
> 
>        "type: JobType"
>            The job type
> 
> Maybe "The kind of job that is being performed"?)

IMHO, that's just a more verbose way of saying nothing new. The
"problem" is that "type: JobType" is already descriptive enough, there
is no real need for providing any additional information.

Also, I'd like to mention that almost all of the documentation is taken
from BlockJobInfo, so if we decide to change something, we should
probably change it in both places.

> > +# @status:              Current job state/status
> 
> Why the "state/status"?  Forgive me my incompetence, but I don't see a
> real difference here.  But in any case, one ought to be enough, no?
> 
> (OK, full disclosure: My gut tells me "status" is what we now call the
> "progress", and this field should be called "state".  But it's called
> "status" now and it doesn't really make a difference, so it's fine to
> describe it as either.)

I'll defer to John who wrote this description originally. I think we're
just using status/state inconsistently for the same thing (JobStatus,
which represents the states of the job state machine).

> > +#
> > +# @current-progress:    The current progress value
> > +#
> > +# @total-progress:      The maximum progress value
> 
> Hm, not really.  This makes it sound like at any point, this will be the
> maximum even in the future, but that's not true.
> 
> Maybe "estimated progress maximum"?  Or be even more verbose (no, that
> doesn't hurt): "This is an estimation of the value @current-progress
> needs to reach for the job to complete."
> 
> (Actually, I find it important to note that it is an estimation, maybe
> we event want to be really explicit about the fact that this value may
> change all the time, in any direction.)

I'll try to improve the documentation of these fields (both here and in
BlockJobInfo) for v2.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command
  2018-05-16 11:21     ` Kevin Wolf
@ 2018-05-16 11:27       ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-16 11:27 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-16 13:21, Kevin Wolf wrote:
> Am 15.05.2018 um 01:09 hat Max Reitz geschrieben:
>> On 2018-05-09 18:26, Kevin Wolf wrote:
>>> This adds a minimal query-jobs implementation that shouldn't pose many
>>> design questions. It can later be extended to expose more information,
>>> and especially job-specific information.
>>>
>>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> 
>>> +##
>>> +# @JobInfo:
>>> +#
>>> +# Information about a job.
>>> +#
>>> +# @id:                  The job identifier
>>> +#
>>> +# @type:                The job type
>>
>> I'm not really happy with this description that effectively provides no
>> information that one cannot already get from the field name, but I
>> cannot come up with something better, so I'd rather stop typing now.
>>
>> (OK, my issue here is that "job type" can be anything.  That could mean
>> e.g. "Is this a block job?".   Maybe an explicit reference to JobType
>> might help here, although that is already part of the documentation.  On
>> that note, maybe a quote from the documentation might help make my point
>> that this description is not very useful:
>>
>>        "type: JobType"
>>            The job type
>>
>> Maybe "The kind of job that is being performed"?)
> 
> IMHO, that's just a more verbose way of saying nothing new.

True, but “Job.type: JobType -- The job type” is exactly the kind of
documentation people like to make fun of.

>                                                             The
> "problem" is that "type: JobType" is already descriptive enough, there
> is no real need for providing any additional information.

Yes, but it still doesn’t read nicely.

One could give examples (“e.g. mirror or backup”), but then again, if we
were to remove any of those, we’d have to update the documentation here.
 Also, you can again argue that such a list is precisely under JobType,
which is true.

> Also, I'd like to mention that almost all of the documentation is taken
> from BlockJobInfo, so if we decide to change something, we should
> probably change it in both places.

Ha!  Good excuse, but you know, you touched this, now you own it. :-)

>>> +# @status:              Current job state/status
>>
>> Why the "state/status"?  Forgive me my incompetence, but I don't see a
>> real difference here.  But in any case, one ought to be enough, no?
>>
>> (OK, full disclosure: My gut tells me "status" is what we now call the
>> "progress", and this field should be called "state".  But it's called
>> "status" now and it doesn't really make a difference, so it's fine to
>> describe it as either.)
> 
> I'll defer to John who wrote this description originally. I think we're
> just using status/state inconsistently for the same thing (JobStatus,
> which represents the states of the job state machine).
> 
>>> +#
>>> +# @current-progress:    The current progress value
>>> +#
>>> +# @total-progress:      The maximum progress value
>>
>> Hm, not really.  This makes it sound like at any point, this will be the
>> maximum even in the future, but that's not true.
>>
>> Maybe "estimated progress maximum"?  Or be even more verbose (no, that
>> doesn't hurt): "This is an estimation of the value @current-progress
>> needs to reach for the job to complete."
>>
>> (Actually, I find it important to note that it is an estimation, maybe
>> we event want to be really explicit about the fact that this value may
>> change all the time, in any direction.)
> 
> I'll try to improve the documentation of these fields (both here and in
> BlockJobInfo) for v2.

Thanks!

Max


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

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

* Re: [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event
  2018-05-14 22:11   ` Max Reitz
@ 2018-05-16 16:15     ` Kevin Wolf
  0 siblings, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-16 16:15 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 15.05.2018 um 00:11 hat Max Reitz geschrieben:
> You forgot to adjust 094, and although I cannot prove it (I don't have
> an nfs setup handy right now), I have a hunch that this patch breaks 173
> as well.

NFS qemu-iotests look completely broken. And the block driver, too. I've
sent a fix for the block driver at least...

Not sure how the qemu-iotests are supposed to work, but I get messages
like these:

+mkdir: cannot create directory 'nfs://127.0.0.1//home/kwolf/source/qemu/tests/qemu-iotests/scratch': No such file or directory
+common.config: Error: $TEST_DIR (nfs://127.0.0.1//home/kwolf/source/qemu/tests/qemu-iotests/scratch) is not a directory

I guess I'm okay with breaking 173 in this series when the NFS tests are
in this state...

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code to Job
  2018-05-14 23:02   ` John Snow
@ 2018-05-16 16:50     ` Kevin Wolf
  2018-05-16 17:38       ` John Snow
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-16 16:50 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-block, qemu-devel, jcody, armbru, mreitz

Am 15.05.2018 um 01:02 hat John Snow geschrieben:
> 
> 
> On 05/09/2018 12:26 PM, Kevin Wolf wrote:
> > This commit moves some core functions for dealing with the job coroutine
> > from BlockJob to Job. This includes primarily entering the coroutine
> > (both for the first and reentering) and yielding explicitly and at pause
> > points.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> >  include/block/blockjob.h     |  40 ---------
> >  include/block/blockjob_int.h |  26 ------
> >  include/qemu/job.h           |  76 ++++++++++++++++
> >  block/backup.c               |   2 +-
> >  block/commit.c               |   4 +-
> >  block/mirror.c               |  22 ++---
> >  block/replication.c          |   2 +-
> >  block/stream.c               |   4 +-
> >  blockdev.c                   |   8 +-
> >  blockjob.c                   | 201 +++++++------------------------------------
> >  job.c                        | 137 +++++++++++++++++++++++++++++
> >  tests/test-bdrv-drain.c      |  38 ++++----
> >  tests/test-blockjob-txn.c    |  12 +--
> >  tests/test-blockjob.c        |  14 +--
> >  14 files changed, 296 insertions(+), 290 deletions(-)
> > 
> 
> [...]
> 
> >  
> >  /* Assumes the block_job_mutex is held */
> > -static bool block_job_timer_pending(BlockJob *job)
> > +static bool job_timer_pending(Job *job)
> 
> Is this intentionally left behind in blockjob.c, even once you've
> changed the name (and moved the state to job.c?)

Yes, it's just a small callback that is structually part of
block_job_set_speed(). Maybe I shouldn't rename it, but it does get a
Job rather than a BlockJob now, so I'm not sure.

Kevin

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

* Re: [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code to Job
  2018-05-16 16:50     ` Kevin Wolf
@ 2018-05-16 17:38       ` John Snow
  0 siblings, 0 replies; 146+ messages in thread
From: John Snow @ 2018-05-16 17:38 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, qemu-devel, jcody, armbru, mreitz



On 05/16/2018 12:50 PM, Kevin Wolf wrote:
> Am 15.05.2018 um 01:02 hat John Snow geschrieben:
>>
>>
>> On 05/09/2018 12:26 PM, Kevin Wolf wrote:
>>> This commit moves some core functions for dealing with the job coroutine
>>> from BlockJob to Job. This includes primarily entering the coroutine
>>> (both for the first and reentering) and yielding explicitly and at pause
>>> points.
>>>
>>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>>> ---
>>>  include/block/blockjob.h     |  40 ---------
>>>  include/block/blockjob_int.h |  26 ------
>>>  include/qemu/job.h           |  76 ++++++++++++++++
>>>  block/backup.c               |   2 +-
>>>  block/commit.c               |   4 +-
>>>  block/mirror.c               |  22 ++---
>>>  block/replication.c          |   2 +-
>>>  block/stream.c               |   4 +-
>>>  blockdev.c                   |   8 +-
>>>  blockjob.c                   | 201 +++++++------------------------------------
>>>  job.c                        | 137 +++++++++++++++++++++++++++++
>>>  tests/test-bdrv-drain.c      |  38 ++++----
>>>  tests/test-blockjob-txn.c    |  12 +--
>>>  tests/test-blockjob.c        |  14 +--
>>>  14 files changed, 296 insertions(+), 290 deletions(-)
>>>
>>
>> [...]
>>
>>>  
>>>  /* Assumes the block_job_mutex is held */
>>> -static bool block_job_timer_pending(BlockJob *job)
>>> +static bool job_timer_pending(Job *job)
>>
>> Is this intentionally left behind in blockjob.c, even once you've
>> changed the name (and moved the state to job.c?)
> 
> Yes, it's just a small callback that is structually part of
> block_job_set_speed(). Maybe I shouldn't rename it, but it does get a
> Job rather than a BlockJob now, so I'm not sure.
> 
> Kevin
> 

Your choice; it's not clear to me yet which things truly belong in which
file because I haven't had a chance to look at the finished result. I'm
sure if it looks like it needs to move later that someone will
eventually move it.

--js

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

* Re: [Qemu-devel] [PATCH 11/42] job: Add job_delete()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 11/42] job: Add job_delete() Kevin Wolf
  2018-05-11 22:56   ` Max Reitz
  2018-05-14 20:15   ` John Snow
@ 2018-05-16 17:54   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 17:54 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> This moves freeing the Job object and its fields from block_job_unref()
> to job_delete().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>   include/qemu/job.h | 3 +++
>   blockjob.c         | 3 +--
>   job.c              | 6 ++++++
>   3 files changed, 10 insertions(+), 2 deletions(-)
> 

> +
> +void job_delete(Job *job)
> +{
> +    g_free(job->id);
> +    g_free(job);
> +}

Should this be free-like, acting as a no-op when job == NULL on input?

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs Kevin Wolf
  2018-05-14 13:59   ` Max Reitz
  2018-05-14 20:42   ` John Snow
@ 2018-05-16 18:03   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 18:03 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> This moves the job list from BlockJob to Job. Now we can check for
> duplicate IDs in job_create().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>   include/block/blockjob.h |  3 ---
>   include/qemu/job.h       | 19 +++++++++++++++++++
>   blockjob.c               | 47 +++++++++++++++++++++++++----------------------
>   job.c                    | 31 +++++++++++++++++++++++++++++++
>   4 files changed, 75 insertions(+), 25 deletions(-)

> @@ -71,4 +75,19 @@ JobType job_type(Job *job);
>   /** Returns the enum string for the JobType of a given Job. */
>   const char *job_type_str(Job *job);
>   
> +/**
> + * Get the next element from the list of block jobs after @job, or the
> + * first one if @job is %NULL.
> + *
> + * Returns the requested job, or %NULL if there are no more jobs left.
> + */
> +Job *job_next(Job *job);

The new code is supposed to allow you to prime the search by passing in 
NULL.  This looks similar to the semantics originally held by 
block_job_next(), where


> -BlockJob *block_job_next(BlockJob *job)
> +static bool is_block_job(Job *job)
>   {
> -    if (!job) {
> -        return QLIST_FIRST(&block_jobs);
> -    }

the old code did so by special-casing an incoming NULL.

> -    return QLIST_NEXT(job, job_list);
> +    return job_type(job) == JOB_TYPE_BACKUP ||
> +           job_type(job) == JOB_TYPE_COMMIT ||
> +           job_type(job) == JOB_TYPE_MIRROR ||
> +           job_type(job) == JOB_TYPE_STREAM;
> +}
> +
> +BlockJob *block_job_next(BlockJob *bjob)
> +{
> +    Job *job = &bjob->job;

But the new code blindly dereferences what may be a NULL bjob.  Taking 
the address of a member of the NULL pointer may happen to work if that 
member is at offset 0, but is also likely to trigger compiler or 
sanitizer warnings; and does not work if the member is not at offset 0.

I think all you have to do is:

Job *job = bjob ? &bjob->job : NULL;

> +
> +    do {
> +        job = job_next(job);
> +    } while (job && !is_block_job(job));
> +
> +
> +    return job ? container_of(job, BlockJob, job) : NULL;

Why two blank lines?

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job Kevin Wolf
  2018-05-14 14:20   ` Max Reitz
  2018-05-14 20:58   ` John Snow
@ 2018-05-16 18:11   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 18:11 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> This moves BlockJob.status and the closely related functions
> (block_)job_state_transition() and (block_)job_apply_verb to Job. The
> two QAPI enums are renamed to JobStatus and JobVerb.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>   qapi/block-core.json     |  14 +++----
>   include/block/blockjob.h |   3 --
>   include/qemu/job.h       |  13 ++++++
>   blockjob.c               | 102 +++++++++++------------------------------------
>   job.c                    |  56 ++++++++++++++++++++++++++
>   tests/test-blockjob.c    |  39 +++++++++---------
>   block/trace-events       |   2 -
>   trace-events             |   4 ++
>   8 files changed, 122 insertions(+), 111 deletions(-)
> 

> @@ -90,4 +93,14 @@ Job *job_next(Job *job);
>    */
>   Job *job_get(const char *id);
>   
> +/**
> + * Check whether the verb @bv can be applied to @job in its current state.
> + * Returns 0 if the verb can be applied; otherwise errp is set and -EPERM
> + * returned.
> + */
> +int job_apply_verb(Job *job, JobVerb bv, Error **errp);

Why 'JobVerb bv' rather than 'jv' or 'verb'?

> @@ -968,7 +913,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
>       job->refcnt        = 1;
>       job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
>       job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
> -    block_job_state_transition(job, BLOCK_JOB_STATUS_CREATED);

No replacement to the counterpart job_state_transition() here because 
that should be handled in the parent class now, right? [1]

...

> +
> +int job_apply_verb(Job *job, JobVerb bv, Error **errp)

Again, the name bv made sense for BlockJobVerb, but less so now.

> @@ -81,6 +135,8 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
>       job->driver        = driver;
>       job->id            = g_strdup(job_id);
>   
> +    job_state_transition(job, JOB_STATUS_CREATED);
> +

[1] Yes, just took me a while to get to it.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 14/42] job: Add reference counting Kevin Wolf
  2018-05-14 14:29   ` Max Reitz
  2018-05-14 21:34   ` John Snow
@ 2018-05-16 18:17   ` Eric Blake
  2018-05-16 20:56     ` Kevin Wolf
  2 siblings, 1 reply; 146+ messages in thread
From: Eric Blake @ 2018-05-16 18:17 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> This moves reference counting from BlockJob to Job.
> 
> In order to keep calling the BlockJob cleanup code when the job is
> deleted via job_unref(), introduce a new JobDriver.free callback. Every
> block job must use block_job_free() for this callback, this is asserted
> in block_job_create().
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---

> +++ b/job.c

> +
> +void job_unref(Job *job)
> +{
> +    if (--job->refcnt == 0) {

Should this be free()-like and allow an incoming job == NULL as a no-op?

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-16 10:51       ` Max Reitz
@ 2018-05-16 18:32         ` Eric Blake
  0 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 18:32 UTC (permalink / raw)
  To: Max Reitz, Kevin Wolf
  Cc: qemu-block, jsnow, armbru, jcody, qemu-devel, stefanha

On 05/16/2018 05:51 AM, Max Reitz wrote:

>>>> -static void commit_complete(BlockJob *job, void *opaque)
>>>> +static void commit_complete(Job *job, void *opaque)
>>>>   {
>>>> -    CommitBlockJob *s = container_of(job, CommitBlockJob, common);
>>>> +    CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
>>>
>>> Now this is just abuse.
>>>
>>> ...but it's not the first time someone packs two container_of() into
>>> one, it appears.  So, whatever, I guess.
>>
>> I don't think it's abuse. Why wouldn't I directly cast to the type that
>> I really want instead of casting to each of the uninteresting parent
>> classes, too?
> 
> Because the final parameter is called "member" and not "path". :-)

container_of() is using offsetof(); and in C99 7.17, the parameter is 
named "member-designator" which can indeed jump through multiple layers 
(any valid address constant, as defined in 6.6P9).  I don't see this as 
abuse of the interface.


>>> The best explanation I can come up with is that the original code
>>> acquired the AioContext both of the block device at the time of the BH
>>> (because that needs to be done), and at the time of
>>> block_job_defer_to_main_loop() -- because the latter is probably the
>>> context the block_job_defer_to_main_loop() call came from, so it should
>>> be (b)locked.
>>>
>>> But if that's the case, then the same should be done here.  The context
>>> of the job may change between scheduling the BH and the BH being
>>> executed, so we might lock a different context here than the one
>>> job_defer_to_main_loop() ran in (i.e., job->aio_context at the time of
>>> job_defer_to_main_loop() running).  And maybe we should lock that old
>>> context, too -- just like block_job_defer_to_main_loop_bh() did.
>>
>> Why should we lock the old context? We don't access anything protected
>> by it. Even the data->job->bs access has gone away because we now have
>> job->aio_context.
> 
> Because the old code did so and I don't know why. O:-)
> 
> Your explanation does make sense to me, though, so:

Then it's best to include that explanation in the commit message itself, 
to save the future reader the hassle of digging up this thread.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job Kevin Wolf
  2018-05-14 15:52   ` Max Reitz
  2018-05-14 22:33   ` John Snow
@ 2018-05-16 18:37   ` Eric Blake
  2 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 18:37 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---

Rather sparse on the commit message, given the other comments in this 
thread.

> +++ b/include/qemu/job.h
> @@ -58,6 +58,9 @@ typedef struct Job {
>        */
>       bool cancelled;
>   
> +    /** Set to true when the job has deferred work to the main loop. */
> +    bool deferred_to_main_loop;
> +
>       /** Element of the list of jobs */
>       QLIST_ENTRY(Job) job_list;
>   } Job;
> @@ -131,6 +134,23 @@ Job *job_get(const char *id);
>    */
>   int job_apply_verb(Job *job, JobVerb bv, Error **errp);
>   
> +typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
> +
> +/**
> + * @job: The job
> + * @fn: The function to run in the main loop
> + * @opaque: The opaque value that is passed to @fn
> + *
> + * This function must be called by the main job coroutine just before it
> + * returns.  @fn is executed in the main loop with the job AioContext acquired.
> + *
> + * Block jobs must call bdrv_unref(), bdrv_close(), and anything that uses
> + * bdrv_drain_all() in the main loop.

Do we still want this block-job-specific comment in the main job.h header?

> + *
> + * The @job AioContext is held while @fn executes.
> + */
> +void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);
> +


-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job Kevin Wolf
  2018-05-14 17:50   ` Max Reitz
@ 2018-05-16 19:13   ` Eric Blake
  2018-05-16 20:53     ` Kevin Wolf
  1 sibling, 1 reply; 146+ messages in thread
From: Eric Blake @ 2018-05-16 19:13 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> This renames the BlockJobCreateFlags constants, moves a few JOB_INTERNAL
> checks to job_create() and the auto_{finalize,dismiss} fields from
> BlockJob to Job.

Do we still want to allow auto-finalize on all jobs, or should we keep 
it just for legacy support on block jobs?  Even more so for auto-dismiss 
(if you use the legacy interface, that's what you expect to happen; but 
other than for legacy block jobs, any sane management app is going to 
request auto-dismiss be false, so why expose it to generic jobs?)

Of course, it may still be easiest to plumb up auto- actions in the 
internal code (where it has to work to keep legacy block jobs from 
breaking, but no new callers have to enable it), so my argument may not 
apply until you actually expose a QMP interface for generic jobs.

> +++ b/block/mirror.c
> @@ -1282,7 +1282,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
>       }
>       is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
>       base = mode == MIRROR_SYNC_MODE_TOP ? backing_bs(bs) : NULL;
> -    mirror_start_job(job_id, bs, BLOCK_JOB_DEFAULT, target, replaces,
> +    mirror_start_job(job_id, bs, JOB_DEFAULT, target, replaces,
>                        speed, granularity, buf_size, backing_mode,
>                        on_source_error, on_target_error, unmap, NULL, NULL,
>                        &mirror_job_driver, is_none_mode, base, false,

Just an observation that this is a lot of parameters; would using boxed 
QAPI types make these calls any more obvious?  But that's a separate 
cleanup.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending()
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending() Kevin Wolf
  2018-05-14 17:56   ` Max Reitz
@ 2018-05-16 19:15   ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 19:15 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> block_job_event_pending() doesn't only send a QMP event, but it also
> transitions to the PENDING state. Split the function so that we get one
> part only sending the event (like other block_job_event_* functions) and
> another part than does the state transition.

s/than/that/

> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>   blockjob.c | 27 ++++++++++++++++++---------
>   1 file changed, 18 insertions(+), 9 deletions(-)
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event Kevin Wolf
  2018-05-14 22:11   ` Max Reitz
@ 2018-05-16 19:26   ` Eric Blake
  2018-05-16 20:46     ` Kevin Wolf
  1 sibling, 1 reply; 146+ messages in thread
From: Eric Blake @ 2018-05-16 19:26 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> This adds a QMP event that is emitted whenever a job transitions from
> one status to another. For the event, a new qapi/job.json schema file is
> created which will contain all job-related definitions that aren't tied
> to the block layer.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---

> +++ b/qapi/job.json

> +##
> +# @JOB_STATUS_CHANGE:
> +#
> +# Emitted when a job transitions to a different status.
> +#
> +# @id: The job identifier
> +# @status: The new job status
> +#
> +# Since: 2.13
> +##
> +{ 'event': 'JOB_STATUS_CHANGE',
> +  'data': { 'id': 'str',
> +            'status': 'JobStatus' } }

Is it worth also trying to list the old state that the transition came 
from? But that's new compared to what block jobs are currently doing, so 
if we can't come up with a strong reason to add that, I'm okay.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event
  2018-05-16 19:26   ` Eric Blake
@ 2018-05-16 20:46     ` Kevin Wolf
  2018-05-16 20:53       ` Eric Blake
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-16 20:46 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-block, mreitz, jsnow, armbru, jcody, qemu-devel

Am 16.05.2018 um 21:26 hat Eric Blake geschrieben:
> On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> > This adds a QMP event that is emitted whenever a job transitions from
> > one status to another. For the event, a new qapi/job.json schema file is
> > created which will contain all job-related definitions that aren't tied
> > to the block layer.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> 
> > +++ b/qapi/job.json
> 
> > +##
> > +# @JOB_STATUS_CHANGE:
> > +#
> > +# Emitted when a job transitions to a different status.
> > +#
> > +# @id: The job identifier
> > +# @status: The new job status
> > +#
> > +# Since: 2.13
> > +##
> > +{ 'event': 'JOB_STATUS_CHANGE',
> > +  'data': { 'id': 'str',
> > +            'status': 'JobStatus' } }
> 
> Is it worth also trying to list the old state that the transition came from?
> But that's new compared to what block jobs are currently doing, so if we
> can't come up with a strong reason to add that, I'm okay.

If a management tool needs this information (I don't see why), it just
needs to keep track of the last state it had seen, no?

Kevin

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

* Re: [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event
  2018-05-16 20:46     ` Kevin Wolf
@ 2018-05-16 20:53       ` Eric Blake
  0 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 20:53 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, mreitz, jsnow, armbru, jcody, qemu-devel

On 05/16/2018 03:46 PM, Kevin Wolf wrote:

>>> +{ 'event': 'JOB_STATUS_CHANGE',
>>> +  'data': { 'id': 'str',
>>> +            'status': 'JobStatus' } }
>>
>> Is it worth also trying to list the old state that the transition came from?
>> But that's new compared to what block jobs are currently doing, so if we
>> can't come up with a strong reason to add that, I'm okay.
> 
> If a management tool needs this information (I don't see why), it just
> needs to keep track of the last state it had seen, no?

And if you miss an event, the previous state is not preserved for any 
later query-job command.  Exposing something in a (possibly-missed) 
event that is not available elsewhere doesn't buy us much.  So I agree - 
there is no strong reason to advertise the previous state.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job
  2018-05-16 19:13   ` Eric Blake
@ 2018-05-16 20:53     ` Kevin Wolf
  0 siblings, 0 replies; 146+ messages in thread
From: Kevin Wolf @ 2018-05-16 20:53 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-block, mreitz, jsnow, armbru, jcody, qemu-devel

Am 16.05.2018 um 21:13 hat Eric Blake geschrieben:
> On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> > This renames the BlockJobCreateFlags constants, moves a few JOB_INTERNAL
> > checks to job_create() and the auto_{finalize,dismiss} fields from
> > BlockJob to Job.
> 
> Do we still want to allow auto-finalize on all jobs, or should we keep it
> just for legacy support on block jobs?  Even more so for auto-dismiss (if
> you use the legacy interface, that's what you expect to happen; but other
> than for legacy block jobs, any sane management app is going to request
> auto-dismiss be false, so why expose it to generic jobs?)
> 
> Of course, it may still be easiest to plumb up auto- actions in the internal
> code (where it has to work to keep legacy block jobs from breaking, but no
> new callers have to enable it), so my argument may not apply until you
> actually expose a QMP interface for generic jobs.

This series doesn't expose it anyway. We can later decide whether we
want to add it or not. A sophisticated management tool like libvirt that
needs to manage individual nodes and cope with daemon restarts will
never make use of them, but they might still be useful in hand-crafted
scripts for defined special cases.

> > +++ b/block/mirror.c
> > @@ -1282,7 +1282,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
> >       }
> >       is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
> >       base = mode == MIRROR_SYNC_MODE_TOP ? backing_bs(bs) : NULL;
> > -    mirror_start_job(job_id, bs, BLOCK_JOB_DEFAULT, target, replaces,
> > +    mirror_start_job(job_id, bs, JOB_DEFAULT, target, replaces,
> >                        speed, granularity, buf_size, backing_mode,
> >                        on_source_error, on_target_error, unmap, NULL, NULL,
> >                        &mirror_job_driver, is_none_mode, base, false,
> 
> Just an observation that this is a lot of parameters; would using boxed QAPI
> types make these calls any more obvious?  But that's a separate cleanup.

I'm not sure if we have a QAPI type that matches this. But maybe it
could become a QAPI struct and very few extra parameters.

Kevin

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

* Re: [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-16 18:17   ` Eric Blake
@ 2018-05-16 20:56     ` Kevin Wolf
  2018-05-16 21:25       ` Eric Blake
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-16 20:56 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-block, mreitz, jsnow, armbru, jcody, qemu-devel

Am 16.05.2018 um 20:17 hat Eric Blake geschrieben:
> On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> > This moves reference counting from BlockJob to Job.
> > 
> > In order to keep calling the BlockJob cleanup code when the job is
> > deleted via job_unref(), introduce a new JobDriver.free callback. Every
> > block job must use block_job_free() for this callback, this is asserted
> > in block_job_create().
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> 
> > +++ b/job.c
> 
> > +
> > +void job_unref(Job *job)
> > +{
> > +    if (--job->refcnt == 0) {
> 
> Should this be free()-like and allow an incoming job == NULL as a no-op?

This behaves like block_job_unref() always behavec, and I don't see a
single caller having a NULL check before calling job_unref(), so is it
worth it?

Kevin

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

* Re: [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job
  2018-05-09 16:26 ` [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job Kevin Wolf
  2018-05-14 21:33   ` Max Reitz
@ 2018-05-16 21:23   ` Eric Blake
  1 sibling, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 21:23 UTC (permalink / raw)
  To: Kevin Wolf, qemu-block; +Cc: mreitz, jsnow, armbru, jcody, qemu-devel

On 05/09/2018 11:26 AM, Kevin Wolf wrote:
> BlockJob has fields .offset and .len, which are actually misnomers today
> because they are no longer tied to block device sizes, but just progress
> counters. As such they make a lot of sense in generic Jobs.
> 
> This patch moves the fields to Job and renames them to .progress_current
> and .progress_total to describe their function better.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---

> +++ b/include/qemu/job.h
> @@ -114,6 +114,16 @@ typedef struct Job {
>       /** True if this job should automatically dismiss itself */
>       bool auto_dismiss;
>   
> +    /**
> +     * Current progress. The unit is arbitrary as long as the ratio between
> +     * progress_current and progress_total represents the estimated percentage
> +     * of work already done.
> +     */
> +    int64_t progress_current;
> +
> +    /** Estimated progress_current value at the completion of the job */
> +    int64_t progress_total;
> +
>       /** ret code passed to block_job_completed. */
>       int ret;
>   
> @@ -304,6 +314,23 @@ void job_ref(Job *job);
>    */
>   void job_unref(Job *job);
>   
> +/**
> + * @job: The job that has made progress
> + * @done: How much progress the job made
> + *
> + * Updates the progress counter of the job.

Which progress counter? It might be worth calling out that this 
specifically updates the progress_current counter.  Also, it might be 
worth mentioning that the value is added to the current value of 
progress_counter, rather than directly assigned.

> + */
> +void job_progress_update(Job *job, uint64_t done);
> +
> +/**
> + * @job: The job whose expected progress end value is set
> + * @remaining: Expected end value of the progress counter of the job
> + *
> + * Sets the expected end value of the progress counter of a job so that a
> + * completion percentage can be calculated when the progress is updated.
> + */
> +void job_progress_set_remaining(Job *job, uint64_t remaining);

The name 'remaining' sounds like this is a delta (that progress_total is 
computed by summing the current progress_current + remaining) rather 
than directly the new value of progress_total.  Is that intended?

> +++ b/job.c
> @@ -364,6 +364,16 @@ void job_unref(Job *job)
>       }
>   }
>   
> +void job_progress_update(Job *job, uint64_t done)
> +{
> +    job->progress_current += done;
> +}
> +
> +void job_progress_set_remaining(Job *job, uint64_t remaining)
> +{
> +    job->progress_total = job->progress_current + remaining;

Okay, so implementation-wise, both functions ARE taking a delta, rather 
than directly assigning the internal field.  The delta can only be 
positive, but job->progress_total can still shrink over time as needed.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 14/42] job: Add reference counting
  2018-05-16 20:56     ` Kevin Wolf
@ 2018-05-16 21:25       ` Eric Blake
  0 siblings, 0 replies; 146+ messages in thread
From: Eric Blake @ 2018-05-16 21:25 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, mreitz, jsnow, armbru, jcody, qemu-devel

On 05/16/2018 03:56 PM, Kevin Wolf wrote:

>>> +
>>> +void job_unref(Job *job)
>>> +{
>>> +    if (--job->refcnt == 0) {
>>
>> Should this be free()-like and allow an incoming job == NULL as a no-op?
> 
> This behaves like block_job_unref() always behavec, and I don't see a
> single caller having a NULL check before calling job_unref(), so is it
> worth it?

Only if it makes it easier to clean up a partially-constructed object 
(which is the most likely case of wanting to pass in NULL)

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

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

* Re: [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs
  2018-05-14 23:44   ` Max Reitz
@ 2018-05-17 13:50     ` Kevin Wolf
  2018-05-28 13:07       ` Max Reitz
  0 siblings, 1 reply; 146+ messages in thread
From: Kevin Wolf @ 2018-05-17 13:50 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

Am 15.05.2018 um 01:44 hat Max Reitz geschrieben:
> On 2018-05-09 18:26, Kevin Wolf wrote:
> > This adds a test case that tests the new job-* QMP commands with
> > mirror and backup block jobs.
> > 
> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> > ---
> >  tests/qemu-iotests/219     | 201 ++++++++++++++++++++++++++++
> >  tests/qemu-iotests/219.out | 327 +++++++++++++++++++++++++++++++++++++++++++++
> >  tests/qemu-iotests/group   |   1 +
> >  3 files changed, 529 insertions(+)
> >  create mode 100755 tests/qemu-iotests/219
> >  create mode 100644 tests/qemu-iotests/219.out

> > +Pause/resume in READY
> > +=== Testing block-job-pause/block-job-resume ===
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +=== Testing block-job-pause/job-resume ===
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +=== Testing job-pause/block-job-resume ===
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +=== Testing job-pause/job-resume ===
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> > +{u'return': {}}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
> 
> This is really, really mean.  Don't you have any compassion with the
> poor little job that just wants to have Feierabend?
> 
> It worked so hard and it's always on standby and instantly ready when
> you need it.  Yet, you keep it hanging.  That's not nice.

If you just mean that I do some testing with the poor job before I
complete it, then I'm afraid the job will have to suffer this.

But if you have a more serious concern, I don't see it. Isn't the job
properly completed in the end, as the following lines show?

> > +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
> > +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
> > +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
> > +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
> > +{u'return': {}}
> > +
> > +Waiting for PENDING state...
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
> > +{u'return': []}

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs
  2018-05-17 13:50     ` Kevin Wolf
@ 2018-05-28 13:07       ` Max Reitz
  0 siblings, 0 replies; 146+ messages in thread
From: Max Reitz @ 2018-05-28 13:07 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-block, eblake, jsnow, armbru, jcody, qemu-devel

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

On 2018-05-17 15:50, Kevin Wolf wrote:
> Am 15.05.2018 um 01:44 hat Max Reitz geschrieben:
>> On 2018-05-09 18:26, Kevin Wolf wrote:
>>> This adds a test case that tests the new job-* QMP commands with
>>> mirror and backup block jobs.
>>>
>>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>>> ---
>>>  tests/qemu-iotests/219     | 201 ++++++++++++++++++++++++++++
>>>  tests/qemu-iotests/219.out | 327 +++++++++++++++++++++++++++++++++++++++++++++
>>>  tests/qemu-iotests/group   |   1 +
>>>  3 files changed, 529 insertions(+)
>>>  create mode 100755 tests/qemu-iotests/219
>>>  create mode 100644 tests/qemu-iotests/219.out
> 
>>> +Pause/resume in READY
>>> +=== Testing block-job-pause/block-job-resume ===
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +=== Testing block-job-pause/job-resume ===
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +=== Testing job-pause/block-job-resume ===
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +=== Testing job-pause/job-resume ===
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'standby', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'standby', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>> +{u'return': {}}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'ready', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': [{u'status': u'ready', u'current-progress': 4194304, u'total-progress': 4194304, u'id': u'job0', u'type': u'mirror'}]}
>>
>> This is really, really mean.  Don't you have any compassion with the
>> poor little job that just wants to have Feierabend?
>>
>> It worked so hard and it's always on standby and instantly ready when
>> you need it.  Yet, you keep it hanging.  That's not nice.
> 
> If you just mean that I do some testing with the poor job before I
> complete it, then I'm afraid the job will have to suffer this.
> 
> But if you have a more serious concern, I don't see it. Isn't the job
> properly completed in the end, as the following lines show?

No, I was just making fun of the standby <-> ready transition. :-)

Max

>>> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
>>> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
>>> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'finalize'"}}
>>> +{u'error': {u'class': u'GenericError', u'desc': u"Job 'job0' in state 'ready' cannot accept command verb 'dismiss'"}}
>>> +{u'return': {}}
>>> +
>>> +Waiting for PENDING state...
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'waiting', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'pending', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'concluded', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'null', u'id': u'job0'}, u'event': u'JOB_STATUS_CHANGE'}
>>> +{u'return': []}
> 
> Kevin
> 



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

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

end of thread, other threads:[~2018-05-28 13:07 UTC | newest]

Thread overview: 146+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-09 16:25 [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
2018-05-09 16:25 ` [Qemu-devel] [PATCH 01/42] blockjob: Fix assertion in block_job_finalize() Kevin Wolf
2018-05-09 17:28   ` Eric Blake
2018-05-11 21:55   ` Max Reitz
2018-05-11 22:29   ` John Snow
2018-05-09 16:25 ` [Qemu-devel] [PATCH 02/42] blockjob: Wrappers for progress counter access Kevin Wolf
2018-05-11 22:12   ` Max Reitz
2018-05-14 10:16     ` Kevin Wolf
2018-05-14 19:14   ` John Snow
2018-05-09 16:25 ` [Qemu-devel] [PATCH 03/42] blockjob: Move RateLimit to BlockJob Kevin Wolf
2018-05-11 22:14   ` Max Reitz
2018-05-14 19:19   ` John Snow
2018-05-09 16:25 ` [Qemu-devel] [PATCH 04/42] blockjob: Implement block_job_set_speed() centrally Kevin Wolf
2018-05-11 22:19   ` Max Reitz
2018-05-14 19:27   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 05/42] blockjob: Introduce block_job_ratelimit_get_delay() Kevin Wolf
2018-05-11 22:25   ` Max Reitz
2018-05-14 19:36   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 06/42] blockjob: Add block_job_driver() Kevin Wolf
2018-05-11 22:28   ` Max Reitz
2018-05-14 19:43   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 07/42] blockjob: Remove block_job_pause/resume_all() Kevin Wolf
2018-05-09 17:30   ` Eric Blake
2018-05-11 22:30   ` Max Reitz
2018-05-14 19:44     ` John Snow
2018-05-14 19:45     ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 08/42] job: Create Job, JobDriver and job_create() Kevin Wolf
2018-05-11 22:46   ` Max Reitz
2018-05-14 12:58     ` Kevin Wolf
2018-05-14 19:57   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 09/42] job: Rename BlockJobType into JobType Kevin Wolf
2018-05-11 22:48   ` Max Reitz
2018-05-14 20:05   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 10/42] job: Add JobDriver.job_type Kevin Wolf
2018-05-11 22:53   ` Max Reitz
2018-05-14 11:31     ` Kevin Wolf
2018-05-14 20:12     ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 11/42] job: Add job_delete() Kevin Wolf
2018-05-11 22:56   ` Max Reitz
2018-05-14 20:15   ` John Snow
2018-05-16 17:54   ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 12/42] job: Maintain a list of all jobs Kevin Wolf
2018-05-14 13:59   ` Max Reitz
2018-05-14 20:42   ` John Snow
2018-05-16 18:03   ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 13/42] job: Move state transitions to Job Kevin Wolf
2018-05-14 14:20   ` Max Reitz
2018-05-14 20:58   ` John Snow
2018-05-16 18:11   ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 14/42] job: Add reference counting Kevin Wolf
2018-05-14 14:29   ` Max Reitz
2018-05-14 21:34   ` John Snow
2018-05-15  9:06     ` Kevin Wolf
2018-05-16 18:17   ` Eric Blake
2018-05-16 20:56     ` Kevin Wolf
2018-05-16 21:25       ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 15/42] job: Move cancelled to Job Kevin Wolf
2018-05-14 14:39   ` Max Reitz
2018-05-14 21:53   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 16/42] job: Add Job.aio_context Kevin Wolf
2018-05-14 15:20   ` Max Reitz
2018-05-14 22:02   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 17/42] job: Move defer_to_main_loop to Job Kevin Wolf
2018-05-14 15:52   ` Max Reitz
2018-05-15 12:17     ` Kevin Wolf
2018-05-16 10:51       ` Max Reitz
2018-05-16 18:32         ` Eric Blake
2018-05-14 22:33   ` John Snow
2018-05-15 12:22     ` Kevin Wolf
2018-05-16 18:37   ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 18/42] job: Move coroutine and related code " Kevin Wolf
2018-05-14 16:47   ` Max Reitz
2018-05-14 23:02   ` John Snow
2018-05-16 16:50     ` Kevin Wolf
2018-05-16 17:38       ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 19/42] job: Add job_sleep_ns() Kevin Wolf
2018-05-14 16:57   ` Max Reitz
2018-05-14 23:10   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 20/42] job: Move pause/resume functions to Job Kevin Wolf
2018-05-14 17:13   ` Max Reitz
2018-05-14 23:23   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 21/42] job: Replace BlockJob.completed with job_is_completed() Kevin Wolf
2018-05-14 17:33   ` Max Reitz
2018-05-14 23:43   ` John Snow
2018-05-09 16:26 ` [Qemu-devel] [PATCH 22/42] job: Move BlockJobCreateFlags to Job Kevin Wolf
2018-05-14 17:50   ` Max Reitz
2018-05-16 19:13   ` Eric Blake
2018-05-16 20:53     ` Kevin Wolf
2018-05-09 16:26 ` [Qemu-devel] [PATCH 23/42] blockjob: Split block_job_event_pending() Kevin Wolf
2018-05-14 17:56   ` Max Reitz
2018-05-16 19:15   ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 24/42] job: Add job_event_*() Kevin Wolf
2018-05-14 18:09   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 25/42] job: Move single job finalisation to Job Kevin Wolf
2018-05-14 18:46   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 26/42] job: Convert block_job_cancel_async() " Kevin Wolf
2018-05-14 19:00   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 27/42] job: Add job_drain() Kevin Wolf
2018-05-14 19:26   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 28/42] job: Move .complete callback to Job Kevin Wolf
2018-05-14 19:37   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 29/42] job: Move job_finish_sync() " Kevin Wolf
2018-05-14 19:49   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 30/42] job: Switch transactions to JobTxn Kevin Wolf
2018-05-14 20:00   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 31/42] job: Move transactions to Job Kevin Wolf
2018-05-14 20:28   ` Max Reitz
2018-05-14 21:17   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 32/42] job: Move completion and cancellation " Kevin Wolf
2018-05-14 20:53   ` Max Reitz
2018-05-15 12:59     ` Kevin Wolf
2018-05-16 10:52       ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 33/42] job: Add job_yield() Kevin Wolf
2018-05-14 20:59   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 34/42] job: Add job_dismiss() Kevin Wolf
2018-05-14 21:06   ` Max Reitz
2018-05-14 22:26   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 35/42] job: Add job_is_ready() Kevin Wolf
2018-05-14 21:11   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 36/42] job: Add job_transition_to_ready() Kevin Wolf
2018-05-14 21:22   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 37/42] job: Move progress fields to Job Kevin Wolf
2018-05-14 21:33   ` Max Reitz
2018-05-16 21:23   ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 38/42] job: Add JOB_STATUS_CHANGE QMP event Kevin Wolf
2018-05-14 22:11   ` Max Reitz
2018-05-16 16:15     ` Kevin Wolf
2018-05-16 19:26   ` Eric Blake
2018-05-16 20:46     ` Kevin Wolf
2018-05-16 20:53       ` Eric Blake
2018-05-09 16:26 ` [Qemu-devel] [PATCH 39/42] job: Add lifecycle QMP commands Kevin Wolf
2018-05-14 22:31   ` Max Reitz
2018-05-15 14:08     ` Kevin Wolf
2018-05-16 10:54       ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 40/42] job: Add query-jobs QMP command Kevin Wolf
2018-05-14 23:09   ` Max Reitz
2018-05-16 11:21     ` Kevin Wolf
2018-05-16 11:27       ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 41/42] iotests: Move qmp_to_opts() to VM Kevin Wolf
2018-05-14 23:11   ` Max Reitz
2018-05-09 16:26 ` [Qemu-devel] [PATCH 42/42] qemu-iotests: Test job-* with block jobs Kevin Wolf
2018-05-14 23:44   ` Max Reitz
2018-05-17 13:50     ` Kevin Wolf
2018-05-28 13:07       ` Max Reitz
2018-05-15 14:15 ` [Qemu-devel] [PATCH 00/42] Generic background jobs Kevin Wolf
2018-05-15 14:43   ` Eric Blake

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.