blockjobs: add prepare callback

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>
This commit is contained in:
John Snow 2018-03-10 03:27:40 -05:00 committed by Kevin Wolf
parent efe4d4b7b2
commit 2da4617a54
2 changed files with 37 additions and 3 deletions

View 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) static void block_job_commit(BlockJob *job)
{ {
assert(!job->ret); 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); assert(job->completed);
@ -472,6 +480,7 @@ static void block_job_completed_single(BlockJob *job)
QLIST_REMOVE(job, txn_list); QLIST_REMOVE(job, txn_list);
block_job_txn_unref(job->txn); block_job_txn_unref(job->txn);
block_job_conclude(job); block_job_conclude(job);
return 0;
} }
static void block_job_cancel_async(BlockJob *job) static void block_job_cancel_async(BlockJob *job)
@ -487,17 +496,22 @@ static void block_job_cancel_async(BlockJob *job)
job->cancelled = true; 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; AioContext *ctx;
BlockJob *job, *next; BlockJob *job, *next;
int rc = 0;
QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) { QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) {
ctx = blk_get_aio_context(job->blk); ctx = blk_get_aio_context(job->blk);
aio_context_acquire(ctx); aio_context_acquire(ctx);
fn(job); rc = fn(job);
aio_context_release(ctx); aio_context_release(ctx);
if (rc) {
break;
}
} }
return rc;
} }
static int block_job_finish_sync(BlockJob *job, 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; BlockJobTxn *txn = job->txn;
BlockJob *other_job; BlockJob *other_job;
int rc = 0;
/* /*
* Successful completion, see if there are other running jobs in this * Successful completion, see if there are other running jobs in this
* txn. * txn.
@ -590,6 +606,14 @@ static void block_job_completed_txn_success(BlockJob *job)
} }
assert(other_job->ret == 0); 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. */ /* We are the last completed job, commit the transaction. */
block_job_txn_apply(txn, block_job_completed_single); block_job_txn_apply(txn, block_job_completed_single);
} }

View File

@ -53,6 +53,16 @@ struct BlockJobDriver {
*/ */
void (*complete)(BlockJob *job, Error **errp); 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 * 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 * belonging to the same transaction complete; or upon this job's