[PATCH] qemu: blockjob: Transition into 'ready' state only from expected states
Nir Soffer
nsoffer at redhat.com
Tue Apr 20 12:07:03 UTC 2021
On Tue, Apr 20, 2021 at 2:45 PM Peter Krempa <pkrempa at redhat.com> wrote:
>
> In certain rare occasions qemu can transition a block job which was
> already 'ready' into 'standby' and then back. If this happens in the
> following order libvirt will get confused about the actual job state:
>
> 1) the block copy job is 'ready' (job->state == QEMU_BLOCKJOB_STATE_READY)
>
> 2) user calls qemuDomainBlockJobAbort with VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT
> flag but without VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC
>
> 3) the block job is switched to synchronous event handling
>
> 4) the block job blips to 'standby' and back to 'ready', the event is
> not processed since the blockjob is in sync mode for now
>
> 5) qemuDomainBlockJobPivot is called:
> 5.1) 'job-complete' QMP command is issued
> 5.2) job->state is set to QEMU_BLOCKJOB_STATE_PIVOTING
>
> 6) code for synchronous-wait for the job completion in qemuDomainBlockJobAbort
> is invoked
>
> 7) the waiting loop calls qemuBlockJobUpdate:
>
> 7.1) job->newstate is QEMU_BLOCKJOB_STATE_READY due to 4)
> 7.2) qemuBlockJobEventProcess is called
> 7.3) the handler for QEMU_BLOCKJOB_STATE_READY overwrites
> job->state from QEMU_BLOCKJOB_STATE_PIVOTING to QEMU_BLOCKJOB_STATE_READY
>
> 8) qemuDomainBlockJobAbort is looking for a finished job, so waits again
>
> 9) qemu finishes the blockjob and transitions it into 'concluded' state
>
> 10) qemuBlockJobUpdate is triggered again, this time finalizing the job.
> 10.1) job->newstate is = QEMU_BLOCKJOB_STATE_CONCLUDED
> job->state is = QEMU_BLOCKJOB_STATE_READY
> 10.2) qemuBlockJobEventProcessConcluded is called, the function
> checks whether there was an error with the blockjob. Since
> there was no error job->newstate becomes
> QEMU_BLOCKJOB_STATE_COMPLETED.
> 10.3) qemuBlockJobEventProcessConcludedTransition selects the action
> for the appropriate block job type where we have:
>
> case QEMU_BLOCKJOB_TYPE_COPY:
> if (job->state == QEMU_BLOCKJOB_STATE_PIVOTING && success)
> qemuBlockJobProcessEventConcludedCopyPivot(driver, vm, job, asyncJob);
> else
> qemuBlockJobProcessEventConcludedCopyAbort(driver, vm, job, asyncJob);
> break;
The log does not make it clear what libvirt is doing and why.
> Since job->state is QEMU_BLOCKJOB_STATE_READY,
> qemuBlockJobProcessEventConcludedCopyAbort is called.
>
> This patch forbids transitions to QEMU_BLOCKJOB_STATE_READY if the
> previous job state isn't QEMU_BLOCKJOB_STATE_RUNNING or
> QEMU_BLOCKJOB_STATE_NEW.
>
> Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1951507
> Signed-off-by: Peter Krempa <pkrempa at redhat.com>
> ---
> src/qemu/qemu_blockjob.c | 19 +++++++++++++------
> 1 file changed, 13 insertions(+), 6 deletions(-)
>
> diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c
> index 21fcc29ddb..9ae4500f4d 100644
> --- a/src/qemu/qemu_blockjob.c
> +++ b/src/qemu/qemu_blockjob.c
> @@ -1697,14 +1697,21 @@ qemuBlockJobEventProcess(virQEMUDriver *driver,
> break;
>
> case QEMU_BLOCKJOB_STATE_READY:
> - /* mirror may be NULL for copy job corresponding to migration */
> - if (job->disk) {
> - job->disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_READY;
> - qemuBlockJobEmitEvents(driver, vm, job->disk, job->type, job->newstate);
> + /* in certain cases qemu can blip out and back into 'ready' state for
> + * a blockjob. In cases when we already are past RUNNING the job such
> + * as when pivoting/aborting this could reset the internally set job
> + * state, thus we ignore it if the job isn't in expected state */
> + if (job->state == QEMU_BLOCKJOB_STATE_NEW ||
> + job->state == QEMU_BLOCKJOB_STATE_RUNNING) {
> + /* mirror may be NULL for copy job corresponding to migration */
> + if (job->disk) {
> + job->disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_READY;
> + qemuBlockJobEmitEvents(driver, vm, job->disk, job->type, job->newstate);
> + }
> + job->state = job->newstate;
> + qemuDomainSaveStatus(vm);
> }
> - job->state = job->newstate;
> job->newstate = -1;
> - qemuDomainSaveStatus(vm);
> break;
>
> case QEMU_BLOCKJOB_STATE_NEW:
> --
> 2.30.2
>
The fix looks good to me.
Should we add a debug log about ignoring the event when the state
is not running or new?
Seems that flipping state between ready and standby is a repeating
issue. Does it affect other block jobs?
Finally, there is no change in tests, so I guess we are missing a test
verifying this flow?
Nir
More information about the libvir-list
mailing list