#include "qapi/qmp/qlist.h"
#include "trace.h"
+/*
+ * qmp_dispatcher_co_busy is used for synchronisation between the
+ * monitor thread and the main thread to ensure that the dispatcher
+ * coroutine never gets scheduled a second time when it's already
+ * scheduled (scheduling the same coroutine twice is forbidden).
+ *
+ * It is true if the coroutine will process at least one more request
+ * before going to sleep. Either it has been kicked already, or it
+ * is active and processing requests. Additional requests may therefore
+ * be pushed onto mon->qmp_requests, and @qmp_dispatcher_co_shutdown may
+ * be set without further ado. @qmp_dispatcher_co must not be woken up
+ * in this case.
+ *
+ * If false, you have to wake up @qmp_dispatcher_co after pushing new
+ * requests. You also have to set @qmp_dispatcher_co_busy to true
+ * before waking up the coroutine.
+ *
+ * The coroutine will automatically change this variable back to false
+ * before it yields. Nobody else may set the variable to false.
+ *
+ * Access must be atomic for thread safety.
+ */
+static bool qmp_dispatcher_co_busy = true;
+
struct QMPRequest {
/* Owner of the request */
MonitorQMP *mon;
Monitor *mon;
MonitorQMP *qmp_mon;
- QEMU_LOCK_GUARD(&monitor_lock);
-
QTAILQ_FOREACH(mon, &mon_list, entry) {
if (!monitor_is_qmp(mon)) {
continue;
return req_obj;
}
-void coroutine_fn monitor_qmp_dispatcher_co(void *data)
+static QMPRequest *monitor_qmp_dispatcher_pop_any(void)
{
- QMPRequest *req_obj = NULL;
- QDict *rsp;
- bool oob_enabled;
- MonitorQMP *mon;
-
while (true) {
- assert(qatomic_mb_read(&qmp_dispatcher_co_busy) == true);
+ /*
+ * To avoid double scheduling, busy is true on entry to
+ * monitor_qmp_dispatcher_co(), and must be set again before
+ * aio_co_wake()-ing it.
+ */
+ assert(qatomic_read(&qmp_dispatcher_co_busy) == true);
/*
* Mark the dispatcher as not busy already here so that we
* don't miss any new requests coming in the middle of our
* processing.
+ *
+ * Clear qmp_dispatcher_co_busy before reading request.
*/
- qatomic_mb_set(&qmp_dispatcher_co_busy, false);
+ qatomic_set_mb(&qmp_dispatcher_co_busy, false);
- /* On shutdown, don't take any more requests from the queue */
- if (qmp_dispatcher_co_shutdown) {
- return;
- }
+ WITH_QEMU_LOCK_GUARD(&monitor_lock) {
+ QMPRequest *req_obj;
- while (!(req_obj = monitor_qmp_requests_pop_any_with_lock())) {
- /*
- * No more requests to process. Wait to be reentered from
- * handle_qmp_command() when it pushes more requests, or
- * from monitor_cleanup() when it requests shutdown.
- */
- if (!qmp_dispatcher_co_shutdown) {
- qemu_coroutine_yield();
-
- /*
- * busy must be set to true again by whoever
- * rescheduled us to avoid double scheduling
- */
- assert(qatomic_xchg(&qmp_dispatcher_co_busy, false) == true);
- }
-
- /*
- * qmp_dispatcher_co_shutdown may have changed if we
- * yielded and were reentered from monitor_cleanup()
- */
+ /* On shutdown, don't take any more requests from the queue */
if (qmp_dispatcher_co_shutdown) {
- return;
+ return NULL;
}
- }
-
- trace_monitor_qmp_in_band_dequeue(req_obj,
- req_obj->mon->qmp_requests->length);
- if (qatomic_xchg(&qmp_dispatcher_co_busy, true) == true) {
- /*
- * Someone rescheduled us (probably because a new requests
- * came in), but we didn't actually yield. Do that now,
- * only to be immediately reentered and removed from the
- * list of scheduled coroutines.
- */
- qemu_coroutine_yield();
+ req_obj = monitor_qmp_requests_pop_any_with_lock();
+ if (req_obj) {
+ return req_obj;
+ }
}
/*
- * Move the coroutine from iohandler_ctx to qemu_aio_context for
- * executing the command handler so that it can make progress if it
- * involves an AIO_WAIT_WHILE().
+ * No more requests to process. Wait to be reentered from
+ * handle_qmp_command() when it pushes more requests, or
+ * from monitor_cleanup() when it requests shutdown.
*/
- aio_co_schedule(qemu_get_aio_context(), qmp_dispatcher_co);
qemu_coroutine_yield();
+ }
+}
+
+void coroutine_fn monitor_qmp_dispatcher_co(void *data)
+{
+ QMPRequest *req_obj;
+ QDict *rsp;
+ bool oob_enabled;
+ MonitorQMP *mon;
+
+ while ((req_obj = monitor_qmp_dispatcher_pop_any()) != NULL) {
+ trace_monitor_qmp_in_band_dequeue(req_obj,
+ req_obj->mon->qmp_requests->length);
/*
* @req_obj has a request, we hold req_obj->mon->qmp_queue_lock
monitor_resume(&mon->common);
}
+ /*
+ * Drop the queue mutex now, before yielding, otherwise we might
+ * deadlock if the main thread tries to lock it.
+ */
qemu_mutex_unlock(&mon->qmp_queue_lock);
+ if (qatomic_xchg(&qmp_dispatcher_co_busy, true) == true) {
+ /*
+ * Someone rescheduled us (probably because a new requests
+ * came in), but we didn't actually yield. Do that now,
+ * only to be immediately reentered and removed from the
+ * list of scheduled coroutines.
+ */
+ qemu_coroutine_yield();
+ }
+
/* Process request */
if (req_obj->req) {
if (trace_event_get_state(TRACE_MONITOR_QMP_CMD_IN_BAND)) {
}
qmp_request_free(req_obj);
+ }
+ qatomic_set(&qmp_dispatcher_co, NULL);
+}
- /*
- * Yield and reschedule so the main loop stays responsive.
- *
- * Move back to iohandler_ctx so that nested event loops for
- * qemu_aio_context don't start new monitor commands.
- */
- aio_co_schedule(iohandler_get_aio_context(), qmp_dispatcher_co);
- qemu_coroutine_yield();
+void qmp_dispatcher_co_wake(void)
+{
+ /* Write request before reading qmp_dispatcher_co_busy. */
+ smp_mb__before_rmw();
+
+ if (!qatomic_xchg(&qmp_dispatcher_co_busy, true)) {
+ aio_co_wake(qmp_dispatcher_co);
}
}
}
/* Kick the dispatcher routine */
- if (!qatomic_xchg(&qmp_dispatcher_co_busy, true)) {
- aio_co_wake(qmp_dispatcher_co);
- }
+ qmp_dispatcher_co_wake();
}
static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size)