All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Marc-André Lureau" <marcandre.lureau@gmail.com>
To: Peter Xu <peterx@redhat.com>
Cc: QEMU <qemu-devel@nongnu.org>, Laurent Vivier <lvivier@redhat.com>,
	Fam Zheng <famz@redhat.com>, Juan Quintela <quintela@redhat.com>,
	Michael Roth <mdroth@linux.vnet.ibm.com>,
	Markus Armbruster <armbru@redhat.com>,
	Stefan Hajnoczi <shajnocz@redhat.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	"Dr . David Alan Gilbert" <dgilbert@redhat.com>
Subject: Re: [Qemu-devel] [PATCH v8 14/23] monitor: separate QMP parser and dispatcher
Date: Fri, 23 Mar 2018 17:18:53 +0100	[thread overview]
Message-ID: <CAJ+F1C+EuOA56p932r8gS_sgYfnqaqdRykFwxX-qt0U-arXS2A@mail.gmail.com> (raw)
In-Reply-To: <20180309090006.10018-15-peterx@redhat.com>

On Fri, Mar 9, 2018 at 9:59 AM, Peter Xu <peterx@redhat.com> wrote:
> Originally QMP goes through these steps:
>
>   JSON Parser --> QMP Dispatcher --> Respond
>       /|\    (2)                (3)     |
>    (1) |                               \|/ (4)
>        +---------  main thread  --------+
>
> This patch does this:
>
>   JSON Parser     QMP Dispatcher --> Respond
>       /|\ |           /|\       (4)     |
>        |  | (2)        | (3)            |  (5)
>    (1) |  +----->      |               \|/
>        +---------  main thread  <-------+
>
> So the parsing job and the dispatching job is isolated now.  It gives us
> a chance in following up patches to totally move the parser outside.
>
> The isolation is done using one QEMUBH. Only one dispatcher QEMUBH is
> used for all the monitors.
>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Signed-off-by: Peter Xu <peterx@redhat.com>
> ---
>  monitor.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 178 insertions(+), 23 deletions(-)
>
> diff --git a/monitor.c b/monitor.c
> index de9343be87..5104e5db07 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -172,6 +172,13 @@ typedef struct {
>       */
>      QmpCommandList *commands;
>      bool qmp_caps[QMP_CAPABILITY__MAX];
> +    /*
> +     * Protects qmp request/response queue.  Please take monitor_lock
> +     * first when used together.
> +     */
> +    QemuMutex qmp_queue_lock;
> +    /* Input queue that holds all the parsed QMP requests */
> +    GQueue *qmp_requests;
>  } MonitorQMP;
>
>  /*
> @@ -218,6 +225,8 @@ struct Monitor {
>  /* Let's add monitor global variables to this struct. */
>  static struct {
>      IOThread *mon_iothread;
> +    /* Bottom half to dispatch the requests received from IO thread */
> +    QEMUBH *qmp_dispatcher_bh;
>  } mon_global;
>
>  /* QMP checker flags */
> @@ -600,11 +609,13 @@ static void monitor_data_init(Monitor *mon, bool skip_flush,
>  {
>      memset(mon, 0, sizeof(Monitor));
>      qemu_mutex_init(&mon->out_lock);
> +    qemu_mutex_init(&mon->qmp.qmp_queue_lock);
>      mon->outbuf = qstring_new();
>      /* Use *mon_cmds by default. */
>      mon->cmd_table = mon_cmds;
>      mon->skip_flush = skip_flush;
>      mon->use_io_thr = use_io_thr;
> +    mon->qmp.qmp_requests = g_queue_new();
>  }
>
>  static void monitor_data_destroy(Monitor *mon)
> @@ -617,6 +628,8 @@ static void monitor_data_destroy(Monitor *mon)
>      readline_free(mon->rs);
>      QDECREF(mon->outbuf);
>      qemu_mutex_destroy(&mon->out_lock);
> +    qemu_mutex_destroy(&mon->qmp.qmp_queue_lock);
> +    g_queue_free(mon->qmp.qmp_requests);
>  }
>
>  char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
> @@ -1056,6 +1069,16 @@ static void monitor_init_qmp_commands(void)
>                           qmp_marshal_qmp_capabilities, QCO_NO_OPTIONS);
>  }
>
> +static bool qmp_cap_enabled(Monitor *mon, QMPCapability cap)
> +{
> +    return mon->qmp.qmp_caps[cap];
> +}
> +
> +static bool qmp_oob_enabled(Monitor *mon)
> +{
> +    return qmp_cap_enabled(mon, QMP_CAPABILITY_OOB);
> +}
> +
>  static void qmp_caps_check(Monitor *mon, QMPCapabilityList *list,
>                             Error **errp)
>  {
> @@ -3866,30 +3889,39 @@ static void monitor_qmp_respond(Monitor *mon, QObject *rsp,
>      qobject_decref(rsp);
>  }
>
> -static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
> +struct QMPRequest {
> +    /* Owner of the request */
> +    Monitor *mon;
> +    /* "id" field of the request */
> +    QObject *id;
> +    /* Request object to be handled */
> +    QObject *req;
> +    /*
> +     * Whether we need to resume the monitor afterward.  This flag is
> +     * used to emulate the old QMP server behavior that the current
> +     * command must be completed before execution of the next one.
> +     */
> +    bool need_resume;
> +};
> +typedef struct QMPRequest QMPRequest;
> +
> +/*
> + * Dispatch one single QMP request. The function will free the req_obj
> + * and objects inside it before return.
> + */
> +static void monitor_qmp_dispatch_one(QMPRequest *req_obj)
>  {
> -    QObject *req, *rsp = NULL, *id = NULL;
> +    Monitor *mon, *old_mon;
> +    QObject *req, *rsp = NULL, *id;
>      QDict *qdict = NULL;
> -    MonitorQMP *mon_qmp = container_of(parser, MonitorQMP, parser);
> -    Monitor *old_mon, *mon = container_of(mon_qmp, Monitor, qmp);
> -
> -    Error *err = NULL;
> +    bool need_resume;
>
> -    req = json_parser_parse_err(tokens, NULL, &err);
> -    if (!req && !err) {
> -        /* json_parser_parse_err() sucks: can fail without setting @err */
> -        error_setg(&err, QERR_JSON_PARSING);
> -    }
> -    if (err) {
> -        goto err_out;
> -    }
> +    req = req_obj->req;
> +    mon = req_obj->mon;
> +    id = req_obj->id;
> +    need_resume = req_obj->need_resume;
>
> -    qdict = qobject_to_qdict(req);
> -    if (qdict) {
> -        id = qdict_get(qdict, "id");
> -        qobject_incref(id);
> -        qdict_del(qdict, "id");
> -    } /* else will fail qmp_dispatch() */
> +    g_free(req_obj);
>
>      if (trace_event_get_state_backends(TRACE_HANDLE_QMP_COMMAND)) {
>          QString *req_json = qobject_to_json(req);
> @@ -3900,7 +3932,7 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
>      old_mon = cur_mon;
>      cur_mon = mon;

There is another issue with this series, since cur_mon is global (and
not protected), an oob command may change the cur_mon while another
command is running in the main thread with unexpected consequences. I
don't have a clear idea what is the best way to solve it. Making the
variable per-thread, or going all the way to get rid of cur_mon (my
preference, but much harder)

>
> -    rsp = qmp_dispatch(cur_mon->qmp.commands, req);
> +    rsp = qmp_dispatch(mon->qmp.commands, req);
>
>      cur_mon = old_mon;
>
> @@ -3916,12 +3948,122 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
>          }
>      }
>
> -err_out:
> -    monitor_qmp_respond(mon, rsp, err, id);
> +    /* Respond if necessary */
> +    monitor_qmp_respond(mon, rsp, NULL, id);
> +
> +    /* This pairs with the monitor_suspend() in handle_qmp_command(). */
> +    if (need_resume) {
> +        monitor_resume(mon);
> +    }
>
>      qobject_decref(req);
>  }
>
> +/*
> + * Pop one QMP request from monitor queues, return NULL if not found.
> + * We are using round-robin fashion to pop the request, to avoid
> + * processing command only on a very busy monitor.  To achieve that,
> + * when we processed one request on specific monitor, we put that
> + * monitor to the end of mon_list queue.
> + */
> +static QMPRequest *monitor_qmp_requests_pop_one(void)
> +{
> +    QMPRequest *req_obj = NULL;
> +    Monitor *mon;
> +
> +    qemu_mutex_lock(&monitor_lock);
> +
> +    QTAILQ_FOREACH(mon, &mon_list, entry) {
> +        qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
> +        req_obj = g_queue_pop_head(mon->qmp.qmp_requests);
> +        qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
> +        if (req_obj) {
> +            break;
> +        }
> +    }
> +
> +    if (req_obj) {
> +        /*
> +         * We found one request on the monitor. Degrade this monitor's
> +         * priority to lowest by re-inserting it to end of queue.
> +         */
> +        QTAILQ_REMOVE(&mon_list, mon, entry);
> +        QTAILQ_INSERT_TAIL(&mon_list, mon, entry);
> +    }
> +
> +    qemu_mutex_unlock(&monitor_lock);
> +
> +    return req_obj;
> +}
> +
> +static void monitor_qmp_bh_dispatcher(void *data)
> +{
> +    QMPRequest *req_obj = monitor_qmp_requests_pop_one();
> +
> +    if (req_obj) {
> +        monitor_qmp_dispatch_one(req_obj);
> +        /* Reschedule instead of looping so the main loop stays responsive */
> +        qemu_bh_schedule(mon_global.qmp_dispatcher_bh);
> +    }
> +}
> +
> +static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
> +{
> +    QObject *req, *id = NULL;
> +    QDict *qdict = NULL;
> +    MonitorQMP *mon_qmp = container_of(parser, MonitorQMP, parser);
> +    Monitor *mon = container_of(mon_qmp, Monitor, qmp);
> +    Error *err = NULL;
> +    QMPRequest *req_obj;
> +
> +    req = json_parser_parse_err(tokens, NULL, &err);
> +    if (!req && !err) {
> +        /* json_parser_parse_err() sucks: can fail without setting @err */
> +        error_setg(&err, QERR_JSON_PARSING);
> +    }
> +    if (err) {
> +        monitor_qmp_respond(mon, NULL, err, NULL);
> +        qobject_decref(req);
> +        return;
> +    }
> +
> +    qdict = qobject_to_qdict(req);
> +    if (qdict) {
> +        id = qdict_get(qdict, "id");
> +        qobject_incref(id);
> +        qdict_del(qdict, "id");
> +    } /* else will fail qmp_dispatch() */
> +
> +    req_obj = g_new0(QMPRequest, 1);
> +    req_obj->mon = mon;
> +    req_obj->id = id;
> +    req_obj->req = req;
> +    req_obj->need_resume = false;
> +
> +    /*
> +     * If OOB is not enabled on current monitor, we'll emulate the old
> +     * behavior that we won't process current monitor any more until
> +     * it is responded.  This helps make sure that as long as OOB is
> +     * not enabled, the server will never drop any command.
> +     */
> +    if (!qmp_oob_enabled(mon)) {
> +        monitor_suspend(mon);
> +        req_obj->need_resume = true;
> +    }
> +
> +    /*
> +     * Put the request to the end of queue so that requests will be
> +     * handled in time order.  Ownership for req_obj, req, id,
> +     * etc. will be delivered to the handler side.
> +     */
> +    qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
> +    g_queue_push_tail(mon->qmp.qmp_requests, req_obj);
> +    qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
> +
> +    /* Kick the dispatcher routine */
> +    qemu_bh_schedule(mon_global.qmp_dispatcher_bh);
> +}
> +
>  static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size)
>  {
>      Monitor *mon = opaque;
> @@ -4134,6 +4276,15 @@ static void monitor_iothread_init(void)
>  {
>      mon_global.mon_iothread = iothread_create("mon_iothread",
>                                                &error_abort);
> +
> +    /*
> +     * This MUST be on main loop thread since we have commands that
> +     * have assumption to be run on main loop thread.  It would be
> +     * nice that one day we can remove this assumption in the future.
> +     */
> +    mon_global.qmp_dispatcher_bh = aio_bh_new(qemu_get_aio_context(),
> +                                              monitor_qmp_bh_dispatcher,
> +                                              NULL);
>  }
>
>  void monitor_init_globals(void)
> @@ -4280,6 +4431,10 @@ void monitor_cleanup(void)
>      }
>      qemu_mutex_unlock(&monitor_lock);
>
> +    /* QEMUBHs needs to be deleted before destroying the IOThread. */
> +    qemu_bh_delete(mon_global.qmp_dispatcher_bh);
> +    mon_global.qmp_dispatcher_bh = NULL;
> +
>      iothread_destroy(mon_global.mon_iothread);
>      mon_global.mon_iothread = NULL;
>  }
> --
> 2.14.3
>
>



-- 
Marc-André Lureau

  parent reply	other threads:[~2018-03-23 16:18 UTC|newest]

Thread overview: 78+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-03-09  8:59 [Qemu-devel] [PATCH v8 00/23] QMP: out-of-band (OOB) execution support Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 01/23] docs: update QMP documents for OOB commands Peter Xu
2018-03-09 17:13   ` Eric Blake
2018-03-12  3:32     ` Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 02/23] qobject: introduce qstring_get_try_str() Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 03/23] qobject: introduce qobject_get_try_str() Peter Xu
2018-03-09 20:10   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 04/23] qobject: let object_property_get_str() use new API Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 05/23] monitor: move skip_flush into monitor_data_init Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 06/23] monitor: move the cur_mon hack deeper for QMP Peter Xu
2018-03-10 23:13   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 07/23] monitor: unify global init Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 08/23] monitor: let mon_list be tail queue Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 09/23] monitor: allow using IO thread for parsing Peter Xu
2018-03-10 23:19   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 10/23] qmp: introduce QMPCapability Peter Xu
2018-03-11  1:25   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 11/23] monitor: introduce monitor_qmp_respond() Peter Xu
2018-03-11  1:35   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 12/23] monitor: let suspend_cnt be thread safe Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 13/23] monitor: let suspend/resume work even with QMPs Peter Xu
2018-03-11  1:53   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 14/23] monitor: separate QMP parser and dispatcher Peter Xu
2018-03-11  2:00   ` Eric Blake
2018-03-21 18:01   ` Marc-André Lureau
2018-03-21 20:09     ` Dr. David Alan Gilbert
2018-03-21 20:33       ` Eric Blake
2018-03-21 23:32         ` Marc-André Lureau
2018-03-22  5:00           ` Peter Xu
2018-03-22 13:24             ` Eric Blake
2018-03-23 16:18   ` Marc-André Lureau [this message]
2018-03-26  8:07     ` Peter Xu
2018-03-26  8:33       ` Marc-André Lureau
2018-03-26  9:08         ` Peter Xu
2018-03-26  9:46           ` Marc-André Lureau
2018-03-28  4:02             ` Peter Xu
2018-04-04 13:58               ` Marc-André Lureau
2018-04-08  3:02                 ` Peter Xu
2018-04-09  9:19                   ` Marc-André Lureau
2018-04-10  7:15                     ` Peter Xu
2018-04-10  7:56                       ` Peter Xu
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 15/23] qmp: add new event "command-dropped" Peter Xu
2018-03-11  2:03   ` Eric Blake
2018-03-09  8:59 ` [Qemu-devel] [PATCH v8 16/23] monitor: send event when command queue full Peter Xu
2018-03-11  2:11   ` Eric Blake
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 17/23] qapi: introduce new cmd option "allow-oob" Peter Xu
2018-03-11  2:27   ` Eric Blake
2018-03-12  3:35     ` Peter Xu
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 18/23] qmp: support out-of-band (oob) execution Peter Xu
2018-03-11  2:37   ` Eric Blake
2018-03-22 10:22   ` Marc-André Lureau
2018-03-23  5:18     ` Peter Xu
2018-03-23 10:03       ` Marc-André Lureau
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 19/23] qmp: isolate responses into io thread Peter Xu
2018-03-22 12:00   ` Marc-André Lureau
2018-03-23  5:50     ` Peter Xu
2018-03-23 10:00       ` Marc-André Lureau
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 20/23] monitor: enable IO thread for (qmp & !mux) typed Peter Xu
2018-03-23 12:10   ` Christian Borntraeger
2018-03-23 12:25     ` Peter Xu
2018-03-23 12:44       ` Christian Borntraeger
2018-03-23 13:01         ` Peter Xu
2018-03-23 13:21           ` Peter Maydell
2018-03-23 13:23           ` Christian Borntraeger
2018-03-23 13:39             ` Peter Xu
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 21/23] qmp: add command "x-oob-test" Peter Xu
2018-03-11  2:42   ` Eric Blake
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 22/23] tests: qmp-test: verify command batching Peter Xu
2018-03-11  2:45   ` Eric Blake
2018-03-12  3:43     ` Peter Xu
2018-03-09  9:00 ` [Qemu-devel] [PATCH v8 23/23] tests: qmp-test: add oob test Peter Xu
2018-03-11  2:49   ` Eric Blake
2018-03-12  3:56     ` Peter Xu
2018-03-11  2:59 ` [Qemu-devel] [PATCH v8 00/23] QMP: out-of-band (OOB) execution support Eric Blake
2018-03-12  4:14   ` Peter Xu
2018-03-12 12:01 ` Eric Blake
2018-03-12 12:55   ` Daniel P. Berrangé
2018-03-13  2:06     ` Peter Xu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAJ+F1C+EuOA56p932r8gS_sgYfnqaqdRykFwxX-qt0U-arXS2A@mail.gmail.com \
    --to=marcandre.lureau@gmail.com \
    --cc=armbru@redhat.com \
    --cc=dgilbert@redhat.com \
    --cc=famz@redhat.com \
    --cc=lvivier@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=pbonzini@redhat.com \
    --cc=peterx@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=quintela@redhat.com \
    --cc=shajnocz@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.