[libvirt PATCH v2 1/1] qemu: fix vcpu clearing when multiple vcpu hotunplugs timeout

Peter Krempa pkrempa at redhat.com
Tue Nov 22 14:06:34 UTC 2022


On Thu, Nov 17, 2022 at 21:39:58 +0530, Shaleen Bathla wrote:
> ping

Sorry I was sick so didn't get to this until now.

> On Fri, Nov 11, 2022 at 02:54:38PM +0530, Shaleen Bathla wrote:
> > Problem:
> > libvirt has a 5 second timeout (generally) for hotplug/unplug
> > operations which can time out due to heavy load in guest.
> > 
> > vcpu hotunplug occurs one vcpu at a time.
> > But, if we perform hotplug-unplug repeatedly,
> > Case 1: qemu sends multiple timedout vcpu unplug notification before
> > libvirt processed first one.
> > Case 2: when attempting a hotplug, qemu finishes unplug of another cpu
> > 
> > libvirt waits for an async event notification from qemu regarding
> > successful vcpu delete.
> > qemu deletes its vcpu, vcpuinfo and sends notification to libvirt.
> > libvirt handles vcpu delete notification, and updates vcpuinfo
> > received from qemu in qemuDomainRefreshVcpuInfo().
> > 
> > qemu's vcpuinfo during refresh will not contain other deleted, sent vcpu
> > qemu's vcpuinfo will overwrite libvirt's vcpuinfo of the other pending
> > timedout vcpu(s) which qemu has deleted and notified libvirt.
> > The overwrite resets other timedout vcpu's alias to NULL and tid to 0.
> > 
> > The error is then seen when validating tid of vcpus.
> > Example error log:
> > "internal error: qemu didn't report thread id for vcpu 'XX'"
> > 
> > Solution:
> > Clear vcpu alias of only the vcpu that hotplug API calls.
> > Other vcpu-alias won't get reset when vcpuinfo refresh occurs.
> > Validation is also done for corresponding vcpus only, not all.
> > 
> > Co-authored-by: Partha Satapathy <partha.satapathy at oracle.com>
> > Signed-off-by: Shaleen Bathla <shaleen.bathla at oracle.com>
> > ---
> >  src/qemu/qemu_domain.c  |  6 ++++--
> >  src/qemu/qemu_hotplug.c | 39 +++++++++++++++++++++++++++++++--------
> >  2 files changed, 35 insertions(+), 10 deletions(-)
> > 
> > diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
> > index 3435da5bdc4c..6ae842d0e32f 100644
> > --- a/src/qemu/qemu_domain.c
> > +++ b/src/qemu/qemu_domain.c
> > @@ -9773,8 +9773,10 @@ qemuDomainRefreshVcpuInfo(virDomainObj *vm,
> >          vcpupriv->vcpus = info[i].vcpus;
> >          VIR_FREE(vcpupriv->type);
> >          vcpupriv->type = g_steal_pointer(&info[i].type);
> > -        VIR_FREE(vcpupriv->alias);
> > -        vcpupriv->alias = g_steal_pointer(&info[i].alias);
> > +        if (info[i].alias) {
> > +            VIR_FREE(vcpupriv->alias);
> > +            vcpupriv->alias = g_steal_pointer(&info[i].alias);
> > +        }
> >          virJSONValueFree(vcpupriv->props);
> >          vcpupriv->props = g_steal_pointer(&info[i].props);
> >          vcpupriv->enable_id = info[i].id;
> > diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
> > index da92ced2f444..f11b90a220ac 100644
> > --- a/src/qemu/qemu_hotplug.c
> > +++ b/src/qemu/qemu_hotplug.c
> > @@ -6090,6 +6090,8 @@ qemuDomainRemoveVcpu(virDomainObj *vm,
> >      unsigned int nvcpus = vcpupriv->vcpus;
> >      virErrorPtr save_error = NULL;
> >      size_t i;
> > +    bool hasVcpuPids = qemuDomainHasVcpuPids(vm);
> > +    bool rollback = false;
> > 
> >      if (qemuDomainRefreshVcpuInfo(vm, VIR_ASYNC_JOB_NONE, false) < 0)
> >          return -1;
> > @@ -6097,14 +6099,26 @@ qemuDomainRemoveVcpu(virDomainObj *vm,
> >      /* validation requires us to set the expected state prior to calling it */

This comment no longer makes sense after this commit.

> >      for (i = vcpu; i < vcpu + nvcpus; i++) {
> >          vcpuinfo = virDomainDefGetVcpu(vm->def, i);
> > +        vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpuinfo);
> > +
> >          vcpuinfo->online = false;
> > +        VIR_FREE(vcpupriv->alias); /* clear vcpu alias of only this vcpu */
> > +
> > +        if (vcpupriv->tid != 0) {
> > +            if (hasVcpuPids)
> > +                virReportError(VIR_ERR_INTERNAL_ERROR,
> > +                               _("qemu reported thread id for inactive vcpu '%zu'"), i);

Actually we can report this without checking hasVcpuPids as if qemu
doesn't report thread ids the vcpu will not have any.

> > +
> > +            rollback = true;
> > +            break;
> > +        }
> >      }
> > 
> > -    if (qemuDomainValidateVcpuInfo(vm) < 0) {
> > +    if (rollback) {
> >          /* rollback vcpu count if the setting has failed */
> >          virDomainAuditVcpu(vm, oldvcpus, oldvcpus - nvcpus, "update", false);
> > 
> > -        for (i = vcpu; i < vcpu + nvcpus; i++) {
> > +        for (; i >= vcpu; i--) {
> >              vcpuinfo = virDomainDefGetVcpu(vm->def, i);
> >              vcpuinfo->online = true;

We don't need to roll back when we don't set it in the first place.

> >          }
> > @@ -6141,6 +6155,9 @@ qemuDomainRemoveVcpuAlias(virDomainObj *vm,
> >              return;
> >          }
> >      }
> > +
> > +    VIR_DEBUG("%s not found in vcpulist of domain %s ",
> > +              alias, vm->def->name);

We prefer quotes around substitutions to make it clear what's the added
part.

> >  }
> > 
> > 
> > @@ -6209,6 +6226,7 @@ qemuDomainHotplugAddVcpu(virQEMUDriver *driver,
> >      int rc;
> >      int oldvcpus = virDomainDefGetVcpus(vm->def);
> >      size_t i;
> > +    bool hasVcpuPids = qemuDomainHasVcpuPids(vm);
> > 
> >      if (!qemuDomainSupportsNewVcpuHotplug(vm)) {
> >          virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
> > @@ -6245,14 +6263,19 @@ qemuDomainHotplugAddVcpu(virQEMUDriver *driver,
> > 
> >          vcpuinfo->online = true;
> > 
> > -        if (vcpupriv->tid > 0 &&
> > -            qemuProcessSetupVcpu(vm, i, true) < 0)
> > -            return -1;
> > +        if (vcpupriv->tid > 0) {
> > +            if (qemuProcessSetupVcpu(vm, i, true) < 0) {
> > +                return -1;
> > +            }
> > +        } else {
> > +            if (hasVcpuPids) {
> > +                virReportError(VIR_ERR_INTERNAL_ERROR,
> > +                               _("qemu didn't report thread id for vcpu '%zu'"), i);
> > +                return -1;

Well this way the code doesn't setup cgroups for any other cpus. This
has to be checked at the end of the loop.

> > +            }
> > +        }
> >      }
> > 
> > -    if (qemuDomainValidateVcpuInfo(vm) < 0)
> > -        return -1;
> > -
> >      qemuDomainVcpuPersistOrder(vm->def);
> > 
> >      if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0)

I'll be posting a fixed version with all my suggestions addressed.
Please make sure to give it a test.


More information about the libvir-list mailing list