Async job that wants to allow concurrent execution of regular
job should call qemuDomainObjEnterInterruptible and then
qemuDomainObjExitInterruptible when it wants to disable concurrent
execution of regular jobs.
These functions can be called from non concurrent regular jobs. In
this case the functions are noops. Non concurrent regular job is
regular job started when no async job is running. We need to take
care of these functions being called from non concurrent regular jobs
because some high level routines can be called from both contexts -
async job and non concurrent regular job. One example is qemuConnectAgent
which can be called during domain startup (async job) or domain
reconnect (non concurrent regular job).
qemuDomainObjEnterInterruptible result is void even though it detects
some error conditions. These error conditions depend solely on
qemu driver design thus for the sake of simplicity errors are
not propagated. If by mistake function will be used improreply
at least we have diagnostics in log.
These enter/exit functions detect some error conditions but
do not propagate them to the callers because this conditions
depend solely on qemu driver consistensy and not outer conditions.
(By driver consistency I mean domain mutex, domain condition
should be initialised and enter/exit should not be called
without any job or from concurret regular job).
In other words propagating error can not help handling any world situation
which is not under out control. Drop some log on detecting self
inconsistency should be enough as it should be fixed eventually.
qemuDomainObjWait and qemuDomainObjSleep are typical uses cases
of interruptible state - wait on domain condition/sleep with
interruptible state set.
---
src/qemu/qemu_domain.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_domain.h | 12 +++++
2 files changed, 156 insertions(+)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 61d2833..f6a403c 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -333,6 +333,7 @@ qemuDomainObjResetAsyncJob(qemuDomainObjPrivatePtr priv)
job->spiceMigration = false;
job->spiceMigrated = false;
job->postcopyEnabled = false;
+ job->asyncInterruptible = false;
VIR_FREE(job->current);
}
@@ -4697,6 +4698,149 @@ qemuDomainObjEnterMonitorAsync(virQEMUDriverPtr driver,
return qemuDomainObjEnterMonitorInternal(driver, obj, asyncJob);
}
+/*
+ * @obj must be locked before calling. Must be used within context of async job
+ * or non concurrent regular job.
+ *
+ * Enters interruptible state for async job. When async job is in this
+ * state another thread can run concurrent regular job.
+ */
+void
+qemuDomainObjEnterInterruptible(virDomainObjPtr obj)
+{
+ qemuDomainObjPrivatePtr priv = obj->privateData;
+ struct qemuDomainJobObj *job = &priv->job;
+
+ if (job->asyncJob) {
+ /*
+ * Entering interruptible state from concurrent regular job is design
+ * flaw. Let's not return error in this case to try to recover but
+ * drop error message to help detect this situation.
+ */
+ if (job->active) {
+ VIR_ERROR(_("Attempt to enter interruptible state for the concurrent
"
+ "regular job"));
+ return;
+ }
+ } else {
+ /*
+ * Entering interruptible state without any job at all is design flaw.
+ * Let's not return error in this case to try to recover but
+ * drop error message to help detect this situation.
+ */
+ if (!job->active)
+ VIR_ERROR(_("Attempt to enter interruptible state without any
job"));
+
+ /* In case of non concurrent regular we don't need to do anything and
+ * this situation can be easily detected on exit interruptible state
+ * as no job con run concurrently to non concurrent regular job. */
+ return;
+ }
+
+ job->asyncInterruptible = true;
+ VIR_DEBUG("Async job enters interruptible state.(obj=%p name=%s,
async=%s)",
+ obj, obj->def->name,
+ qemuDomainAsyncJobTypeToString(job->asyncJob));
+ virCondBroadcast(&priv->job.asyncCond);
+}
+
+
+/*
+ * @obj must be locked before calling. Must be used within context of async job
+ * or non concurrent regular job. Must be called if qemuDomainObjEnterInterruptible
+ * is called before.
+ *
+ * Exits async job interruptible state so after exit from this function
+ * no concurrent regular jobs are allowed to run simultaneously. The function
+ * waits until any concurrent regular job started after async job entered
+ * interruptible state is finished before exit.
+ */
+void
+qemuDomainObjExitInterruptible(virDomainObjPtr obj)
+{
+ qemuDomainObjPrivatePtr priv = obj->privateData;
+ struct qemuDomainJobObj *job = &priv->job;
+
+ /* Just noop for the case of non concurrent regular job. See
+ * also entering function. */
+ if (!job->asyncJob && job->active)
+ return;
+
+ job->asyncInterruptible = false;
+ VIR_DEBUG("Async job exits interruptible state. "
+ "(obj=%p name=%s, async=%s)",
+ obj, obj->def->name,
+ qemuDomainAsyncJobTypeToString(job->asyncJob));
+
+ /*
+ * Wait until no concurrent regular job is running. There is no real
+ * conditions for wait to fail thus just do not return error in case
+ * wait fails but log error just for safety.
+ */
+ while (job->active) {
+ if (virCondWait(&priv->job.cond, &obj->parent.lock) < 0) {
+ char buf[1024];
+
+ VIR_ERROR(_("failed to wait for job condition: %s"),
+ virStrerror(errno, buf, sizeof(buf)));
+ return;
+ }
+ }
+}
+
+
+/*
+ * @obj must be locked before calling. Must be used within context of async job
+ * or non concurrent regular job.
+ *
+ * Wait on @obj lock. Async job is in interruptlible state during wait so
+ * concurrent regular jobs are allowed to run.
+ */
+int
+qemuDomainObjWait(virDomainObjPtr obj)
+{
+ qemuDomainObjEnterInterruptible(obj);
+
+ if (virCondWait(&obj->cond, &obj->parent.lock) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to wait for domain condition"));
+ qemuDomainObjExitInterruptible(obj);
+ return -1;
+ }
+
+ qemuDomainObjExitInterruptible(obj);
+
+ if (!virDomainObjIsActive(obj)) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("domain is not running"));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * obj must be locked before calling. Must be used within context of async job
+ * or nonconcurrent regular job.
+ *
+ * Sleep with obj lock dropped. Async job is in interruptlible state during wait
+ * so concurrent regular jobs are allowed to run.
+ */
+void
+qemuDomainObjSleep(virDomainObjPtr obj, unsigned long nsec)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = nsec };
+
+ qemuDomainObjEnterInterruptible(obj);
+
+ virObjectUnlock(obj);
+ nanosleep(&ts, NULL);
+ virObjectLock(obj);
+
+ qemuDomainObjExitInterruptible(obj);
+}
+
/*
* obj must be locked before calling
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index e021da5..25a6823 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -151,6 +151,8 @@ struct qemuDomainJobObj {
virCond asyncCond; /* Use to coordinate with async jobs */
qemuDomainAsyncJob asyncJob; /* Currently active async job */
+ bool asyncInterruptible; /* Regular jobs compatible with current
+ async job are allowed to run */
unsigned long long asyncOwner; /* Thread which set current async job */
const char *asyncOwnerAPI; /* The API which owns the async job */
unsigned long long asyncStarted; /* When the current async job started */
@@ -527,6 +529,16 @@ int qemuDomainObjEnterMonitorAsync(virQEMUDriverPtr driver,
qemuDomainAsyncJob asyncJob)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
+void qemuDomainObjEnterInterruptible(virDomainObjPtr obj)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void qemuDomainObjExitInterruptible(virDomainObjPtr obj)
+ ATTRIBUTE_NONNULL(1);
+
+int qemuDomainObjWait(virDomainObjPtr obj)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
+
+void qemuDomainObjSleep(virDomainObjPtr obj, unsigned long nsec)
+ ATTRIBUTE_NONNULL(1);
qemuAgentPtr qemuDomainObjEnterAgent(virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1);
--
1.8.3.1