Blame SOURCES/kvm-job-Move-single-job-finalisation-to-Job.patch

1bdc94
From adcfa2a0ddbf68a34b24afcbfaaf03a19d1406ca Mon Sep 17 00:00:00 2001
1bdc94
From: Kevin Wolf <kwolf@redhat.com>
1bdc94
Date: Tue, 26 Jun 2018 09:48:16 +0200
1bdc94
Subject: [PATCH 47/89] job: Move single job finalisation to Job
1bdc94
1bdc94
RH-Author: Kevin Wolf <kwolf@redhat.com>
1bdc94
Message-id: <20180626094856.6924-34-kwolf@redhat.com>
1bdc94
Patchwork-id: 81070
1bdc94
O-Subject: [RHV-7.6 qemu-kvm-rhev PATCH v2 33/73] job: Move single job finalisation to Job
1bdc94
Bugzilla: 1513543
1bdc94
RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
1bdc94
RH-Acked-by: Max Reitz <mreitz@redhat.com>
1bdc94
RH-Acked-by: Fam Zheng <famz@redhat.com>
1bdc94
1bdc94
This moves the finalisation of a single job from BlockJob to Job.
1bdc94
1bdc94
Some part of this code depends on job transactions, and job transactions
1bdc94
call this code, we introduce some temporary calls from Job functions to
1bdc94
BlockJob ones. This will be fixed once transactions move to Job, too.
1bdc94
1bdc94
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
1bdc94
Reviewed-by: Max Reitz <mreitz@redhat.com>
1bdc94
(cherry picked from commit 4ad351819b974d724e926fd23cdd66bec3c9768e)
1bdc94
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
1bdc94
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
1bdc94
---
1bdc94
 block/backup.c               |  22 +++----
1bdc94
 block/commit.c               |   2 +-
1bdc94
 block/mirror.c               |   2 +-
1bdc94
 blockjob.c                   | 142 ++++++++-----------------------------------
1bdc94
 include/block/blockjob.h     |   9 ---
1bdc94
 include/block/blockjob_int.h |  36 -----------
1bdc94
 include/qemu/job.h           |  53 +++++++++++++++-
1bdc94
 job.c                        | 100 +++++++++++++++++++++++++++++-
1bdc94
 qemu-img.c                   |   2 +-
1bdc94
 tests/test-blockjob.c        |  10 +--
1bdc94
 10 files changed, 194 insertions(+), 184 deletions(-)
1bdc94
1bdc94
diff --git a/block/backup.c b/block/backup.c
1bdc94
index 4d011d5..bd31282 100644
1bdc94
--- a/block/backup.c
1bdc94
+++ b/block/backup.c
1bdc94
@@ -207,25 +207,25 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
1bdc94
     }
1bdc94
 }
1bdc94
 
1bdc94
-static void backup_commit(BlockJob *job)
1bdc94
+static void backup_commit(Job *job)
1bdc94
 {
1bdc94
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
1bdc94
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
1bdc94
     if (s->sync_bitmap) {
1bdc94
         backup_cleanup_sync_bitmap(s, 0);
1bdc94
     }
1bdc94
 }
1bdc94
 
1bdc94
-static void backup_abort(BlockJob *job)
1bdc94
+static void backup_abort(Job *job)
1bdc94
 {
1bdc94
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
1bdc94
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
1bdc94
     if (s->sync_bitmap) {
1bdc94
         backup_cleanup_sync_bitmap(s, -1);
1bdc94
     }
1bdc94
 }
1bdc94
 
1bdc94
-static void backup_clean(BlockJob *job)
1bdc94
+static void backup_clean(Job *job)
1bdc94
 {
1bdc94
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
1bdc94
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
1bdc94
     assert(s->target);
1bdc94
     blk_unref(s->target);
1bdc94
     s->target = NULL;
1bdc94
@@ -530,10 +530,10 @@ static const BlockJobDriver backup_job_driver = {
1bdc94
         .free                   = block_job_free,
1bdc94
         .user_resume            = block_job_user_resume,
1bdc94
         .start                  = backup_run,
1bdc94
+        .commit                 = backup_commit,
1bdc94
+        .abort                  = backup_abort,
1bdc94
+        .clean                  = backup_clean,
1bdc94
     },
1bdc94
-    .commit                 = backup_commit,
1bdc94
-    .abort                  = backup_abort,
1bdc94
-    .clean                  = backup_clean,
1bdc94
     .attached_aio_context   = backup_attached_aio_context,
1bdc94
     .drain                  = backup_drain,
1bdc94
 };
1bdc94
@@ -678,8 +678,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
1bdc94
         bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
1bdc94
     }
1bdc94
     if (job) {
1bdc94
-        backup_clean(&job->common);
1bdc94
-        block_job_early_fail(&job->common);
1bdc94
+        backup_clean(&job->common.job);
1bdc94
+        job_early_fail(&job->common.job);
1bdc94
     }
1bdc94
 
1bdc94
     return NULL;
1bdc94
diff --git a/block/commit.c b/block/commit.c
1bdc94
index 7a6ae59..e53b2d7 100644
1bdc94
--- a/block/commit.c
1bdc94
+++ b/block/commit.c
1bdc94
@@ -385,7 +385,7 @@ fail:
1bdc94
     if (commit_top_bs) {
1bdc94
         bdrv_replace_node(commit_top_bs, top, &error_abort);
1bdc94
     }
1bdc94
-    block_job_early_fail(&s->common);
1bdc94
+    job_early_fail(&s->common.job);
1bdc94
 }
1bdc94
 
1bdc94
 
1bdc94
diff --git a/block/mirror.c b/block/mirror.c
1bdc94
index 5091e72..e9a90ea 100644
1bdc94
--- a/block/mirror.c
1bdc94
+++ b/block/mirror.c
1bdc94
@@ -1257,7 +1257,7 @@ fail:
1bdc94
 
1bdc94
         g_free(s->replaces);
1bdc94
         blk_unref(s->target);
1bdc94
-        block_job_early_fail(&s->common);
1bdc94
+        job_early_fail(&s->common.job);
1bdc94
     }
1bdc94
 
1bdc94
     bdrv_child_try_set_perm(mirror_top_bs->backing, 0, BLK_PERM_ALL,
1bdc94
diff --git a/blockjob.c b/blockjob.c
1bdc94
index 05d7921..34c57da 100644
1bdc94
--- a/blockjob.c
1bdc94
+++ b/blockjob.c
1bdc94
@@ -127,7 +127,7 @@ void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job)
1bdc94
     block_job_txn_ref(txn);
1bdc94
 }
1bdc94
 
1bdc94
-static void block_job_txn_del_job(BlockJob *job)
1bdc94
+void block_job_txn_del_job(BlockJob *job)
1bdc94
 {
1bdc94
     if (job->txn) {
1bdc94
         QLIST_REMOVE(job, txn_list);
1bdc94
@@ -262,101 +262,12 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
1bdc94
     return job->driver;
1bdc94
 }
1bdc94
 
1bdc94
-static void block_job_decommission(BlockJob *job)
1bdc94
-{
1bdc94
-    assert(job);
1bdc94
-    job->job.busy = false;
1bdc94
-    job->job.paused = false;
1bdc94
-    job->job.deferred_to_main_loop = true;
1bdc94
-    block_job_txn_del_job(job);
1bdc94
-    job_state_transition(&job->job, JOB_STATUS_NULL);
1bdc94
-    job_unref(&job->job);
1bdc94
-}
1bdc94
-
1bdc94
-static void block_job_do_dismiss(BlockJob *job)
1bdc94
-{
1bdc94
-    block_job_decommission(job);
1bdc94
-}
1bdc94
-
1bdc94
-static void block_job_conclude(BlockJob *job)
1bdc94
-{
1bdc94
-    job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
1bdc94
-    if (job->job.auto_dismiss || !job_started(&job->job)) {
1bdc94
-        block_job_do_dismiss(job);
1bdc94
-    }
1bdc94
-}
1bdc94
-
1bdc94
-static void block_job_update_rc(BlockJob *job)
1bdc94
-{
1bdc94
-    if (!job->ret && job_is_cancelled(&job->job)) {
1bdc94
-        job->ret = -ECANCELED;
1bdc94
-    }
1bdc94
-    if (job->ret) {
1bdc94
-        job_state_transition(&job->job, JOB_STATUS_ABORTING);
1bdc94
-    }
1bdc94
-}
1bdc94
-
1bdc94
 static int block_job_prepare(BlockJob *job)
1bdc94
 {
1bdc94
-    if (job->ret == 0 && job->driver->prepare) {
1bdc94
-        job->ret = job->driver->prepare(job);
1bdc94
-    }
1bdc94
-    return job->ret;
1bdc94
-}
1bdc94
-
1bdc94
-static void block_job_commit(BlockJob *job)
1bdc94
-{
1bdc94
-    assert(!job->ret);
1bdc94
-    if (job->driver->commit) {
1bdc94
-        job->driver->commit(job);
1bdc94
-    }
1bdc94
-}
1bdc94
-
1bdc94
-static void block_job_abort(BlockJob *job)
1bdc94
-{
1bdc94
-    assert(job->ret);
1bdc94
-    if (job->driver->abort) {
1bdc94
-        job->driver->abort(job);
1bdc94
-    }
1bdc94
-}
1bdc94
-
1bdc94
-static void block_job_clean(BlockJob *job)
1bdc94
-{
1bdc94
-    if (job->driver->clean) {
1bdc94
-        job->driver->clean(job);
1bdc94
+    if (job->job.ret == 0 && job->driver->prepare) {
1bdc94
+        job->job.ret = job->driver->prepare(job);
1bdc94
     }
1bdc94
-}
1bdc94
-
1bdc94
-static int block_job_finalize_single(BlockJob *job)
1bdc94
-{
1bdc94
-    assert(job_is_completed(&job->job));
1bdc94
-
1bdc94
-    /* Ensure abort is called for late-transactional failures */
1bdc94
-    block_job_update_rc(job);
1bdc94
-
1bdc94
-    if (!job->ret) {
1bdc94
-        block_job_commit(job);
1bdc94
-    } else {
1bdc94
-        block_job_abort(job);
1bdc94
-    }
1bdc94
-    block_job_clean(job);
1bdc94
-
1bdc94
-    if (job->cb) {
1bdc94
-        job->cb(job->opaque, job->ret);
1bdc94
-    }
1bdc94
-
1bdc94
-    /* Emit events only if we actually started */
1bdc94
-    if (job_started(&job->job)) {
1bdc94
-        if (job_is_cancelled(&job->job)) {
1bdc94
-            job_event_cancelled(&job->job);
1bdc94
-        } else {
1bdc94
-            job_event_completed(&job->job);
1bdc94
-        }
1bdc94
-    }
1bdc94
-
1bdc94
-    block_job_txn_del_job(job);
1bdc94
-    block_job_conclude(job);
1bdc94
-    return 0;
1bdc94
+    return job->job.ret;
1bdc94
 }
1bdc94
 
1bdc94
 static void block_job_cancel_async(BlockJob *job, bool force)
1bdc94
@@ -424,8 +335,8 @@ static int block_job_finish_sync(BlockJob *job,
1bdc94
     while (!job_is_completed(&job->job)) {
1bdc94
         aio_poll(qemu_get_aio_context(), true);
1bdc94
     }
1bdc94
-    ret = (job_is_cancelled(&job->job) && job->ret == 0)
1bdc94
-          ? -ECANCELED : job->ret;
1bdc94
+    ret = (job_is_cancelled(&job->job) && job->job.ret == 0)
1bdc94
+          ? -ECANCELED : job->job.ret;
1bdc94
     job_unref(&job->job);
1bdc94
     return ret;
1bdc94
 }
1bdc94
@@ -466,7 +377,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
1bdc94
             assert(job_is_cancelled(&other_job->job));
1bdc94
             block_job_finish_sync(other_job, NULL, NULL);
1bdc94
         }
1bdc94
-        block_job_finalize_single(other_job);
1bdc94
+        job_finalize_single(&other_job->job);
1bdc94
         aio_context_release(ctx);
1bdc94
     }
1bdc94
 
1bdc94
@@ -478,6 +389,11 @@ static int block_job_needs_finalize(BlockJob *job)
1bdc94
     return !job->job.auto_finalize;
1bdc94
 }
1bdc94
 
1bdc94
+static int block_job_finalize_single(BlockJob *job)
1bdc94
+{
1bdc94
+    return job_finalize_single(&job->job);
1bdc94
+}
1bdc94
+
1bdc94
 static void block_job_do_finalize(BlockJob *job)
1bdc94
 {
1bdc94
     int rc;
1bdc94
@@ -516,7 +432,7 @@ static void block_job_completed_txn_success(BlockJob *job)
1bdc94
         if (!job_is_completed(&other_job->job)) {
1bdc94
             return;
1bdc94
         }
1bdc94
-        assert(other_job->ret == 0);
1bdc94
+        assert(other_job->job.ret == 0);
1bdc94
     }
1bdc94
 
1bdc94
     block_job_txn_apply(txn, block_job_transition_to_pending, false);
1bdc94
@@ -601,14 +517,14 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
1bdc94
         return;
1bdc94
     }
1bdc94
 
1bdc94
-    block_job_do_dismiss(job);
1bdc94
+    job_do_dismiss(&job->job);
1bdc94
     *jobptr = NULL;
1bdc94
 }
1bdc94
 
1bdc94
 void block_job_cancel(BlockJob *job, bool force)
1bdc94
 {
1bdc94
     if (job->job.status == JOB_STATUS_CONCLUDED) {
1bdc94
-        block_job_do_dismiss(job);
1bdc94
+        job_do_dismiss(&job->job);
1bdc94
         return;
1bdc94
     }
1bdc94
     block_job_cancel_async(job, force);
1bdc94
@@ -691,8 +607,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
1bdc94
     info->status    = job->job.status;
1bdc94
     info->auto_finalize = job->job.auto_finalize;
1bdc94
     info->auto_dismiss  = job->job.auto_dismiss;
1bdc94
-    info->has_error = job->ret != 0;
1bdc94
-    info->error     = job->ret ? g_strdup(strerror(-job->ret)) : NULL;
1bdc94
+    info->has_error = job->job.ret != 0;
1bdc94
+    info->error     = job->job.ret ? g_strdup(strerror(-job->job.ret)) : NULL;
1bdc94
     return info;
1bdc94
 }
1bdc94
 
1bdc94
@@ -729,8 +645,8 @@ static void block_job_event_completed(Notifier *n, void *opaque)
1bdc94
         return;
1bdc94
     }
1bdc94
 
1bdc94
-    if (job->ret < 0) {
1bdc94
-        msg = strerror(-job->ret);
1bdc94
+    if (job->job.ret < 0) {
1bdc94
+        msg = strerror(-job->job.ret);
1bdc94
     }
1bdc94
 
1bdc94
     qapi_event_send_block_job_completed(job_type(&job->job),
1bdc94
@@ -787,7 +703,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
1bdc94
     }
1bdc94
 
1bdc94
     job = job_create(job_id, &driver->job_driver, blk_get_aio_context(blk),
1bdc94
-                     flags, errp);
1bdc94
+                     flags, cb, opaque, errp);
1bdc94
     if (job == NULL) {
1bdc94
         blk_unref(blk);
1bdc94
         return NULL;
1bdc94
@@ -799,8 +715,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
1bdc94
 
1bdc94
     job->driver        = driver;
1bdc94
     job->blk           = blk;
1bdc94
-    job->cb            = cb;
1bdc94
-    job->opaque        = opaque;
1bdc94
 
1bdc94
     job->finalize_cancelled_notifier.notify = block_job_event_cancelled;
1bdc94
     job->finalize_completed_notifier.notify = block_job_event_completed;
1bdc94
@@ -828,7 +742,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
1bdc94
 
1bdc94
         block_job_set_speed(job, speed, &local_err);
1bdc94
         if (local_err) {
1bdc94
-            block_job_early_fail(job);
1bdc94
+            job_early_fail(&job->job);
1bdc94
             error_propagate(errp, local_err);
1bdc94
             return NULL;
1bdc94
         }
1bdc94
@@ -847,20 +761,14 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
1bdc94
     return job;
1bdc94
 }
1bdc94
 
1bdc94
-void block_job_early_fail(BlockJob *job)
1bdc94
-{
1bdc94
-    assert(job->job.status == JOB_STATUS_CREATED);
1bdc94
-    block_job_decommission(job);
1bdc94
-}
1bdc94
-
1bdc94
 void block_job_completed(BlockJob *job, int ret)
1bdc94
 {
1bdc94
     assert(job && job->txn && !job_is_completed(&job->job));
1bdc94
     assert(blk_bs(job->blk)->job == job);
1bdc94
-    job->ret = ret;
1bdc94
-    block_job_update_rc(job);
1bdc94
-    trace_block_job_completed(job, ret, job->ret);
1bdc94
-    if (job->ret) {
1bdc94
+    job->job.ret = ret;
1bdc94
+    job_update_rc(&job->job);
1bdc94
+    trace_block_job_completed(job, ret, job->job.ret);
1bdc94
+    if (job->job.ret) {
1bdc94
         block_job_completed_txn_abort(job);
1bdc94
     } else {
1bdc94
         block_job_completed_txn_success(job);
1bdc94
diff --git a/include/block/blockjob.h b/include/block/blockjob.h
1bdc94
index aef0629..3f405d1 100644
1bdc94
--- a/include/block/blockjob.h
1bdc94
+++ b/include/block/blockjob.h
1bdc94
@@ -76,9 +76,6 @@ typedef struct BlockJob {
1bdc94
     /** Rate limiting data structure for implementing @speed. */
1bdc94
     RateLimit limit;
1bdc94
 
1bdc94
-    /** The completion function that will be called when the job completes.  */
1bdc94
-    BlockCompletionFunc *cb;
1bdc94
-
1bdc94
     /** Block other operations when block job is running */
1bdc94
     Error *blocker;
1bdc94
 
1bdc94
@@ -94,12 +91,6 @@ typedef struct BlockJob {
1bdc94
     /** BlockDriverStates that are involved in this block job */
1bdc94
     GSList *nodes;
1bdc94
 
1bdc94
-    /** The opaque value that is passed to the completion function.  */
1bdc94
-    void *opaque;
1bdc94
-
1bdc94
-    /** ret code passed to block_job_completed. */
1bdc94
-    int ret;
1bdc94
-
1bdc94
     BlockJobTxn *txn;
1bdc94
     QLIST_ENTRY(BlockJob) txn_list;
1bdc94
 } BlockJob;
1bdc94
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
1bdc94
index 88639f7..bf2b762 100644
1bdc94
--- a/include/block/blockjob_int.h
1bdc94
+++ b/include/block/blockjob_int.h
1bdc94
@@ -54,34 +54,6 @@ struct BlockJobDriver {
1bdc94
      */
1bdc94
     int (*prepare)(BlockJob *job);
1bdc94
 
1bdc94
-    /**
1bdc94
-     * If the callback is not NULL, it will be invoked when all the jobs
1bdc94
-     * belonging to the same transaction complete; or upon this job's
1bdc94
-     * completion if it is not in a transaction. Skipped if NULL.
1bdc94
-     *
1bdc94
-     * All jobs will complete with a call to either .commit() or .abort() but
1bdc94
-     * never both.
1bdc94
-     */
1bdc94
-    void (*commit)(BlockJob *job);
1bdc94
-
1bdc94
-    /**
1bdc94
-     * If the callback is not NULL, it will be invoked when any job in the
1bdc94
-     * same transaction fails; or upon this job's failure (due to error or
1bdc94
-     * cancellation) if it is not in a transaction. Skipped if NULL.
1bdc94
-     *
1bdc94
-     * All jobs will complete with a call to either .commit() or .abort() but
1bdc94
-     * never both.
1bdc94
-     */
1bdc94
-    void (*abort)(BlockJob *job);
1bdc94
-
1bdc94
-    /**
1bdc94
-     * If the callback is not NULL, it will be invoked after a call to either
1bdc94
-     * .commit() or .abort(). Regardless of which callback is invoked after
1bdc94
-     * completion, .clean() will always be called, even if the job does not
1bdc94
-     * belong to a transaction group.
1bdc94
-     */
1bdc94
-    void (*clean)(BlockJob *job);
1bdc94
-
1bdc94
     /*
1bdc94
      * If the callback is not NULL, it will be invoked before the job is
1bdc94
      * resumed in a new AioContext.  This is the place to move any resources
1bdc94
@@ -156,14 +128,6 @@ void block_job_yield(BlockJob *job);
1bdc94
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
1bdc94
 
1bdc94
 /**
1bdc94
- * block_job_early_fail:
1bdc94
- * @bs: The block device.
1bdc94
- *
1bdc94
- * The block job could not be started, free it.
1bdc94
- */
1bdc94
-void block_job_early_fail(BlockJob *job);
1bdc94
-
1bdc94
-/**
1bdc94
  * block_job_completed:
1bdc94
  * @job: The job being completed.
1bdc94
  * @ret: The status code.
1bdc94
diff --git a/include/qemu/job.h b/include/qemu/job.h
1bdc94
index 14d9377..3e817be 100644
1bdc94
--- a/include/qemu/job.h
1bdc94
+++ b/include/qemu/job.h
1bdc94
@@ -29,6 +29,7 @@
1bdc94
 #include "qapi/qapi-types-block-core.h"
1bdc94
 #include "qemu/queue.h"
1bdc94
 #include "qemu/coroutine.h"
1bdc94
+#include "block/aio.h"
1bdc94
 
1bdc94
 typedef struct JobDriver JobDriver;
1bdc94
 
1bdc94
@@ -105,6 +106,15 @@ typedef struct Job {
1bdc94
     /** True if this job should automatically dismiss itself */
1bdc94
     bool auto_dismiss;
1bdc94
 
1bdc94
+    /** ret code passed to block_job_completed. */
1bdc94
+    int ret;
1bdc94
+
1bdc94
+    /** The completion function that will be called when the job completes.  */
1bdc94
+    BlockCompletionFunc *cb;
1bdc94
+
1bdc94
+    /** The opaque value that is passed to the completion function.  */
1bdc94
+    void *opaque;
1bdc94
+
1bdc94
     /** Notifiers called when a cancelled job is finalised */
1bdc94
     NotifierList on_finalize_cancelled;
1bdc94
 
1bdc94
@@ -151,6 +161,35 @@ struct JobDriver {
1bdc94
      */
1bdc94
     void (*user_resume)(Job *job);
1bdc94
 
1bdc94
+    /**
1bdc94
+     * If the callback is not NULL, it will be invoked when all the jobs
1bdc94
+     * belonging to the same transaction complete; or upon this job's
1bdc94
+     * completion if it is not in a transaction. Skipped if NULL.
1bdc94
+     *
1bdc94
+     * All jobs will complete with a call to either .commit() or .abort() but
1bdc94
+     * never both.
1bdc94
+     */
1bdc94
+    void (*commit)(Job *job);
1bdc94
+
1bdc94
+    /**
1bdc94
+     * If the callback is not NULL, it will be invoked when any job in the
1bdc94
+     * same transaction fails; or upon this job's failure (due to error or
1bdc94
+     * cancellation) if it is not in a transaction. Skipped if NULL.
1bdc94
+     *
1bdc94
+     * All jobs will complete with a call to either .commit() or .abort() but
1bdc94
+     * never both.
1bdc94
+     */
1bdc94
+    void (*abort)(Job *job);
1bdc94
+
1bdc94
+    /**
1bdc94
+     * If the callback is not NULL, it will be invoked after a call to either
1bdc94
+     * .commit() or .abort(). Regardless of which callback is invoked after
1bdc94
+     * completion, .clean() will always be called, even if the job does not
1bdc94
+     * belong to a transaction group.
1bdc94
+     */
1bdc94
+    void (*clean)(Job *job);
1bdc94
+
1bdc94
+
1bdc94
     /** Called when the job is freed */
1bdc94
     void (*free)(Job *job);
1bdc94
 };
1bdc94
@@ -174,10 +213,12 @@ typedef enum JobCreateFlags {
1bdc94
  * @driver: The class object for the newly-created job.
1bdc94
  * @ctx: The AioContext to run the job coroutine in.
1bdc94
  * @flags: Creation flags for the job. See @JobCreateFlags.
1bdc94
+ * @cb: Completion function for the job.
1bdc94
+ * @opaque: Opaque pointer value passed to @cb.
1bdc94
  * @errp: Error object.
1bdc94
  */
1bdc94
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
1bdc94
-                 int flags, Error **errp);
1bdc94
+                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp);
1bdc94
 
1bdc94
 /**
1bdc94
  * Add a reference to Job refcnt, it will be decreased with job_unref, and then
1bdc94
@@ -300,6 +341,10 @@ Job *job_get(const char *id);
1bdc94
  */
1bdc94
 int job_apply_verb(Job *job, JobVerb verb, Error **errp);
1bdc94
 
1bdc94
+/** The @job could not be started, free it. */
1bdc94
+void job_early_fail(Job *job);
1bdc94
+
1bdc94
+
1bdc94
 typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
1bdc94
 
1bdc94
 /**
1bdc94
@@ -322,5 +367,11 @@ void job_state_transition(Job *job, JobStatus s1);
1bdc94
 void coroutine_fn job_do_yield(Job *job, uint64_t ns);
1bdc94
 bool job_should_pause(Job *job);
1bdc94
 bool job_started(Job *job);
1bdc94
+void job_do_dismiss(Job *job);
1bdc94
+int job_finalize_single(Job *job);
1bdc94
+void job_update_rc(Job *job);
1bdc94
+
1bdc94
+typedef struct BlockJob BlockJob;
1bdc94
+void block_job_txn_del_job(BlockJob *job);
1bdc94
 
1bdc94
 #endif
1bdc94
diff --git a/job.c b/job.c
1bdc94
index 817c3b4..64b64da 100644
1bdc94
--- a/job.c
1bdc94
+++ b/job.c
1bdc94
@@ -85,7 +85,7 @@ void job_state_transition(Job *job, JobStatus s1)
1bdc94
 {
1bdc94
     JobStatus s0 = job->status;
1bdc94
     assert(s1 >= 0 && s1 <= JOB_STATUS__MAX);
1bdc94
-    trace_job_state_transition(job, /* TODO re-enable: job->ret */ 0,
1bdc94
+    trace_job_state_transition(job, job->ret,
1bdc94
                                JobSTT[s0][s1] ? "allowed" : "disallowed",
1bdc94
                                JobStatus_str(s0), JobStatus_str(s1));
1bdc94
     assert(JobSTT[s0][s1]);
1bdc94
@@ -182,7 +182,7 @@ static void job_sleep_timer_cb(void *opaque)
1bdc94
 }
1bdc94
 
1bdc94
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
1bdc94
-                 int flags, Error **errp)
1bdc94
+                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp)
1bdc94
 {
1bdc94
     Job *job;
1bdc94
 
1bdc94
@@ -214,6 +214,8 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
1bdc94
     job->pause_count   = 1;
1bdc94
     job->auto_finalize = !(flags & JOB_MANUAL_FINALIZE);
1bdc94
     job->auto_dismiss  = !(flags & JOB_MANUAL_DISMISS);
1bdc94
+    job->cb            = cb;
1bdc94
+    job->opaque        = opaque;
1bdc94
 
1bdc94
     notifier_list_init(&job->on_finalize_cancelled);
1bdc94
     notifier_list_init(&job->on_finalize_completed);
1bdc94
@@ -449,6 +451,100 @@ void job_user_resume(Job *job, Error **errp)
1bdc94
     job_resume(job);
1bdc94
 }
1bdc94
 
1bdc94
+void job_do_dismiss(Job *job)
1bdc94
+{
1bdc94
+    assert(job);
1bdc94
+    job->busy = false;
1bdc94
+    job->paused = false;
1bdc94
+    job->deferred_to_main_loop = true;
1bdc94
+
1bdc94
+    /* TODO Don't assume it's a BlockJob */
1bdc94
+    block_job_txn_del_job((BlockJob*) job);
1bdc94
+
1bdc94
+    job_state_transition(job, JOB_STATUS_NULL);
1bdc94
+    job_unref(job);
1bdc94
+}
1bdc94
+
1bdc94
+void job_early_fail(Job *job)
1bdc94
+{
1bdc94
+    assert(job->status == JOB_STATUS_CREATED);
1bdc94
+    job_do_dismiss(job);
1bdc94
+}
1bdc94
+
1bdc94
+static void job_conclude(Job *job)
1bdc94
+{
1bdc94
+    job_state_transition(job, JOB_STATUS_CONCLUDED);
1bdc94
+    if (job->auto_dismiss || !job_started(job)) {
1bdc94
+        job_do_dismiss(job);
1bdc94
+    }
1bdc94
+}
1bdc94
+
1bdc94
+void job_update_rc(Job *job)
1bdc94
+{
1bdc94
+    if (!job->ret && job_is_cancelled(job)) {
1bdc94
+        job->ret = -ECANCELED;
1bdc94
+    }
1bdc94
+    if (job->ret) {
1bdc94
+        job_state_transition(job, JOB_STATUS_ABORTING);
1bdc94
+    }
1bdc94
+}
1bdc94
+
1bdc94
+static void job_commit(Job *job)
1bdc94
+{
1bdc94
+    assert(!job->ret);
1bdc94
+    if (job->driver->commit) {
1bdc94
+        job->driver->commit(job);
1bdc94
+    }
1bdc94
+}
1bdc94
+
1bdc94
+static void job_abort(Job *job)
1bdc94
+{
1bdc94
+    assert(job->ret);
1bdc94
+    if (job->driver->abort) {
1bdc94
+        job->driver->abort(job);
1bdc94
+    }
1bdc94
+}
1bdc94
+
1bdc94
+static void job_clean(Job *job)
1bdc94
+{
1bdc94
+    if (job->driver->clean) {
1bdc94
+        job->driver->clean(job);
1bdc94
+    }
1bdc94
+}
1bdc94
+
1bdc94
+int job_finalize_single(Job *job)
1bdc94
+{
1bdc94
+    assert(job_is_completed(job));
1bdc94
+
1bdc94
+    /* Ensure abort is called for late-transactional failures */
1bdc94
+    job_update_rc(job);
1bdc94
+
1bdc94
+    if (!job->ret) {
1bdc94
+        job_commit(job);
1bdc94
+    } else {
1bdc94
+        job_abort(job);
1bdc94
+    }
1bdc94
+    job_clean(job);
1bdc94
+
1bdc94
+    if (job->cb) {
1bdc94
+        job->cb(job->opaque, job->ret);
1bdc94
+    }
1bdc94
+
1bdc94
+    /* Emit events only if we actually started */
1bdc94
+    if (job_started(job)) {
1bdc94
+        if (job_is_cancelled(job)) {
1bdc94
+            job_event_cancelled(job);
1bdc94
+        } else {
1bdc94
+            job_event_completed(job);
1bdc94
+        }
1bdc94
+    }
1bdc94
+
1bdc94
+    /* TODO Don't assume it's a BlockJob */
1bdc94
+    block_job_txn_del_job((BlockJob*) job);
1bdc94
+    job_conclude(job);
1bdc94
+    return 0;
1bdc94
+}
1bdc94
+
1bdc94
 
1bdc94
 typedef struct {
1bdc94
     Job *job;
1bdc94
diff --git a/qemu-img.c b/qemu-img.c
1bdc94
index 843dc6a..91b3151 100644
1bdc94
--- a/qemu-img.c
1bdc94
+++ b/qemu-img.c
1bdc94
@@ -883,7 +883,7 @@ static void run_block_job(BlockJob *job, Error **errp)
1bdc94
     if (!job_is_completed(&job->job)) {
1bdc94
         ret = block_job_complete_sync(job, errp);
1bdc94
     } else {
1bdc94
-        ret = job->ret;
1bdc94
+        ret = job->job.ret;
1bdc94
     }
1bdc94
     job_unref(&job->job);
1bdc94
     aio_context_release(aio_context);
1bdc94
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
1bdc94
index 8bb0aa8..1fe6803 100644
1bdc94
--- a/tests/test-blockjob.c
1bdc94
+++ b/tests/test-blockjob.c
1bdc94
@@ -128,11 +128,11 @@ static void test_job_ids(void)
1bdc94
     job[1] = do_test_id(blk[1], "id0", false);
1bdc94
 
1bdc94
     /* But once job[0] finishes we can reuse its ID */
1bdc94
-    block_job_early_fail(job[0]);
1bdc94
+    job_early_fail(&job[0]->job);
1bdc94
     job[1] = do_test_id(blk[1], "id0", true);
1bdc94
 
1bdc94
     /* No job ID specified, defaults to the backend name ('drive1') */
1bdc94
-    block_job_early_fail(job[1]);
1bdc94
+    job_early_fail(&job[1]->job);
1bdc94
     job[1] = do_test_id(blk[1], NULL, true);
1bdc94
 
1bdc94
     /* Duplicate job ID */
1bdc94
@@ -145,9 +145,9 @@ static void test_job_ids(void)
1bdc94
     /* This one is valid */
1bdc94
     job[2] = do_test_id(blk[2], "id_2", true);
1bdc94
 
1bdc94
-    block_job_early_fail(job[0]);
1bdc94
-    block_job_early_fail(job[1]);
1bdc94
-    block_job_early_fail(job[2]);
1bdc94
+    job_early_fail(&job[0]->job);
1bdc94
+    job_early_fail(&job[1]->job);
1bdc94
+    job_early_fail(&job[2]->job);
1bdc94
 
1bdc94
     destroy_blk(blk[0]);
1bdc94
     destroy_blk(blk[1]);
1bdc94
-- 
1bdc94
1.8.3.1
1bdc94