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