]> xenbits.xensource.com Git - qemu-xen.git/commitdiff
blockjobs: add prepare callback
authorJohn Snow <jsnow@redhat.com>
Sat, 10 Mar 2018 08:27:40 +0000 (03:27 -0500)
committerKevin Wolf <kwolf@redhat.com>
Mon, 19 Mar 2018 11:01:24 +0000 (12:01 +0100)
Some jobs upon finalization may need to perform some work that can
still fail. If these jobs are part of a transaction, it's important
that these callbacks fail the entire transaction.

We allow for a new callback in addition to commit/abort/clean that
allows us the opportunity to have fairly late-breaking failures
in the transactional process.

The expected flow is:

- All jobs in a transaction converge to the PENDING state,
  added in a forthcoming commit.
- Upon being finalized, either automatically or explicitly
  by the user, jobs prepare to complete.
- If any job fails preparation, all jobs call .abort.
- Otherwise, they succeed and call .commit.

Signed-off-by: John Snow <jsnow@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
blockjob.c
include/block/blockjob_int.h

index 7e0382475110cd831c6e7ccf14383af4b1c30eba..3c1580e1ef527aab1b98dcbfce82a730a5874d1b 100644 (file)
@@ -415,6 +415,14 @@ static void block_job_update_rc(BlockJob *job)
     }
 }
 
+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);
@@ -438,7 +446,7 @@ static void block_job_clean(BlockJob *job)
     }
 }
 
-static void block_job_completed_single(BlockJob *job)
+static int block_job_completed_single(BlockJob *job)
 {
     assert(job->completed);
 
@@ -472,6 +480,7 @@ static void block_job_completed_single(BlockJob *job)
     QLIST_REMOVE(job, txn_list);
     block_job_txn_unref(job->txn);
     block_job_conclude(job);
+    return 0;
 }
 
 static void block_job_cancel_async(BlockJob *job)
@@ -487,17 +496,22 @@ static void block_job_cancel_async(BlockJob *job)
     job->cancelled = true;
 }
 
-static void block_job_txn_apply(BlockJobTxn *txn, void fn(BlockJob *))
+static int block_job_txn_apply(BlockJobTxn *txn, int fn(BlockJob *))
 {
     AioContext *ctx;
     BlockJob *job, *next;
+    int rc = 0;
 
     QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) {
         ctx = blk_get_aio_context(job->blk);
         aio_context_acquire(ctx);
-        fn(job);
+        rc = fn(job);
         aio_context_release(ctx);
+        if (rc) {
+            break;
+        }
     }
+    return rc;
 }
 
 static int block_job_finish_sync(BlockJob *job,
@@ -580,6 +594,8 @@ static void block_job_completed_txn_success(BlockJob *job)
 {
     BlockJobTxn *txn = job->txn;
     BlockJob *other_job;
+    int rc = 0;
+
     /*
      * Successful completion, see if there are other running jobs in this
      * txn.
@@ -590,6 +606,14 @@ static void block_job_completed_txn_success(BlockJob *job)
         }
         assert(other_job->ret == 0);
     }
+
+    /* Jobs may require some prep-work to complete without failure */
+    rc = block_job_txn_apply(txn, block_job_prepare);
+    if (rc) {
+        block_job_completed_txn_abort(job);
+        return;
+    }
+
     /* We are the last completed job, commit the transaction. */
     block_job_txn_apply(txn, block_job_completed_single);
 }
index 259d49b32a9e13521e509ebd141e2d059a3fac63..642adce68b1c2f59a2c7464982e1281be0656376 100644 (file)
@@ -53,6 +53,16 @@ struct BlockJobDriver {
      */
     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.
+     *
+     * 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 when all the jobs
      * belonging to the same transaction complete; or upon this job's