@@ -376,9 +376,21 @@ void block_job_start(BlockJob *job)
bdrv_coroutine_enter(blk_bs(job->blk), job->co);
}
+static int block_job_prepare(BlockJob *job)
+{
+ if (job->prepared) {
+ return job->ret;
+ }
+ job->prepared = true;
+ 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);
+ assert(!job->ret && job->prepared);
if (job->driver->commit) {
job->driver->commit(job);
}
@@ -408,6 +420,9 @@ static void block_job_completed_single(BlockJob *job)
job->ret = -ECANCELED;
}
+ /* NB: updates job->ret, only if not called on this job yet */
+ block_job_prepare(job);
+
if (!job->ret) {
block_job_commit(job);
} else {
@@ -545,6 +560,8 @@ static void block_job_completed_txn_success(BlockJob *job)
AioContext *ctx;
BlockJobTxn *txn = job->txn;
BlockJob *other_job, *next;
+ int rc = 0;
+
/*
* Successful completion, see if there are other running jobs in this
* txn.
@@ -554,6 +571,22 @@ static void block_job_completed_txn_success(BlockJob *job)
return;
}
}
+
+ /* Jobs may require some prep-work to complete without failure */
+ QLIST_FOREACH_SAFE(other_job, &txn->jobs, txn_list, next) {
+ ctx = blk_get_aio_context(other_job->blk);
+ aio_context_acquire(ctx);
+ assert(other_job->ret == 0);
+ rc = block_job_prepare(job);
+ aio_context_release(ctx);
+
+ /* This job failed. Cancel this transaction */
+ if (rc) {
+ block_job_completed_txn_abort(other_job);
+ return;
+ }
+ }
+
/* We are the last completed job, commit the transaction. */
QLIST_FOREACH_SAFE(other_job, &txn->jobs, txn_list, next) {
ctx = blk_get_aio_context(other_job->blk);
@@ -150,6 +150,9 @@ typedef struct BlockJob {
*/
BlockJobStatus status;
+ /* Job has made preparations to call either commit or abort */
+ bool prepared;
+
/** Non-NULL if this job is part of a transaction */
BlockJobTxn *txn;
QLIST_ENTRY(BlockJob) txn_list;
@@ -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
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. Thus, 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 as such: - All jobs in a transaction converge to the WAITING state (added in a forthcoming commit) - All jobs prepare to call either commit/abort - If any job fails, is canceled, or fails preparation, all jobs call their .abort callback. - All jobs enter the PENDING state, awaiting manual intervention (also added in a forthcoming commit) - block-job-finalize is issued by the user/management layer - All jobs call their commit callbacks. Signed-off-by: John Snow <jsnow@redhat.com> --- blockjob.c | 35 ++++++++++++++++++++++++++++++++++- include/block/blockjob.h | 3 +++ include/block/blockjob_int.h | 10 ++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-)