All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
@ 2019-04-09 16:09 ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Hi,

HMP and QMP commands are handled synchronously in qemu today. But
there are benefits allowing the command handler to re-enter the main
loop if the command cannot be handled synchronously, or if it is
long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
without it.

The common solution is to use a pair of command+event in this case.
But this approach has a number of issues:
- you can't "fix" an existing command: you need a new API, and ad-hoc
  documentation for that command+signal association, and old/broken
  command deprecation
- since the reply event is broadcasted and 'id' is used for matching the
  request, it may conflict with other clients request 'id' space
- it is arguably less efficient and elegant (weird API, useless return
  in most cases, broadcast reply, no cancelling on disconnect etc)

The following series implements an async command solution instead. By
introducing a session context and a command return handler, it can:
- defer the return, allowing the mainloop to reenter
- return only to the caller (instead of broadcast events for reply)
- optionnally allow cancellation when the client is gone
- track on-going qapi command(s) per client/session

and without introduction of new QMP APIs or client visible change.

Existing qemu commands can be gradually replaced by async:true
variants when needed, while carefully reviewing the concurrency
aspects. The async:true commands marshaller helpers are splitted in
half, the calling and return functions. The command is called with a
QmpReturn context, that can return immediately or later, using the
generated return helper, which allows for a step-by-step conversion.

The screendump command is converted to an async:true version to solve
rhbz#1230527. The command shows basic cancellation (this could be
extended if needed). It could be further improved to do asynchronous
IO writes as well.

v4:
- rebased, mostly adapting to new OOB code
  (there was not much feedback in v3 for the async command part,
   but preliminary patches got merged!)
- drop the RFC status

v3:
- complete rework, dropping the asynchronous commands visibility from
  the protocol side entirely (until there is a real need for it)
- rebased, with a few preliminary cleanup patches
- teach asynchronous commands to HMP

v2:
- documentation fixes and improvements
- fix calling async commands sync without id
- fix bad hmp monitor assert
- add a few extra asserts
- add async with no-id failure and screendump test

Marc-André Lureau (20):
  qmp: constify QmpCommand and list
  json-lexer: make it safe to call destroy multiple times
  qmp: add QmpSession
  QmpSession: add a return callback
  QmpSession: add json parser and use it in qga
  monitor: use qmp session to parse json feed
  qga: simplify dispatch_return_cb
  QmpSession: introduce QmpReturn
  qmp: simplify qmp_return_error()
  QmpSession: keep a queue of pending commands
  QmpSession: return orderly
  qmp: introduce asynchronous command type
  scripts: learn 'async' qapi commands
  qmp: add qmp_return_is_cancelled()
  monitor: add qmp_return_get_monitor()
  console: add graphic_hw_update_done()
  console: make screendump asynchronous
  monitor: start making qmp_human_monitor_command() asynchronous
  monitor: teach HMP about asynchronous commands
  hmp: call the asynchronous QMP screendump to fix outdated/glitches

 qapi/misc.json                          |   3 +-
 qapi/ui.json                            |   3 +-
 scripts/qapi/commands.py                | 151 ++++++++++++++---
 scripts/qapi/common.py                  |  15 +-
 scripts/qapi/doc.py                     |   3 +-
 scripts/qapi/introspect.py              |   3 +-
 hmp.h                                   |   3 +-
 include/monitor/monitor.h               |   3 +
 include/qapi/qmp/dispatch.h             |  89 +++++++++-
 include/qapi/qmp/json-parser.h          |   7 +-
 include/ui/console.h                    |   5 +
 hmp.c                                   |   6 +-
 hw/display/qxl-render.c                 |   9 +-
 hw/display/qxl.c                        |   1 +
 monitor.c                               | 198 ++++++++++++++--------
 qapi/qmp-dispatch.c                     | 214 +++++++++++++++++++-----
 qapi/qmp-registry.c                     |  33 +++-
 qga/commands.c                          |   2 +-
 qga/main.c                              |  51 ++----
 qobject/json-lexer.c                    |   5 +-
 qobject/json-streamer.c                 |   3 +-
 tests/test-qmp-cmds.c                   | 206 +++++++++++++++++++----
 ui/console.c                            | 100 +++++++++--
 hmp-commands.hx                         |   3 +-
 tests/qapi-schema/qapi-schema-test.json |   5 +
 tests/qapi-schema/qapi-schema-test.out  |   8 +
 tests/qapi-schema/test-qapi.py          |   8 +-
 27 files changed, 877 insertions(+), 260 deletions(-)

-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
@ 2019-04-09 16:09 ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Hi,

HMP and QMP commands are handled synchronously in qemu today. But
there are benefits allowing the command handler to re-enter the main
loop if the command cannot be handled synchronously, or if it is
long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
without it.

The common solution is to use a pair of command+event in this case.
But this approach has a number of issues:
- you can't "fix" an existing command: you need a new API, and ad-hoc
  documentation for that command+signal association, and old/broken
  command deprecation
- since the reply event is broadcasted and 'id' is used for matching the
  request, it may conflict with other clients request 'id' space
- it is arguably less efficient and elegant (weird API, useless return
  in most cases, broadcast reply, no cancelling on disconnect etc)

The following series implements an async command solution instead. By
introducing a session context and a command return handler, it can:
- defer the return, allowing the mainloop to reenter
- return only to the caller (instead of broadcast events for reply)
- optionnally allow cancellation when the client is gone
- track on-going qapi command(s) per client/session

and without introduction of new QMP APIs or client visible change.

Existing qemu commands can be gradually replaced by async:true
variants when needed, while carefully reviewing the concurrency
aspects. The async:true commands marshaller helpers are splitted in
half, the calling and return functions. The command is called with a
QmpReturn context, that can return immediately or later, using the
generated return helper, which allows for a step-by-step conversion.

The screendump command is converted to an async:true version to solve
rhbz#1230527. The command shows basic cancellation (this could be
extended if needed). It could be further improved to do asynchronous
IO writes as well.

v4:
- rebased, mostly adapting to new OOB code
  (there was not much feedback in v3 for the async command part,
   but preliminary patches got merged!)
- drop the RFC status

v3:
- complete rework, dropping the asynchronous commands visibility from
  the protocol side entirely (until there is a real need for it)
- rebased, with a few preliminary cleanup patches
- teach asynchronous commands to HMP

v2:
- documentation fixes and improvements
- fix calling async commands sync without id
- fix bad hmp monitor assert
- add a few extra asserts
- add async with no-id failure and screendump test

Marc-André Lureau (20):
  qmp: constify QmpCommand and list
  json-lexer: make it safe to call destroy multiple times
  qmp: add QmpSession
  QmpSession: add a return callback
  QmpSession: add json parser and use it in qga
  monitor: use qmp session to parse json feed
  qga: simplify dispatch_return_cb
  QmpSession: introduce QmpReturn
  qmp: simplify qmp_return_error()
  QmpSession: keep a queue of pending commands
  QmpSession: return orderly
  qmp: introduce asynchronous command type
  scripts: learn 'async' qapi commands
  qmp: add qmp_return_is_cancelled()
  monitor: add qmp_return_get_monitor()
  console: add graphic_hw_update_done()
  console: make screendump asynchronous
  monitor: start making qmp_human_monitor_command() asynchronous
  monitor: teach HMP about asynchronous commands
  hmp: call the asynchronous QMP screendump to fix outdated/glitches

 qapi/misc.json                          |   3 +-
 qapi/ui.json                            |   3 +-
 scripts/qapi/commands.py                | 151 ++++++++++++++---
 scripts/qapi/common.py                  |  15 +-
 scripts/qapi/doc.py                     |   3 +-
 scripts/qapi/introspect.py              |   3 +-
 hmp.h                                   |   3 +-
 include/monitor/monitor.h               |   3 +
 include/qapi/qmp/dispatch.h             |  89 +++++++++-
 include/qapi/qmp/json-parser.h          |   7 +-
 include/ui/console.h                    |   5 +
 hmp.c                                   |   6 +-
 hw/display/qxl-render.c                 |   9 +-
 hw/display/qxl.c                        |   1 +
 monitor.c                               | 198 ++++++++++++++--------
 qapi/qmp-dispatch.c                     | 214 +++++++++++++++++++-----
 qapi/qmp-registry.c                     |  33 +++-
 qga/commands.c                          |   2 +-
 qga/main.c                              |  51 ++----
 qobject/json-lexer.c                    |   5 +-
 qobject/json-streamer.c                 |   3 +-
 tests/test-qmp-cmds.c                   | 206 +++++++++++++++++++----
 ui/console.c                            | 100 +++++++++--
 hmp-commands.hx                         |   3 +-
 tests/qapi-schema/qapi-schema-test.json |   5 +
 tests/qapi-schema/qapi-schema-test.out  |   8 +
 tests/qapi-schema/test-qapi.py          |   8 +-
 27 files changed, 877 insertions(+), 260 deletions(-)

-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 01/20] qmp: constify QmpCommand and list
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Since 0b69f6f72ce47a37a749b056b6d5ec64c61f11e8 "qapi: remove
qmp_unregister_command()", the command list can be declared const.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 9 +++++----
 monitor.c                   | 4 ++--
 qapi/qmp-dispatch.c         | 6 +++---
 qapi/qmp-registry.c         | 6 +++---
 qga/commands.c              | 2 +-
 qga/main.c                  | 6 +++---
 6 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 9aa426a398..5a9cf82472 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -39,7 +39,8 @@ typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList;
 
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
-QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name);
+const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
+                                   const char *name);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
 
@@ -47,13 +48,13 @@ bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
 QDict *qmp_error_response(Error *err);
-QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request,
+QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
                     bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
 
-typedef void (*qmp_cmd_callback_fn)(QmpCommand *cmd, void *opaque);
+typedef void (*qmp_cmd_callback_fn)(const QmpCommand *cmd, void *opaque);
 
-void qmp_for_each_command(QmpCommandList *cmds, qmp_cmd_callback_fn fn,
+void qmp_for_each_command(const QmpCommandList *cmds, qmp_cmd_callback_fn fn,
                           void *opaque);
 
 #endif
diff --git a/monitor.c b/monitor.c
index 4807bbe811..abc5bdc4ba 100644
--- a/monitor.c
+++ b/monitor.c
@@ -174,7 +174,7 @@ typedef struct {
      * qmp_capabilities succeeds, we go into command mode, and
      * @command becomes &qmp_commands.
      */
-    QmpCommandList *commands;
+    const QmpCommandList *commands;
     bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */
     bool capab[QMP_CAPABILITY__MAX];         /* offered and accepted */
     /*
@@ -1071,7 +1071,7 @@ static void hmp_info_help(Monitor *mon, const QDict *qdict)
     help_cmd(mon, "info");
 }
 
-static void query_commands_cb(QmpCommand *cmd, void *opaque)
+static void query_commands_cb(const QmpCommand *cmd, void *opaque)
 {
     CommandInfoList *info, **list = opaque;
 
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index e2c366e09e..f9d43046aa 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -75,14 +75,14 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
     return dict;
 }
 
-static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request,
+static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
                                 bool allow_oob, Error **errp)
 {
     Error *local_err = NULL;
     bool oob;
     const char *command;
     QDict *args, *dict;
-    QmpCommand *cmd;
+    const QmpCommand *cmd;
     QObject *ret = NULL;
 
     dict = qmp_dispatch_check_obj(request, allow_oob, errp);
@@ -163,7 +163,7 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
-QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request,
+QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
                     bool allow_oob)
 {
     Error *err = NULL;
diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c
index ca00f74795..d0f9a1d3e3 100644
--- a/qapi/qmp-registry.c
+++ b/qapi/qmp-registry.c
@@ -27,7 +27,7 @@ void qmp_register_command(QmpCommandList *cmds, const char *name,
     QTAILQ_INSERT_TAIL(cmds, cmd, node);
 }
 
-QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name)
+const QmpCommand *qmp_find_command(const QmpCommandList *cmds, const char *name)
 {
     QmpCommand *cmd;
 
@@ -77,10 +77,10 @@ bool qmp_has_success_response(const QmpCommand *cmd)
     return !(cmd->options & QCO_NO_SUCCESS_RESP);
 }
 
-void qmp_for_each_command(QmpCommandList *cmds, qmp_cmd_callback_fn fn,
+void qmp_for_each_command(const QmpCommandList *cmds, qmp_cmd_callback_fn fn,
                           void *opaque)
 {
-    QmpCommand *cmd;
+    const QmpCommand *cmd;
 
     QTAILQ_FOREACH(cmd, cmds, node) {
         fn(cmd, opaque);
diff --git a/qga/commands.c b/qga/commands.c
index 0c7d1385c2..05e9ab6c3d 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -54,7 +54,7 @@ void qmp_guest_ping(Error **errp)
     slog("guest-ping called");
 }
 
-static void qmp_command_info(QmpCommand *cmd, void *opaque)
+static void qmp_command_info(const QmpCommand *cmd, void *opaque)
 {
     GuestAgentInfo *info = opaque;
     GuestAgentCommandInfo *cmd_info;
diff --git a/qga/main.c b/qga/main.c
index c0d77c79c4..5b144b9250 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -359,7 +359,7 @@ static gint ga_strcmp(gconstpointer str1, gconstpointer str2)
 }
 
 /* disable commands that aren't safe for fsfreeze */
-static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque)
+static void ga_disable_non_whitelisted(const QmpCommand *cmd, void *opaque)
 {
     bool whitelisted = false;
     int i = 0;
@@ -378,7 +378,7 @@ static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque)
 }
 
 /* [re-]enable all commands, except those explicitly blacklisted by user */
-static void ga_enable_non_blacklisted(QmpCommand *cmd, void *opaque)
+static void ga_enable_non_blacklisted(const QmpCommand *cmd, void *opaque)
 {
     GList *blacklist = opaque;
     const char *name = qmp_command_name(cmd);
@@ -918,7 +918,7 @@ int64_t ga_get_fd_handle(GAState *s, Error **errp)
     return handle;
 }
 
-static void ga_print_cmd(QmpCommand *cmd, void *opaque)
+static void ga_print_cmd(const QmpCommand *cmd, void *opaque)
 {
     printf("%s\n", qmp_command_name(cmd));
 }
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 01/20] qmp: constify QmpCommand and list
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Since 0b69f6f72ce47a37a749b056b6d5ec64c61f11e8 "qapi: remove
qmp_unregister_command()", the command list can be declared const.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 9 +++++----
 monitor.c                   | 4 ++--
 qapi/qmp-dispatch.c         | 6 +++---
 qapi/qmp-registry.c         | 6 +++---
 qga/commands.c              | 2 +-
 qga/main.c                  | 6 +++---
 6 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 9aa426a398..5a9cf82472 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -39,7 +39,8 @@ typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList;
 
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
-QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name);
+const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
+                                   const char *name);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
 
@@ -47,13 +48,13 @@ bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
 QDict *qmp_error_response(Error *err);
-QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request,
+QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
                     bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
 
-typedef void (*qmp_cmd_callback_fn)(QmpCommand *cmd, void *opaque);
+typedef void (*qmp_cmd_callback_fn)(const QmpCommand *cmd, void *opaque);
 
-void qmp_for_each_command(QmpCommandList *cmds, qmp_cmd_callback_fn fn,
+void qmp_for_each_command(const QmpCommandList *cmds, qmp_cmd_callback_fn fn,
                           void *opaque);
 
 #endif
diff --git a/monitor.c b/monitor.c
index 4807bbe811..abc5bdc4ba 100644
--- a/monitor.c
+++ b/monitor.c
@@ -174,7 +174,7 @@ typedef struct {
      * qmp_capabilities succeeds, we go into command mode, and
      * @command becomes &qmp_commands.
      */
-    QmpCommandList *commands;
+    const QmpCommandList *commands;
     bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */
     bool capab[QMP_CAPABILITY__MAX];         /* offered and accepted */
     /*
@@ -1071,7 +1071,7 @@ static void hmp_info_help(Monitor *mon, const QDict *qdict)
     help_cmd(mon, "info");
 }
 
-static void query_commands_cb(QmpCommand *cmd, void *opaque)
+static void query_commands_cb(const QmpCommand *cmd, void *opaque)
 {
     CommandInfoList *info, **list = opaque;
 
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index e2c366e09e..f9d43046aa 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -75,14 +75,14 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
     return dict;
 }
 
-static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request,
+static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
                                 bool allow_oob, Error **errp)
 {
     Error *local_err = NULL;
     bool oob;
     const char *command;
     QDict *args, *dict;
-    QmpCommand *cmd;
+    const QmpCommand *cmd;
     QObject *ret = NULL;
 
     dict = qmp_dispatch_check_obj(request, allow_oob, errp);
@@ -163,7 +163,7 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
-QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request,
+QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
                     bool allow_oob)
 {
     Error *err = NULL;
diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c
index ca00f74795..d0f9a1d3e3 100644
--- a/qapi/qmp-registry.c
+++ b/qapi/qmp-registry.c
@@ -27,7 +27,7 @@ void qmp_register_command(QmpCommandList *cmds, const char *name,
     QTAILQ_INSERT_TAIL(cmds, cmd, node);
 }
 
-QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name)
+const QmpCommand *qmp_find_command(const QmpCommandList *cmds, const char *name)
 {
     QmpCommand *cmd;
 
@@ -77,10 +77,10 @@ bool qmp_has_success_response(const QmpCommand *cmd)
     return !(cmd->options & QCO_NO_SUCCESS_RESP);
 }
 
-void qmp_for_each_command(QmpCommandList *cmds, qmp_cmd_callback_fn fn,
+void qmp_for_each_command(const QmpCommandList *cmds, qmp_cmd_callback_fn fn,
                           void *opaque)
 {
-    QmpCommand *cmd;
+    const QmpCommand *cmd;
 
     QTAILQ_FOREACH(cmd, cmds, node) {
         fn(cmd, opaque);
diff --git a/qga/commands.c b/qga/commands.c
index 0c7d1385c2..05e9ab6c3d 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -54,7 +54,7 @@ void qmp_guest_ping(Error **errp)
     slog("guest-ping called");
 }
 
-static void qmp_command_info(QmpCommand *cmd, void *opaque)
+static void qmp_command_info(const QmpCommand *cmd, void *opaque)
 {
     GuestAgentInfo *info = opaque;
     GuestAgentCommandInfo *cmd_info;
diff --git a/qga/main.c b/qga/main.c
index c0d77c79c4..5b144b9250 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -359,7 +359,7 @@ static gint ga_strcmp(gconstpointer str1, gconstpointer str2)
 }
 
 /* disable commands that aren't safe for fsfreeze */
-static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque)
+static void ga_disable_non_whitelisted(const QmpCommand *cmd, void *opaque)
 {
     bool whitelisted = false;
     int i = 0;
@@ -378,7 +378,7 @@ static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque)
 }
 
 /* [re-]enable all commands, except those explicitly blacklisted by user */
-static void ga_enable_non_blacklisted(QmpCommand *cmd, void *opaque)
+static void ga_enable_non_blacklisted(const QmpCommand *cmd, void *opaque)
 {
     GList *blacklist = opaque;
     const char *name = qmp_command_name(cmd);
@@ -918,7 +918,7 @@ int64_t ga_get_fd_handle(GAState *s, Error **errp)
     return handle;
 }
 
-static void ga_print_cmd(QmpCommand *cmd, void *opaque)
+static void ga_print_cmd(const QmpCommand *cmd, void *opaque)
 {
     printf("%s\n", qmp_command_name(cmd));
 }
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 02/20] json-lexer: make it safe to call destroy multiple times
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

We can easily avoid the burden of checking if the lexer was
initialized prior to calling destroy by the caller, let's do it.

This allows simplification in state tracking with the following patch,
"qmp: add QmpSession" can call qmp_session_destroy() multiple times,
which in turns calls json_lexer_destroy().

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qobject/json-lexer.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/qobject/json-lexer.c b/qobject/json-lexer.c
index 632320d72d..fa7a2c43a8 100644
--- a/qobject/json-lexer.c
+++ b/qobject/json-lexer.c
@@ -361,5 +361,8 @@ void json_lexer_flush(JSONLexer *lexer)
 
 void json_lexer_destroy(JSONLexer *lexer)
 {
-    g_string_free(lexer->token, true);
+    if (lexer->token) {
+        g_string_free(lexer->token, true);
+        lexer->token = NULL;
+    }
 }
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 02/20] json-lexer: make it safe to call destroy multiple times
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

We can easily avoid the burden of checking if the lexer was
initialized prior to calling destroy by the caller, let's do it.

This allows simplification in state tracking with the following patch,
"qmp: add QmpSession" can call qmp_session_destroy() multiple times,
which in turns calls json_lexer_destroy().

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qobject/json-lexer.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/qobject/json-lexer.c b/qobject/json-lexer.c
index 632320d72d..fa7a2c43a8 100644
--- a/qobject/json-lexer.c
+++ b/qobject/json-lexer.c
@@ -361,5 +361,8 @@ void json_lexer_flush(JSONLexer *lexer)
 
 void json_lexer_destroy(JSONLexer *lexer)
 {
-    g_string_free(lexer->token, true);
+    if (lexer->token) {
+        g_string_free(lexer->token, true);
+        lexer->token = NULL;
+    }
 }
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 03/20] qmp: add QmpSession
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

This structure will hold various data related to a QMP client session:
the list of commands, the parser, the callbacks, the pending
operations...

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 10 +++++++++-
 monitor.c                   | 18 ++++++++++--------
 qapi/qmp-dispatch.c         | 15 ++++++++++++---
 qga/main.c                  |  5 ++++-
 tests/test-qmp-cmds.c       | 28 ++++++++++++++++++++++------
 5 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 5a9cf82472..3b53cfd788 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -37,10 +37,18 @@ typedef struct QmpCommand
 
 typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList;
 
+typedef struct QmpSession QmpSession;
+
+struct QmpSession {
+    const QmpCommandList *cmds;
+};
+
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
+void qmp_session_init(QmpSession *session, const QmpCommandList *cmds);
+void qmp_session_destroy(QmpSession *session);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
 
@@ -48,7 +56,7 @@ bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
 QDict *qmp_error_response(Error *err);
-QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
+QDict *qmp_dispatch(QmpSession *session, QObject *request,
                     bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
 
diff --git a/monitor.c b/monitor.c
index abc5bdc4ba..a07175b4f8 100644
--- a/monitor.c
+++ b/monitor.c
@@ -174,7 +174,7 @@ typedef struct {
      * qmp_capabilities succeeds, we go into command mode, and
      * @command becomes &qmp_commands.
      */
-    const QmpCommandList *commands;
+    QmpSession session;
     bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */
     bool capab[QMP_CAPABILITY__MAX];         /* offered and accepted */
     /*
@@ -520,7 +520,7 @@ static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
     trace_monitor_protocol_event_emit(event, qdict);
     QTAILQ_FOREACH(mon, &mon_list, entry) {
         if (monitor_is_qmp(mon)
-            && mon->qmp.commands != &qmp_cap_negotiation_commands) {
+            && mon->qmp.session.cmds != &qmp_cap_negotiation_commands) {
             qmp_send_response(mon, qdict);
         }
     }
@@ -729,6 +729,7 @@ static void monitor_data_destroy(Monitor *mon)
     g_free(mon->mon_cpu_path);
     qemu_chr_fe_deinit(&mon->chr, false);
     if (monitor_is_qmp(mon)) {
+        qmp_session_destroy(&mon->qmp.session);
         json_message_parser_destroy(&mon->qmp.parser);
     }
     readline_free(mon->rs);
@@ -1090,7 +1091,7 @@ CommandInfoList *qmp_query_commands(Error **errp)
 {
     CommandInfoList *list = NULL;
 
-    qmp_for_each_command(cur_mon->qmp.commands, query_commands_cb, &list);
+    qmp_for_each_command(cur_mon->qmp.session.cmds, query_commands_cb, &list);
 
     return list;
 }
@@ -1207,7 +1208,7 @@ static bool qmp_caps_accept(Monitor *mon, QMPCapabilityList *list,
 void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
                           Error **errp)
 {
-    if (cur_mon->qmp.commands == &qmp_commands) {
+    if (cur_mon->qmp.session.cmds == &qmp_commands) {
         error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
                   "Capabilities negotiation is already complete, command "
                   "ignored");
@@ -1218,7 +1219,7 @@ void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
         return;
     }
 
-    cur_mon->qmp.commands = &qmp_commands;
+    cur_mon->qmp.session.cmds = &qmp_commands;
 }
 
 /* Set the current CPU defined by the user. Callers must hold BQL. */
@@ -4121,11 +4122,11 @@ static void monitor_qmp_dispatch(Monitor *mon, QObject *req)
     old_mon = cur_mon;
     cur_mon = mon;
 
-    rsp = qmp_dispatch(mon->qmp.commands, req, qmp_oob_enabled(mon));
+    rsp = qmp_dispatch(&mon->qmp.session, req, qmp_oob_enabled(mon));
 
     cur_mon = old_mon;
 
-    if (mon->qmp.commands == &qmp_cap_negotiation_commands) {
+    if (mon->qmp.session.cmds == &qmp_cap_negotiation_commands) {
         error = qdict_get_qdict(rsp, "error");
         if (error
             && !g_strcmp0(qdict_get_try_str(error, "class"),
@@ -4399,7 +4400,7 @@ static void monitor_qmp_event(void *opaque, int event)
 
     switch (event) {
     case CHR_EVENT_OPENED:
-        mon->qmp.commands = &qmp_cap_negotiation_commands;
+        qmp_session_init(&mon->qmp.session, &qmp_cap_negotiation_commands);
         monitor_qmp_caps_reset(mon);
         data = qmp_greeting(mon);
         qmp_send_response(mon, data);
@@ -4414,6 +4415,7 @@ static void monitor_qmp_event(void *opaque, int event)
          * is closed.
          */
         monitor_qmp_cleanup_queues(mon);
+        qmp_session_destroy(&mon->qmp.session);
         json_message_parser_destroy(&mon->qmp.parser);
         json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
                                  mon, NULL);
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index f9d43046aa..98a82ac33c 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -163,15 +163,24 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
-QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
-                    bool allow_oob)
+void qmp_session_init(QmpSession *session, const QmpCommandList *cmds)
+{
+    session->cmds = cmds;
+}
+
+void qmp_session_destroy(QmpSession *session)
+{
+    session->cmds = NULL;
+}
+
+QDict *qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
 {
     Error *err = NULL;
     QDict *dict = qobject_to(QDict, request);
     QObject *ret, *id = dict ? qdict_get(dict, "id") : NULL;
     QDict *rsp;
 
-    ret = do_qmp_dispatch(cmds, request, allow_oob, &err);
+    ret = do_qmp_dispatch(session->cmds, request, allow_oob, &err);
     if (err) {
         rsp = qmp_error_response(err);
     } else if (ret) {
diff --git a/qga/main.c b/qga/main.c
index 5b144b9250..bf0082df90 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -74,6 +74,7 @@ typedef struct GAPersistentState {
 typedef struct GAConfig GAConfig;
 
 struct GAState {
+    QmpSession session;
     JSONMessageParser parser;
     GMainLoop *main_loop;
     GAChannel *channel;
@@ -572,7 +573,7 @@ static void process_event(void *opaque, QObject *obj, Error *err)
     }
 
     g_debug("processing command");
-    rsp = qmp_dispatch(&ga_commands, obj, false);
+    rsp = qmp_dispatch(&s->session, obj, false);
 
 end:
     ret = send_response(s, rsp);
@@ -1338,6 +1339,7 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
     json_message_parser_init(&s->parser, process_event, s, NULL);
+    qmp_session_init(&s->session, &ga_commands);
 
 #ifndef _WIN32
     if (!register_signal_handlers()) {
@@ -1369,6 +1371,7 @@ static void cleanup_agent(GAState *s)
     CloseHandle(s->wakeup_event);
 #endif
     if (s->command_state) {
+        qmp_session_destroy(&s->session);
         ga_command_state_cleanup_all(s->command_state);
         ga_command_state_free(s->command_state);
         json_message_parser_destroy(&s->parser);
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 630b1b9bac..7c39ec1657 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -113,44 +113,52 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
 /* test commands with no input and no return value */
 static void test_dispatch_cmd(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp != NULL);
     assert(!qdict_haskey(resp, "error"));
 
     qobject_unref(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
 static void test_dispatch_cmd_oob(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "exec-oob", "test-flags-command");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), true);
+    resp = qmp_dispatch(&session, QOBJECT(req), true);
     assert(resp != NULL);
     assert(!qdict_haskey(resp, "error"));
 
     qobject_unref(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
 /* test commands that return an error due to invalid parameters */
 static void test_dispatch_cmd_failure(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *args = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "execute", "user_def_cmd2");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp != NULL);
     assert(qdict_haskey(resp, "error"));
 
@@ -164,36 +172,44 @@ static void test_dispatch_cmd_failure(void)
 
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp != NULL);
     assert(qdict_haskey(resp, "error"));
 
     qobject_unref(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
 static void test_dispatch_cmd_success_response(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "execute", "cmd-success-response");
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     g_assert_null(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
+
 static QObject *test_qmp_dispatch(QDict *req)
 {
+    QmpSession session = { 0, };
     QDict *resp;
     QObject *ret;
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    qmp_session_init(&session, &qmp_commands);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp && !qdict_haskey(resp, "error"));
     ret = qdict_get(resp, "return");
     assert(ret);
     qobject_ref(ret);
     qobject_unref(resp);
+    qmp_session_destroy(&session);
     return ret;
 }
 
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 03/20] qmp: add QmpSession
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

This structure will hold various data related to a QMP client session:
the list of commands, the parser, the callbacks, the pending
operations...

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 10 +++++++++-
 monitor.c                   | 18 ++++++++++--------
 qapi/qmp-dispatch.c         | 15 ++++++++++++---
 qga/main.c                  |  5 ++++-
 tests/test-qmp-cmds.c       | 28 ++++++++++++++++++++++------
 5 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 5a9cf82472..3b53cfd788 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -37,10 +37,18 @@ typedef struct QmpCommand
 
 typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList;
 
+typedef struct QmpSession QmpSession;
+
+struct QmpSession {
+    const QmpCommandList *cmds;
+};
+
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
+void qmp_session_init(QmpSession *session, const QmpCommandList *cmds);
+void qmp_session_destroy(QmpSession *session);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
 
@@ -48,7 +56,7 @@ bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
 QDict *qmp_error_response(Error *err);
-QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
+QDict *qmp_dispatch(QmpSession *session, QObject *request,
                     bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
 
diff --git a/monitor.c b/monitor.c
index abc5bdc4ba..a07175b4f8 100644
--- a/monitor.c
+++ b/monitor.c
@@ -174,7 +174,7 @@ typedef struct {
      * qmp_capabilities succeeds, we go into command mode, and
      * @command becomes &qmp_commands.
      */
-    const QmpCommandList *commands;
+    QmpSession session;
     bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */
     bool capab[QMP_CAPABILITY__MAX];         /* offered and accepted */
     /*
@@ -520,7 +520,7 @@ static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
     trace_monitor_protocol_event_emit(event, qdict);
     QTAILQ_FOREACH(mon, &mon_list, entry) {
         if (monitor_is_qmp(mon)
-            && mon->qmp.commands != &qmp_cap_negotiation_commands) {
+            && mon->qmp.session.cmds != &qmp_cap_negotiation_commands) {
             qmp_send_response(mon, qdict);
         }
     }
@@ -729,6 +729,7 @@ static void monitor_data_destroy(Monitor *mon)
     g_free(mon->mon_cpu_path);
     qemu_chr_fe_deinit(&mon->chr, false);
     if (monitor_is_qmp(mon)) {
+        qmp_session_destroy(&mon->qmp.session);
         json_message_parser_destroy(&mon->qmp.parser);
     }
     readline_free(mon->rs);
@@ -1090,7 +1091,7 @@ CommandInfoList *qmp_query_commands(Error **errp)
 {
     CommandInfoList *list = NULL;
 
-    qmp_for_each_command(cur_mon->qmp.commands, query_commands_cb, &list);
+    qmp_for_each_command(cur_mon->qmp.session.cmds, query_commands_cb, &list);
 
     return list;
 }
@@ -1207,7 +1208,7 @@ static bool qmp_caps_accept(Monitor *mon, QMPCapabilityList *list,
 void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
                           Error **errp)
 {
-    if (cur_mon->qmp.commands == &qmp_commands) {
+    if (cur_mon->qmp.session.cmds == &qmp_commands) {
         error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
                   "Capabilities negotiation is already complete, command "
                   "ignored");
@@ -1218,7 +1219,7 @@ void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
         return;
     }
 
-    cur_mon->qmp.commands = &qmp_commands;
+    cur_mon->qmp.session.cmds = &qmp_commands;
 }
 
 /* Set the current CPU defined by the user. Callers must hold BQL. */
@@ -4121,11 +4122,11 @@ static void monitor_qmp_dispatch(Monitor *mon, QObject *req)
     old_mon = cur_mon;
     cur_mon = mon;
 
-    rsp = qmp_dispatch(mon->qmp.commands, req, qmp_oob_enabled(mon));
+    rsp = qmp_dispatch(&mon->qmp.session, req, qmp_oob_enabled(mon));
 
     cur_mon = old_mon;
 
-    if (mon->qmp.commands == &qmp_cap_negotiation_commands) {
+    if (mon->qmp.session.cmds == &qmp_cap_negotiation_commands) {
         error = qdict_get_qdict(rsp, "error");
         if (error
             && !g_strcmp0(qdict_get_try_str(error, "class"),
@@ -4399,7 +4400,7 @@ static void monitor_qmp_event(void *opaque, int event)
 
     switch (event) {
     case CHR_EVENT_OPENED:
-        mon->qmp.commands = &qmp_cap_negotiation_commands;
+        qmp_session_init(&mon->qmp.session, &qmp_cap_negotiation_commands);
         monitor_qmp_caps_reset(mon);
         data = qmp_greeting(mon);
         qmp_send_response(mon, data);
@@ -4414,6 +4415,7 @@ static void monitor_qmp_event(void *opaque, int event)
          * is closed.
          */
         monitor_qmp_cleanup_queues(mon);
+        qmp_session_destroy(&mon->qmp.session);
         json_message_parser_destroy(&mon->qmp.parser);
         json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
                                  mon, NULL);
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index f9d43046aa..98a82ac33c 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -163,15 +163,24 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
-QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request,
-                    bool allow_oob)
+void qmp_session_init(QmpSession *session, const QmpCommandList *cmds)
+{
+    session->cmds = cmds;
+}
+
+void qmp_session_destroy(QmpSession *session)
+{
+    session->cmds = NULL;
+}
+
+QDict *qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
 {
     Error *err = NULL;
     QDict *dict = qobject_to(QDict, request);
     QObject *ret, *id = dict ? qdict_get(dict, "id") : NULL;
     QDict *rsp;
 
-    ret = do_qmp_dispatch(cmds, request, allow_oob, &err);
+    ret = do_qmp_dispatch(session->cmds, request, allow_oob, &err);
     if (err) {
         rsp = qmp_error_response(err);
     } else if (ret) {
diff --git a/qga/main.c b/qga/main.c
index 5b144b9250..bf0082df90 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -74,6 +74,7 @@ typedef struct GAPersistentState {
 typedef struct GAConfig GAConfig;
 
 struct GAState {
+    QmpSession session;
     JSONMessageParser parser;
     GMainLoop *main_loop;
     GAChannel *channel;
@@ -572,7 +573,7 @@ static void process_event(void *opaque, QObject *obj, Error *err)
     }
 
     g_debug("processing command");
-    rsp = qmp_dispatch(&ga_commands, obj, false);
+    rsp = qmp_dispatch(&s->session, obj, false);
 
 end:
     ret = send_response(s, rsp);
@@ -1338,6 +1339,7 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
     json_message_parser_init(&s->parser, process_event, s, NULL);
+    qmp_session_init(&s->session, &ga_commands);
 
 #ifndef _WIN32
     if (!register_signal_handlers()) {
@@ -1369,6 +1371,7 @@ static void cleanup_agent(GAState *s)
     CloseHandle(s->wakeup_event);
 #endif
     if (s->command_state) {
+        qmp_session_destroy(&s->session);
         ga_command_state_cleanup_all(s->command_state);
         ga_command_state_free(s->command_state);
         json_message_parser_destroy(&s->parser);
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 630b1b9bac..7c39ec1657 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -113,44 +113,52 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
 /* test commands with no input and no return value */
 static void test_dispatch_cmd(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp != NULL);
     assert(!qdict_haskey(resp, "error"));
 
     qobject_unref(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
 static void test_dispatch_cmd_oob(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "exec-oob", "test-flags-command");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), true);
+    resp = qmp_dispatch(&session, QOBJECT(req), true);
     assert(resp != NULL);
     assert(!qdict_haskey(resp, "error"));
 
     qobject_unref(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
 /* test commands that return an error due to invalid parameters */
 static void test_dispatch_cmd_failure(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *args = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "execute", "user_def_cmd2");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp != NULL);
     assert(qdict_haskey(resp, "error"));
 
@@ -164,36 +172,44 @@ static void test_dispatch_cmd_failure(void)
 
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp != NULL);
     assert(qdict_haskey(resp, "error"));
 
     qobject_unref(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
 static void test_dispatch_cmd_success_response(void)
 {
+    QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *resp;
 
+    qmp_session_init(&session, &qmp_commands);
     qdict_put_str(req, "execute", "cmd-success-response");
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     g_assert_null(resp);
     qobject_unref(req);
+    qmp_session_destroy(&session);
 }
 
+
 static QObject *test_qmp_dispatch(QDict *req)
 {
+    QmpSession session = { 0, };
     QDict *resp;
     QObject *ret;
 
-    resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
+    qmp_session_init(&session, &qmp_commands);
+    resp = qmp_dispatch(&session, QOBJECT(req), false);
     assert(resp && !qdict_haskey(resp, "error"));
     ret = qdict_get(resp, "return");
     assert(ret);
     qobject_ref(ret);
     qobject_unref(resp);
+    qmp_session_destroy(&session);
     return ret;
 }
 
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 04/20] QmpSession: add a return callback
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Introduce a return_cb to allow delaying finishing the dispatch
and sending the response asynchronously. For now, this is just
modifying qmp_dispatch() to call the callback synchronously.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 10 ++++--
 monitor.c                   | 47 ++++++++++---------------
 qapi/qmp-dispatch.c         | 22 +++++++++---
 qga/main.c                  | 34 +++++++++++-------
 tests/test-qmp-cmds.c       | 69 ++++++++++++++++++-------------------
 5 files changed, 98 insertions(+), 84 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 3b53cfd788..d1ce631a93 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -38,16 +38,20 @@ typedef struct QmpCommand
 typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList;
 
 typedef struct QmpSession QmpSession;
+typedef void (QmpDispatchReturn) (QmpSession *session, QDict *rsp);
 
 struct QmpSession {
     const QmpCommandList *cmds;
+    QmpDispatchReturn *return_cb;
 };
 
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
-void qmp_session_init(QmpSession *session, const QmpCommandList *cmds);
+void qmp_session_init(QmpSession *session,
+                      const QmpCommandList *cmds,
+                      QmpDispatchReturn *return_cb);
 void qmp_session_destroy(QmpSession *session);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
@@ -56,8 +60,8 @@ bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
 QDict *qmp_error_response(Error *err);
-QDict *qmp_dispatch(QmpSession *session, QObject *request,
-                    bool allow_oob);
+void qmp_dispatch(QmpSession *session, QObject *request,
+                  bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
 
 typedef void (*qmp_cmd_callback_fn)(const QmpCommand *cmd, void *opaque);
diff --git a/monitor.c b/monitor.c
index a07175b4f8..c23ba76f78 100644
--- a/monitor.c
+++ b/monitor.c
@@ -4101,45 +4101,35 @@ static int monitor_can_read(void *opaque)
     return !atomic_mb_read(&mon->suspend_cnt);
 }
 
-/*
- * Emit QMP response @rsp with ID @id to @mon.
- * Null @rsp can only happen for commands with QCO_NO_SUCCESS_RESP.
- * Nothing is emitted then.
- */
-static void monitor_qmp_respond(Monitor *mon, QDict *rsp)
+static void dispatch_return_cb(QmpSession *session, QDict *rsp)
 {
-    if (rsp) {
-        qmp_send_response(mon, rsp);
+    Monitor *mon = container_of(session, Monitor, qmp.session);
+
+    if (mon->qmp.session.cmds == &qmp_cap_negotiation_commands) {
+        QDict *error = qdict_get_qdict(rsp, "error");
+        if (error
+            && !g_strcmp0(qdict_get_try_str(error, "class"),
+                          QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) {
+            /* Provide a more useful error message */
+            qdict_del(error, "desc");
+            qdict_put_str(error, "desc", "Expecting capabilities negotiation"
+                          " with 'qmp_capabilities'");
+        }
     }
+
+    qmp_send_response(mon, rsp);
 }
 
 static void monitor_qmp_dispatch(Monitor *mon, QObject *req)
 {
     Monitor *old_mon;
-    QDict *rsp;
-    QDict *error;
 
     old_mon = cur_mon;
     cur_mon = mon;
 
-    rsp = qmp_dispatch(&mon->qmp.session, req, qmp_oob_enabled(mon));
+    qmp_dispatch(&mon->qmp.session, req, qmp_oob_enabled(mon));
 
     cur_mon = old_mon;
-
-    if (mon->qmp.session.cmds == &qmp_cap_negotiation_commands) {
-        error = qdict_get_qdict(rsp, "error");
-        if (error
-            && !g_strcmp0(qdict_get_try_str(error, "class"),
-                    QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) {
-            /* Provide a more useful error message */
-            qdict_del(error, "desc");
-            qdict_put_str(error, "desc", "Expecting capabilities negotiation"
-                          " with 'qmp_capabilities'");
-        }
-    }
-
-    monitor_qmp_respond(mon, rsp);
-    qobject_unref(rsp);
 }
 
 /*
@@ -4210,7 +4200,7 @@ static void monitor_qmp_bh_dispatcher(void *data)
         assert(req_obj->err);
         rsp = qmp_error_response(req_obj->err);
         req_obj->err = NULL;
-        monitor_qmp_respond(mon, rsp);
+        qmp_send_response(req_obj->mon, rsp);
         qobject_unref(rsp);
     }
 
@@ -4400,7 +4390,8 @@ static void monitor_qmp_event(void *opaque, int event)
 
     switch (event) {
     case CHR_EVENT_OPENED:
-        qmp_session_init(&mon->qmp.session, &qmp_cap_negotiation_commands);
+        qmp_session_init(&mon->qmp.session,
+                         &qmp_cap_negotiation_commands, dispatch_return_cb);
         monitor_qmp_caps_reset(mon);
         data = qmp_greeting(mon);
         qmp_send_response(mon, data);
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 98a82ac33c..37b058cf97 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -163,17 +163,28 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
-void qmp_session_init(QmpSession *session, const QmpCommandList *cmds)
+void qmp_session_init(QmpSession *session,
+                      const QmpCommandList *cmds,
+                      QmpDispatchReturn *return_cb)
 {
+    assert(return_cb);
+    assert(!session->return_cb);
+
     session->cmds = cmds;
+    session->return_cb = return_cb;
 }
 
 void qmp_session_destroy(QmpSession *session)
 {
+    if (!session->return_cb) {
+        return;
+    }
+
     session->cmds = NULL;
+    session->return_cb = NULL;
 }
 
-QDict *qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
+void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
 {
     Error *err = NULL;
     QDict *dict = qobject_to(QDict, request);
@@ -188,12 +199,13 @@ QDict *qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
         qdict_put_obj(rsp, "return", ret);
     } else {
         /* Can only happen for commands with QCO_NO_SUCCESS_RESP */
-        rsp = NULL;
+        return;
     }
 
-    if (rsp && id) {
+    if (id) {
         qdict_put_obj(rsp, "id", qobject_ref(id));
     }
 
-    return rsp;
+    session->return_cb(session, rsp);
+    qobject_unref(rsp);
 }
diff --git a/qga/main.c b/qga/main.c
index bf0082df90..2a929936db 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -558,29 +558,37 @@ static int send_response(GAState *s, const QDict *rsp)
     return 0;
 }
 
+static void dispatch_return_cb(QmpSession *session, QDict *rsp)
+{
+    GAState *s = container_of(session, GAState, session);
+    int ret = send_response(s, rsp);
+    if (ret < 0) {
+        g_warning("error sending response: %s", strerror(-ret));
+    }
+}
+
 /* handle requests/control events coming in over the channel */
 static void process_event(void *opaque, QObject *obj, Error *err)
 {
     GAState *s = opaque;
-    QDict *rsp;
     int ret;
 
     g_debug("process_event: called");
     assert(!obj != !err);
-    if (err) {
-        rsp = qmp_error_response(err);
-        goto end;
-    }
 
-    g_debug("processing command");
-    rsp = qmp_dispatch(&s->session, obj, false);
+    if (err) {
+        QDict *rsp = qmp_error_response(err);
 
-end:
-    ret = send_response(s, rsp);
-    if (ret < 0) {
-        g_warning("error sending error response: %s", strerror(-ret));
+        ret = send_response(s, rsp);
+        if (ret < 0) {
+            g_warning("error sending error response: %s", strerror(-ret));
+        }
+        qobject_unref(rsp);
+    } else {
+        g_debug("processing command");
+        qmp_dispatch(&s->session, obj, false);
     }
-    qobject_unref(rsp);
+
     qobject_unref(obj);
 }
 
@@ -1339,7 +1347,7 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
     json_message_parser_init(&s->parser, process_event, s, NULL);
-    qmp_session_init(&s->session, &ga_commands);
+    qmp_session_init(&s->session, &ga_commands, dispatch_return_cb);
 
 #ifndef _WIN32
     if (!register_signal_handlers()) {
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 7c39ec1657..3f41cc45bb 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -109,22 +109,23 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
     return ret;
 }
 
+static void dispatch_cmd_return(QmpSession *session, QDict *resp)
+{
+    assert(resp != NULL);
+    assert(!qdict_haskey(resp, "error"));
+}
 
 /* test commands with no input and no return value */
 static void test_dispatch_cmd(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp != NULL);
-    assert(!qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), false);
 
-    qobject_unref(resp);
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
@@ -133,82 +134,80 @@ static void test_dispatch_cmd_oob(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
     qdict_put_str(req, "exec-oob", "test-flags-command");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), true);
-    assert(resp != NULL);
-    assert(!qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), true);
 
-    qobject_unref(resp);
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
 
+static void dispatch_cmd_failure_return(QmpSession *session, QDict *resp)
+{
+    assert(resp != NULL);
+    assert(qdict_haskey(resp, "error"));
+}
+
 /* test commands that return an error due to invalid parameters */
 static void test_dispatch_cmd_failure(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *args = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, dispatch_cmd_failure_return);
     qdict_put_str(req, "execute", "user_def_cmd2");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp != NULL);
-    assert(qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), false);
 
-    qobject_unref(resp);
     qobject_unref(req);
 
     /* check that with extra arguments it throws an error */
     req = qdict_new();
     qdict_put_int(args, "a", 66);
     qdict_put(req, "arguments", args);
-
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp != NULL);
-    assert(qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), false);
 
-    qobject_unref(resp);
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
 
+static QObject *dispatch_ret;
+
 static void test_dispatch_cmd_success_response(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, (QmpDispatchReturn *)abort);
     qdict_put_str(req, "execute", "cmd-success-response");
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    g_assert_null(resp);
+
+    qmp_dispatch(&session, QOBJECT(req), false);
+
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
 
+static void dispatch_return(QmpSession *session, QDict *resp)
+{
+    assert(resp && !qdict_haskey(resp, "error"));
+    dispatch_ret = qdict_get(resp, "return");
+    qobject_ref(dispatch_ret);
+}
 
 static QObject *test_qmp_dispatch(QDict *req)
 {
     QmpSession session = { 0, };
-    QDict *resp;
     QObject *ret;
 
-    qmp_session_init(&session, &qmp_commands);
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp && !qdict_haskey(resp, "error"));
-    ret = qdict_get(resp, "return");
-    assert(ret);
-    qobject_ref(ret);
-    qobject_unref(resp);
+    qmp_session_init(&session, &qmp_commands, dispatch_return);
+    qmp_dispatch(&session, QOBJECT(req), false);
+    ret = dispatch_ret;
+    dispatch_ret = NULL;
     qmp_session_destroy(&session);
     return ret;
 }
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 04/20] QmpSession: add a return callback
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Introduce a return_cb to allow delaying finishing the dispatch
and sending the response asynchronously. For now, this is just
modifying qmp_dispatch() to call the callback synchronously.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 10 ++++--
 monitor.c                   | 47 ++++++++++---------------
 qapi/qmp-dispatch.c         | 22 +++++++++---
 qga/main.c                  | 34 +++++++++++-------
 tests/test-qmp-cmds.c       | 69 ++++++++++++++++++-------------------
 5 files changed, 98 insertions(+), 84 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 3b53cfd788..d1ce631a93 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -38,16 +38,20 @@ typedef struct QmpCommand
 typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList;
 
 typedef struct QmpSession QmpSession;
+typedef void (QmpDispatchReturn) (QmpSession *session, QDict *rsp);
 
 struct QmpSession {
     const QmpCommandList *cmds;
+    QmpDispatchReturn *return_cb;
 };
 
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
-void qmp_session_init(QmpSession *session, const QmpCommandList *cmds);
+void qmp_session_init(QmpSession *session,
+                      const QmpCommandList *cmds,
+                      QmpDispatchReturn *return_cb);
 void qmp_session_destroy(QmpSession *session);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
@@ -56,8 +60,8 @@ bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
 QDict *qmp_error_response(Error *err);
-QDict *qmp_dispatch(QmpSession *session, QObject *request,
-                    bool allow_oob);
+void qmp_dispatch(QmpSession *session, QObject *request,
+                  bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
 
 typedef void (*qmp_cmd_callback_fn)(const QmpCommand *cmd, void *opaque);
diff --git a/monitor.c b/monitor.c
index a07175b4f8..c23ba76f78 100644
--- a/monitor.c
+++ b/monitor.c
@@ -4101,45 +4101,35 @@ static int monitor_can_read(void *opaque)
     return !atomic_mb_read(&mon->suspend_cnt);
 }
 
-/*
- * Emit QMP response @rsp with ID @id to @mon.
- * Null @rsp can only happen for commands with QCO_NO_SUCCESS_RESP.
- * Nothing is emitted then.
- */
-static void monitor_qmp_respond(Monitor *mon, QDict *rsp)
+static void dispatch_return_cb(QmpSession *session, QDict *rsp)
 {
-    if (rsp) {
-        qmp_send_response(mon, rsp);
+    Monitor *mon = container_of(session, Monitor, qmp.session);
+
+    if (mon->qmp.session.cmds == &qmp_cap_negotiation_commands) {
+        QDict *error = qdict_get_qdict(rsp, "error");
+        if (error
+            && !g_strcmp0(qdict_get_try_str(error, "class"),
+                          QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) {
+            /* Provide a more useful error message */
+            qdict_del(error, "desc");
+            qdict_put_str(error, "desc", "Expecting capabilities negotiation"
+                          " with 'qmp_capabilities'");
+        }
     }
+
+    qmp_send_response(mon, rsp);
 }
 
 static void monitor_qmp_dispatch(Monitor *mon, QObject *req)
 {
     Monitor *old_mon;
-    QDict *rsp;
-    QDict *error;
 
     old_mon = cur_mon;
     cur_mon = mon;
 
-    rsp = qmp_dispatch(&mon->qmp.session, req, qmp_oob_enabled(mon));
+    qmp_dispatch(&mon->qmp.session, req, qmp_oob_enabled(mon));
 
     cur_mon = old_mon;
-
-    if (mon->qmp.session.cmds == &qmp_cap_negotiation_commands) {
-        error = qdict_get_qdict(rsp, "error");
-        if (error
-            && !g_strcmp0(qdict_get_try_str(error, "class"),
-                    QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) {
-            /* Provide a more useful error message */
-            qdict_del(error, "desc");
-            qdict_put_str(error, "desc", "Expecting capabilities negotiation"
-                          " with 'qmp_capabilities'");
-        }
-    }
-
-    monitor_qmp_respond(mon, rsp);
-    qobject_unref(rsp);
 }
 
 /*
@@ -4210,7 +4200,7 @@ static void monitor_qmp_bh_dispatcher(void *data)
         assert(req_obj->err);
         rsp = qmp_error_response(req_obj->err);
         req_obj->err = NULL;
-        monitor_qmp_respond(mon, rsp);
+        qmp_send_response(req_obj->mon, rsp);
         qobject_unref(rsp);
     }
 
@@ -4400,7 +4390,8 @@ static void monitor_qmp_event(void *opaque, int event)
 
     switch (event) {
     case CHR_EVENT_OPENED:
-        qmp_session_init(&mon->qmp.session, &qmp_cap_negotiation_commands);
+        qmp_session_init(&mon->qmp.session,
+                         &qmp_cap_negotiation_commands, dispatch_return_cb);
         monitor_qmp_caps_reset(mon);
         data = qmp_greeting(mon);
         qmp_send_response(mon, data);
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 98a82ac33c..37b058cf97 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -163,17 +163,28 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
-void qmp_session_init(QmpSession *session, const QmpCommandList *cmds)
+void qmp_session_init(QmpSession *session,
+                      const QmpCommandList *cmds,
+                      QmpDispatchReturn *return_cb)
 {
+    assert(return_cb);
+    assert(!session->return_cb);
+
     session->cmds = cmds;
+    session->return_cb = return_cb;
 }
 
 void qmp_session_destroy(QmpSession *session)
 {
+    if (!session->return_cb) {
+        return;
+    }
+
     session->cmds = NULL;
+    session->return_cb = NULL;
 }
 
-QDict *qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
+void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
 {
     Error *err = NULL;
     QDict *dict = qobject_to(QDict, request);
@@ -188,12 +199,13 @@ QDict *qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
         qdict_put_obj(rsp, "return", ret);
     } else {
         /* Can only happen for commands with QCO_NO_SUCCESS_RESP */
-        rsp = NULL;
+        return;
     }
 
-    if (rsp && id) {
+    if (id) {
         qdict_put_obj(rsp, "id", qobject_ref(id));
     }
 
-    return rsp;
+    session->return_cb(session, rsp);
+    qobject_unref(rsp);
 }
diff --git a/qga/main.c b/qga/main.c
index bf0082df90..2a929936db 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -558,29 +558,37 @@ static int send_response(GAState *s, const QDict *rsp)
     return 0;
 }
 
+static void dispatch_return_cb(QmpSession *session, QDict *rsp)
+{
+    GAState *s = container_of(session, GAState, session);
+    int ret = send_response(s, rsp);
+    if (ret < 0) {
+        g_warning("error sending response: %s", strerror(-ret));
+    }
+}
+
 /* handle requests/control events coming in over the channel */
 static void process_event(void *opaque, QObject *obj, Error *err)
 {
     GAState *s = opaque;
-    QDict *rsp;
     int ret;
 
     g_debug("process_event: called");
     assert(!obj != !err);
-    if (err) {
-        rsp = qmp_error_response(err);
-        goto end;
-    }
 
-    g_debug("processing command");
-    rsp = qmp_dispatch(&s->session, obj, false);
+    if (err) {
+        QDict *rsp = qmp_error_response(err);
 
-end:
-    ret = send_response(s, rsp);
-    if (ret < 0) {
-        g_warning("error sending error response: %s", strerror(-ret));
+        ret = send_response(s, rsp);
+        if (ret < 0) {
+            g_warning("error sending error response: %s", strerror(-ret));
+        }
+        qobject_unref(rsp);
+    } else {
+        g_debug("processing command");
+        qmp_dispatch(&s->session, obj, false);
     }
-    qobject_unref(rsp);
+
     qobject_unref(obj);
 }
 
@@ -1339,7 +1347,7 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
     json_message_parser_init(&s->parser, process_event, s, NULL);
-    qmp_session_init(&s->session, &ga_commands);
+    qmp_session_init(&s->session, &ga_commands, dispatch_return_cb);
 
 #ifndef _WIN32
     if (!register_signal_handlers()) {
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 7c39ec1657..3f41cc45bb 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -109,22 +109,23 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
     return ret;
 }
 
+static void dispatch_cmd_return(QmpSession *session, QDict *resp)
+{
+    assert(resp != NULL);
+    assert(!qdict_haskey(resp, "error"));
+}
 
 /* test commands with no input and no return value */
 static void test_dispatch_cmd(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp != NULL);
-    assert(!qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), false);
 
-    qobject_unref(resp);
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
@@ -133,82 +134,80 @@ static void test_dispatch_cmd_oob(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
     qdict_put_str(req, "exec-oob", "test-flags-command");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), true);
-    assert(resp != NULL);
-    assert(!qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), true);
 
-    qobject_unref(resp);
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
 
+static void dispatch_cmd_failure_return(QmpSession *session, QDict *resp)
+{
+    assert(resp != NULL);
+    assert(qdict_haskey(resp, "error"));
+}
+
 /* test commands that return an error due to invalid parameters */
 static void test_dispatch_cmd_failure(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
     QDict *args = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, dispatch_cmd_failure_return);
     qdict_put_str(req, "execute", "user_def_cmd2");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp != NULL);
-    assert(qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), false);
 
-    qobject_unref(resp);
     qobject_unref(req);
 
     /* check that with extra arguments it throws an error */
     req = qdict_new();
     qdict_put_int(args, "a", 66);
     qdict_put(req, "arguments", args);
-
     qdict_put_str(req, "execute", "user_def_cmd");
 
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp != NULL);
-    assert(qdict_haskey(resp, "error"));
+    qmp_dispatch(&session, QOBJECT(req), false);
 
-    qobject_unref(resp);
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
 
+static QObject *dispatch_ret;
+
 static void test_dispatch_cmd_success_response(void)
 {
     QmpSession session = { 0, };
     QDict *req = qdict_new();
-    QDict *resp;
 
-    qmp_session_init(&session, &qmp_commands);
+    qmp_session_init(&session, &qmp_commands, (QmpDispatchReturn *)abort);
     qdict_put_str(req, "execute", "cmd-success-response");
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    g_assert_null(resp);
+
+    qmp_dispatch(&session, QOBJECT(req), false);
+
     qobject_unref(req);
     qmp_session_destroy(&session);
 }
 
+static void dispatch_return(QmpSession *session, QDict *resp)
+{
+    assert(resp && !qdict_haskey(resp, "error"));
+    dispatch_ret = qdict_get(resp, "return");
+    qobject_ref(dispatch_ret);
+}
 
 static QObject *test_qmp_dispatch(QDict *req)
 {
     QmpSession session = { 0, };
-    QDict *resp;
     QObject *ret;
 
-    qmp_session_init(&session, &qmp_commands);
-    resp = qmp_dispatch(&session, QOBJECT(req), false);
-    assert(resp && !qdict_haskey(resp, "error"));
-    ret = qdict_get(resp, "return");
-    assert(ret);
-    qobject_ref(ret);
-    qobject_unref(resp);
+    qmp_session_init(&session, &qmp_commands, dispatch_return);
+    qmp_dispatch(&session, QOBJECT(req), false);
+    ret = dispatch_ret;
+    dispatch_ret = NULL;
     qmp_session_destroy(&session);
     return ret;
 }
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 05/20] QmpSession: add json parser and use it in qga
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Move JSON parser to QmpSession, and implement a simple handler to check
the parsed tokens and call qmp_dispatch(). This is enough for a simple
QMP client, like QGA.

The QEMU monitor has more complicated handling of dispatching which
will be addressed in a following patch to benefit from more common
code.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  7 +++++++
 qapi/qmp-dispatch.c         | 19 +++++++++++++++++++
 qga/main.c                  | 31 +------------------------------
 3 files changed, 27 insertions(+), 30 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index d1ce631a93..c84edff7d2 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -15,6 +15,7 @@
 #define QAPI_QMP_DISPATCH_H
 
 #include "qemu/queue.h"
+#include "qapi/qmp/json-parser.h"
 
 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);
 
@@ -42,6 +43,7 @@ typedef void (QmpDispatchReturn) (QmpSession *session, QDict *rsp);
 
 struct QmpSession {
     const QmpCommandList *cmds;
+    JSONMessageParser parser;
     QmpDispatchReturn *return_cb;
 };
 
@@ -52,6 +54,11 @@ const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
                       QmpDispatchReturn *return_cb);
+static inline void
+qmp_session_feed(QmpSession *session, const char *buf, size_t count)
+{
+    json_message_parser_feed(&session->parser, buf, count);
+}
 void qmp_session_destroy(QmpSession *session);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 37b058cf97..803ec626cd 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -163,6 +163,23 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
+static void qmp_json_emit(void *opaque, QObject *obj, Error *err)
+{
+    QmpSession *session = opaque;
+
+    assert(!obj != !err);
+
+    if (err) {
+        QDict *rsp = qmp_error_response(err);
+        session->return_cb(session, rsp);
+        qobject_unref(rsp);
+    } else {
+        qmp_dispatch(session, obj, false);
+    }
+
+    qobject_unref(obj);
+}
+
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
                       QmpDispatchReturn *return_cb)
@@ -170,6 +187,7 @@ void qmp_session_init(QmpSession *session,
     assert(return_cb);
     assert(!session->return_cb);
 
+    json_message_parser_init(&session->parser, qmp_json_emit, session, NULL);
     session->cmds = cmds;
     session->return_cb = return_cb;
 }
@@ -182,6 +200,7 @@ void qmp_session_destroy(QmpSession *session)
 
     session->cmds = NULL;
     session->return_cb = NULL;
+    json_message_parser_destroy(&session->parser);
 }
 
 void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
diff --git a/qga/main.c b/qga/main.c
index 2a929936db..9cd27fc4b8 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -18,7 +18,6 @@
 #include <syslog.h>
 #include <sys/wait.h>
 #endif
-#include "qapi/qmp/json-parser.h"
 #include "qapi/qmp/qdict.h"
 #include "qapi/qmp/qjson.h"
 #include "qapi/qmp/qstring.h"
@@ -75,7 +74,6 @@ typedef struct GAConfig GAConfig;
 
 struct GAState {
     QmpSession session;
-    JSONMessageParser parser;
     GMainLoop *main_loop;
     GAChannel *channel;
     bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
@@ -567,31 +565,6 @@ static void dispatch_return_cb(QmpSession *session, QDict *rsp)
     }
 }
 
-/* handle requests/control events coming in over the channel */
-static void process_event(void *opaque, QObject *obj, Error *err)
-{
-    GAState *s = opaque;
-    int ret;
-
-    g_debug("process_event: called");
-    assert(!obj != !err);
-
-    if (err) {
-        QDict *rsp = qmp_error_response(err);
-
-        ret = send_response(s, rsp);
-        if (ret < 0) {
-            g_warning("error sending error response: %s", strerror(-ret));
-        }
-        qobject_unref(rsp);
-    } else {
-        g_debug("processing command");
-        qmp_dispatch(&s->session, obj, false);
-    }
-
-    qobject_unref(obj);
-}
-
 /* false return signals GAChannel to close the current client connection */
 static gboolean channel_event_cb(GIOCondition condition, gpointer data)
 {
@@ -607,7 +580,7 @@ static gboolean channel_event_cb(GIOCondition condition, gpointer data)
     case G_IO_STATUS_NORMAL:
         buf[count] = 0;
         g_debug("read data, count: %d, data: %s", (int)count, buf);
-        json_message_parser_feed(&s->parser, (char *)buf, (int)count);
+        qmp_session_feed(&s->session, (char *)buf, (int)count);
         break;
     case G_IO_STATUS_EOF:
         g_debug("received EOF");
@@ -1346,7 +1319,6 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     s->command_state = ga_command_state_new();
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
-    json_message_parser_init(&s->parser, process_event, s, NULL);
     qmp_session_init(&s->session, &ga_commands, dispatch_return_cb);
 
 #ifndef _WIN32
@@ -1382,7 +1354,6 @@ static void cleanup_agent(GAState *s)
         qmp_session_destroy(&s->session);
         ga_command_state_cleanup_all(s->command_state);
         ga_command_state_free(s->command_state);
-        json_message_parser_destroy(&s->parser);
     }
     g_free(s->pstate_filepath);
     g_free(s->state_filepath_isfrozen);
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 05/20] QmpSession: add json parser and use it in qga
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Move JSON parser to QmpSession, and implement a simple handler to check
the parsed tokens and call qmp_dispatch(). This is enough for a simple
QMP client, like QGA.

The QEMU monitor has more complicated handling of dispatching which
will be addressed in a following patch to benefit from more common
code.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  7 +++++++
 qapi/qmp-dispatch.c         | 19 +++++++++++++++++++
 qga/main.c                  | 31 +------------------------------
 3 files changed, 27 insertions(+), 30 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index d1ce631a93..c84edff7d2 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -15,6 +15,7 @@
 #define QAPI_QMP_DISPATCH_H
 
 #include "qemu/queue.h"
+#include "qapi/qmp/json-parser.h"
 
 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);
 
@@ -42,6 +43,7 @@ typedef void (QmpDispatchReturn) (QmpSession *session, QDict *rsp);
 
 struct QmpSession {
     const QmpCommandList *cmds;
+    JSONMessageParser parser;
     QmpDispatchReturn *return_cb;
 };
 
@@ -52,6 +54,11 @@ const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
                       QmpDispatchReturn *return_cb);
+static inline void
+qmp_session_feed(QmpSession *session, const char *buf, size_t count)
+{
+    json_message_parser_feed(&session->parser, buf, count);
+}
 void qmp_session_destroy(QmpSession *session);
 void qmp_disable_command(QmpCommandList *cmds, const char *name);
 void qmp_enable_command(QmpCommandList *cmds, const char *name);
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 37b058cf97..803ec626cd 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -163,6 +163,23 @@ bool qmp_is_oob(const QDict *dict)
         && !qdict_haskey(dict, "execute");
 }
 
+static void qmp_json_emit(void *opaque, QObject *obj, Error *err)
+{
+    QmpSession *session = opaque;
+
+    assert(!obj != !err);
+
+    if (err) {
+        QDict *rsp = qmp_error_response(err);
+        session->return_cb(session, rsp);
+        qobject_unref(rsp);
+    } else {
+        qmp_dispatch(session, obj, false);
+    }
+
+    qobject_unref(obj);
+}
+
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
                       QmpDispatchReturn *return_cb)
@@ -170,6 +187,7 @@ void qmp_session_init(QmpSession *session,
     assert(return_cb);
     assert(!session->return_cb);
 
+    json_message_parser_init(&session->parser, qmp_json_emit, session, NULL);
     session->cmds = cmds;
     session->return_cb = return_cb;
 }
@@ -182,6 +200,7 @@ void qmp_session_destroy(QmpSession *session)
 
     session->cmds = NULL;
     session->return_cb = NULL;
+    json_message_parser_destroy(&session->parser);
 }
 
 void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
diff --git a/qga/main.c b/qga/main.c
index 2a929936db..9cd27fc4b8 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -18,7 +18,6 @@
 #include <syslog.h>
 #include <sys/wait.h>
 #endif
-#include "qapi/qmp/json-parser.h"
 #include "qapi/qmp/qdict.h"
 #include "qapi/qmp/qjson.h"
 #include "qapi/qmp/qstring.h"
@@ -75,7 +74,6 @@ typedef struct GAConfig GAConfig;
 
 struct GAState {
     QmpSession session;
-    JSONMessageParser parser;
     GMainLoop *main_loop;
     GAChannel *channel;
     bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
@@ -567,31 +565,6 @@ static void dispatch_return_cb(QmpSession *session, QDict *rsp)
     }
 }
 
-/* handle requests/control events coming in over the channel */
-static void process_event(void *opaque, QObject *obj, Error *err)
-{
-    GAState *s = opaque;
-    int ret;
-
-    g_debug("process_event: called");
-    assert(!obj != !err);
-
-    if (err) {
-        QDict *rsp = qmp_error_response(err);
-
-        ret = send_response(s, rsp);
-        if (ret < 0) {
-            g_warning("error sending error response: %s", strerror(-ret));
-        }
-        qobject_unref(rsp);
-    } else {
-        g_debug("processing command");
-        qmp_dispatch(&s->session, obj, false);
-    }
-
-    qobject_unref(obj);
-}
-
 /* false return signals GAChannel to close the current client connection */
 static gboolean channel_event_cb(GIOCondition condition, gpointer data)
 {
@@ -607,7 +580,7 @@ static gboolean channel_event_cb(GIOCondition condition, gpointer data)
     case G_IO_STATUS_NORMAL:
         buf[count] = 0;
         g_debug("read data, count: %d, data: %s", (int)count, buf);
-        json_message_parser_feed(&s->parser, (char *)buf, (int)count);
+        qmp_session_feed(&s->session, (char *)buf, (int)count);
         break;
     case G_IO_STATUS_EOF:
         g_debug("received EOF");
@@ -1346,7 +1319,6 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     s->command_state = ga_command_state_new();
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
-    json_message_parser_init(&s->parser, process_event, s, NULL);
     qmp_session_init(&s->session, &ga_commands, dispatch_return_cb);
 
 #ifndef _WIN32
@@ -1382,7 +1354,6 @@ static void cleanup_agent(GAState *s)
         qmp_session_destroy(&s->session);
         ga_command_state_cleanup_all(s->command_state);
         ga_command_state_free(s->command_state);
-        json_message_parser_destroy(&s->parser);
     }
     g_free(s->pstate_filepath);
     g_free(s->state_filepath_isfrozen);
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 06/20] monitor: use qmp session to parse json feed
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Use the QmpSession json parser introduced in previous patch to
generalize the handling in both qemu & qemu-ga. Unfortunately, since
the introduction of OOB, it's not as common as it was before that. We
may want to move some of OOB logic in common qmp-dispatch.c/QmpSession
though.

The QEMU monitor has peculiar handling of the stream of commands, for
OOB command processing, which can be solved by overriding the json
emit callback.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h    |  1 +
 include/qapi/qmp/json-parser.h |  7 ++++---
 monitor.c                      | 16 +++++-----------
 qapi/qmp-dispatch.c            |  4 +++-
 qga/main.c                     |  2 +-
 qobject/json-streamer.c        |  3 +--
 tests/test-qmp-cmds.c          | 11 ++++++-----
 7 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index c84edff7d2..b3ca6c9ff2 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -53,6 +53,7 @@ const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
+                      JSONMessageEmit *emit,
                       QmpDispatchReturn *return_cb);
 static inline void
 qmp_session_feed(QmpSession *session, const char *buf, size_t count)
diff --git a/include/qapi/qmp/json-parser.h b/include/qapi/qmp/json-parser.h
index 7345a9bd5c..6f168e8007 100644
--- a/include/qapi/qmp/json-parser.h
+++ b/include/qapi/qmp/json-parser.h
@@ -14,6 +14,8 @@
 #ifndef QAPI_QMP_JSON_PARSER_H
 #define QAPI_QMP_JSON_PARSER_H
 
+typedef void (JSONMessageEmit)(void *opaque, QObject *json, Error *err);
+
 typedef struct JSONLexer {
     int start_state, state;
     GString *token;
@@ -21,7 +23,7 @@ typedef struct JSONLexer {
 } JSONLexer;
 
 typedef struct JSONMessageParser {
-    void (*emit)(void *opaque, QObject *json, Error *err);
+    JSONMessageEmit *emit;
     void *opaque;
     va_list *ap;
     JSONLexer lexer;
@@ -32,8 +34,7 @@ typedef struct JSONMessageParser {
 } JSONMessageParser;
 
 void json_message_parser_init(JSONMessageParser *parser,
-                              void (*emit)(void *opaque, QObject *json,
-                                           Error *err),
+                              JSONMessageEmit *emit,
                               void *opaque, va_list *ap);
 
 void json_message_parser_feed(JSONMessageParser *parser,
diff --git a/monitor.c b/monitor.c
index c23ba76f78..71aad8d1ae 100644
--- a/monitor.c
+++ b/monitor.c
@@ -59,7 +59,6 @@
 #include "qapi/qmp/qnum.h"
 #include "qapi/qmp/qstring.h"
 #include "qapi/qmp/qjson.h"
-#include "qapi/qmp/json-parser.h"
 #include "qapi/qmp/qlist.h"
 #include "qom/object_interfaces.h"
 #include "trace-root.h"
@@ -167,7 +166,6 @@ struct MonFdset {
 };
 
 typedef struct {
-    JSONMessageParser parser;
     /*
      * When a client connects, we're in capabilities negotiation mode.
      * @commands is &qmp_cap_negotiation_commands then.  When command
@@ -730,7 +728,6 @@ static void monitor_data_destroy(Monitor *mon)
     qemu_chr_fe_deinit(&mon->chr, false);
     if (monitor_is_qmp(mon)) {
         qmp_session_destroy(&mon->qmp.session);
-        json_message_parser_destroy(&mon->qmp.parser);
     }
     readline_free(mon->rs);
     qobject_unref(mon->outbuf);
@@ -4216,7 +4213,7 @@ static void monitor_qmp_bh_dispatcher(void *data)
 
 static void handle_qmp_command(void *opaque, QObject *req, Error *err)
 {
-    Monitor *mon = opaque;
+    Monitor *mon = container_of(opaque, Monitor, qmp);
     QObject *id = NULL;
     QDict *qdict;
     QMPRequest *req_obj;
@@ -4278,7 +4275,7 @@ static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size)
 {
     Monitor *mon = opaque;
 
-    json_message_parser_feed(&mon->qmp.parser, (const char *) buf, size);
+    qmp_session_feed(&mon->qmp.session, (const char *) buf, size);
 }
 
 static void monitor_read(void *opaque, const uint8_t *buf, int size)
@@ -4391,7 +4388,9 @@ static void monitor_qmp_event(void *opaque, int event)
     switch (event) {
     case CHR_EVENT_OPENED:
         qmp_session_init(&mon->qmp.session,
-                         &qmp_cap_negotiation_commands, dispatch_return_cb);
+                         &qmp_cap_negotiation_commands,
+                         handle_qmp_command,
+                         dispatch_return_cb);
         monitor_qmp_caps_reset(mon);
         data = qmp_greeting(mon);
         qmp_send_response(mon, data);
@@ -4407,9 +4406,6 @@ static void monitor_qmp_event(void *opaque, int event)
          */
         monitor_qmp_cleanup_queues(mon);
         qmp_session_destroy(&mon->qmp.session);
-        json_message_parser_destroy(&mon->qmp.parser);
-        json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
-                                 mon, NULL);
         mon_refcount--;
         monitor_fdsets_cleanup();
         break;
@@ -4615,8 +4611,6 @@ void monitor_init(Chardev *chr, int flags)
 
     if (monitor_is_qmp(mon)) {
         qemu_chr_fe_set_echo(&mon->chr, true);
-        json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
-                                 mon, NULL);
         if (mon->use_io_thread) {
             /*
              * Make sure the old iowatch is gone.  It's possible when
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 803ec626cd..f2c376d005 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -182,12 +182,14 @@ static void qmp_json_emit(void *opaque, QObject *obj, Error *err)
 
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
+                      JSONMessageEmit *emit,
                       QmpDispatchReturn *return_cb)
 {
     assert(return_cb);
     assert(!session->return_cb);
 
-    json_message_parser_init(&session->parser, qmp_json_emit, session, NULL);
+    json_message_parser_init(&session->parser, emit ?: qmp_json_emit,
+                             session, NULL);
     session->cmds = cmds;
     session->return_cb = return_cb;
 }
diff --git a/qga/main.c b/qga/main.c
index 9cd27fc4b8..14e418f9a5 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -1319,7 +1319,7 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     s->command_state = ga_command_state_new();
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
-    qmp_session_init(&s->session, &ga_commands, dispatch_return_cb);
+    qmp_session_init(&s->session, &ga_commands, NULL, dispatch_return_cb);
 
 #ifndef _WIN32
     if (!register_signal_handlers()) {
diff --git a/qobject/json-streamer.c b/qobject/json-streamer.c
index 47dd7ea576..2a440f2a9e 100644
--- a/qobject/json-streamer.c
+++ b/qobject/json-streamer.c
@@ -100,8 +100,7 @@ out_emit:
 }
 
 void json_message_parser_init(JSONMessageParser *parser,
-                              void (*emit)(void *opaque, QObject *json,
-                                           Error *err),
+                              JSONMessageEmit *emit,
                               void *opaque, va_list *ap)
 {
     parser->emit = emit;
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 3f41cc45bb..b4d0b0440a 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -121,7 +121,7 @@ static void test_dispatch_cmd(void)
     QmpSession session = { 0, };
     QDict *req = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
+    qmp_session_init(&session, &qmp_commands, NULL, dispatch_cmd_return);
     qdict_put_str(req, "execute", "user_def_cmd");
 
     qmp_dispatch(&session, QOBJECT(req), false);
@@ -135,7 +135,7 @@ static void test_dispatch_cmd_oob(void)
     QmpSession session = { 0, };
     QDict *req = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
+    qmp_session_init(&session, &qmp_commands, NULL, dispatch_cmd_return);
     qdict_put_str(req, "exec-oob", "test-flags-command");
 
     qmp_dispatch(&session, QOBJECT(req), true);
@@ -157,7 +157,8 @@ static void test_dispatch_cmd_failure(void)
     QDict *req = qdict_new();
     QDict *args = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, dispatch_cmd_failure_return);
+    qmp_session_init(&session, &qmp_commands, NULL,
+                     dispatch_cmd_failure_return);
     qdict_put_str(req, "execute", "user_def_cmd2");
 
     qmp_dispatch(&session, QOBJECT(req), false);
@@ -183,7 +184,7 @@ static void test_dispatch_cmd_success_response(void)
     QmpSession session = { 0, };
     QDict *req = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, (QmpDispatchReturn *)abort);
+    qmp_session_init(&session, &qmp_commands, NULL, (QmpDispatchReturn *)abort);
     qdict_put_str(req, "execute", "cmd-success-response");
 
     qmp_dispatch(&session, QOBJECT(req), false);
@@ -204,7 +205,7 @@ static QObject *test_qmp_dispatch(QDict *req)
     QmpSession session = { 0, };
     QObject *ret;
 
-    qmp_session_init(&session, &qmp_commands, dispatch_return);
+    qmp_session_init(&session, &qmp_commands, NULL, dispatch_return);
     qmp_dispatch(&session, QOBJECT(req), false);
     ret = dispatch_ret;
     dispatch_ret = NULL;
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 06/20] monitor: use qmp session to parse json feed
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Use the QmpSession json parser introduced in previous patch to
generalize the handling in both qemu & qemu-ga. Unfortunately, since
the introduction of OOB, it's not as common as it was before that. We
may want to move some of OOB logic in common qmp-dispatch.c/QmpSession
though.

The QEMU monitor has peculiar handling of the stream of commands, for
OOB command processing, which can be solved by overriding the json
emit callback.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h    |  1 +
 include/qapi/qmp/json-parser.h |  7 ++++---
 monitor.c                      | 16 +++++-----------
 qapi/qmp-dispatch.c            |  4 +++-
 qga/main.c                     |  2 +-
 qobject/json-streamer.c        |  3 +--
 tests/test-qmp-cmds.c          | 11 ++++++-----
 7 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index c84edff7d2..b3ca6c9ff2 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -53,6 +53,7 @@ const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
+                      JSONMessageEmit *emit,
                       QmpDispatchReturn *return_cb);
 static inline void
 qmp_session_feed(QmpSession *session, const char *buf, size_t count)
diff --git a/include/qapi/qmp/json-parser.h b/include/qapi/qmp/json-parser.h
index 7345a9bd5c..6f168e8007 100644
--- a/include/qapi/qmp/json-parser.h
+++ b/include/qapi/qmp/json-parser.h
@@ -14,6 +14,8 @@
 #ifndef QAPI_QMP_JSON_PARSER_H
 #define QAPI_QMP_JSON_PARSER_H
 
+typedef void (JSONMessageEmit)(void *opaque, QObject *json, Error *err);
+
 typedef struct JSONLexer {
     int start_state, state;
     GString *token;
@@ -21,7 +23,7 @@ typedef struct JSONLexer {
 } JSONLexer;
 
 typedef struct JSONMessageParser {
-    void (*emit)(void *opaque, QObject *json, Error *err);
+    JSONMessageEmit *emit;
     void *opaque;
     va_list *ap;
     JSONLexer lexer;
@@ -32,8 +34,7 @@ typedef struct JSONMessageParser {
 } JSONMessageParser;
 
 void json_message_parser_init(JSONMessageParser *parser,
-                              void (*emit)(void *opaque, QObject *json,
-                                           Error *err),
+                              JSONMessageEmit *emit,
                               void *opaque, va_list *ap);
 
 void json_message_parser_feed(JSONMessageParser *parser,
diff --git a/monitor.c b/monitor.c
index c23ba76f78..71aad8d1ae 100644
--- a/monitor.c
+++ b/monitor.c
@@ -59,7 +59,6 @@
 #include "qapi/qmp/qnum.h"
 #include "qapi/qmp/qstring.h"
 #include "qapi/qmp/qjson.h"
-#include "qapi/qmp/json-parser.h"
 #include "qapi/qmp/qlist.h"
 #include "qom/object_interfaces.h"
 #include "trace-root.h"
@@ -167,7 +166,6 @@ struct MonFdset {
 };
 
 typedef struct {
-    JSONMessageParser parser;
     /*
      * When a client connects, we're in capabilities negotiation mode.
      * @commands is &qmp_cap_negotiation_commands then.  When command
@@ -730,7 +728,6 @@ static void monitor_data_destroy(Monitor *mon)
     qemu_chr_fe_deinit(&mon->chr, false);
     if (monitor_is_qmp(mon)) {
         qmp_session_destroy(&mon->qmp.session);
-        json_message_parser_destroy(&mon->qmp.parser);
     }
     readline_free(mon->rs);
     qobject_unref(mon->outbuf);
@@ -4216,7 +4213,7 @@ static void monitor_qmp_bh_dispatcher(void *data)
 
 static void handle_qmp_command(void *opaque, QObject *req, Error *err)
 {
-    Monitor *mon = opaque;
+    Monitor *mon = container_of(opaque, Monitor, qmp);
     QObject *id = NULL;
     QDict *qdict;
     QMPRequest *req_obj;
@@ -4278,7 +4275,7 @@ static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size)
 {
     Monitor *mon = opaque;
 
-    json_message_parser_feed(&mon->qmp.parser, (const char *) buf, size);
+    qmp_session_feed(&mon->qmp.session, (const char *) buf, size);
 }
 
 static void monitor_read(void *opaque, const uint8_t *buf, int size)
@@ -4391,7 +4388,9 @@ static void monitor_qmp_event(void *opaque, int event)
     switch (event) {
     case CHR_EVENT_OPENED:
         qmp_session_init(&mon->qmp.session,
-                         &qmp_cap_negotiation_commands, dispatch_return_cb);
+                         &qmp_cap_negotiation_commands,
+                         handle_qmp_command,
+                         dispatch_return_cb);
         monitor_qmp_caps_reset(mon);
         data = qmp_greeting(mon);
         qmp_send_response(mon, data);
@@ -4407,9 +4406,6 @@ static void monitor_qmp_event(void *opaque, int event)
          */
         monitor_qmp_cleanup_queues(mon);
         qmp_session_destroy(&mon->qmp.session);
-        json_message_parser_destroy(&mon->qmp.parser);
-        json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
-                                 mon, NULL);
         mon_refcount--;
         monitor_fdsets_cleanup();
         break;
@@ -4615,8 +4611,6 @@ void monitor_init(Chardev *chr, int flags)
 
     if (monitor_is_qmp(mon)) {
         qemu_chr_fe_set_echo(&mon->chr, true);
-        json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
-                                 mon, NULL);
         if (mon->use_io_thread) {
             /*
              * Make sure the old iowatch is gone.  It's possible when
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 803ec626cd..f2c376d005 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -182,12 +182,14 @@ static void qmp_json_emit(void *opaque, QObject *obj, Error *err)
 
 void qmp_session_init(QmpSession *session,
                       const QmpCommandList *cmds,
+                      JSONMessageEmit *emit,
                       QmpDispatchReturn *return_cb)
 {
     assert(return_cb);
     assert(!session->return_cb);
 
-    json_message_parser_init(&session->parser, qmp_json_emit, session, NULL);
+    json_message_parser_init(&session->parser, emit ?: qmp_json_emit,
+                             session, NULL);
     session->cmds = cmds;
     session->return_cb = return_cb;
 }
diff --git a/qga/main.c b/qga/main.c
index 9cd27fc4b8..14e418f9a5 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -1319,7 +1319,7 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
     s->command_state = ga_command_state_new();
     ga_command_state_init(s, s->command_state);
     ga_command_state_init_all(s->command_state);
-    qmp_session_init(&s->session, &ga_commands, dispatch_return_cb);
+    qmp_session_init(&s->session, &ga_commands, NULL, dispatch_return_cb);
 
 #ifndef _WIN32
     if (!register_signal_handlers()) {
diff --git a/qobject/json-streamer.c b/qobject/json-streamer.c
index 47dd7ea576..2a440f2a9e 100644
--- a/qobject/json-streamer.c
+++ b/qobject/json-streamer.c
@@ -100,8 +100,7 @@ out_emit:
 }
 
 void json_message_parser_init(JSONMessageParser *parser,
-                              void (*emit)(void *opaque, QObject *json,
-                                           Error *err),
+                              JSONMessageEmit *emit,
                               void *opaque, va_list *ap)
 {
     parser->emit = emit;
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 3f41cc45bb..b4d0b0440a 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -121,7 +121,7 @@ static void test_dispatch_cmd(void)
     QmpSession session = { 0, };
     QDict *req = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
+    qmp_session_init(&session, &qmp_commands, NULL, dispatch_cmd_return);
     qdict_put_str(req, "execute", "user_def_cmd");
 
     qmp_dispatch(&session, QOBJECT(req), false);
@@ -135,7 +135,7 @@ static void test_dispatch_cmd_oob(void)
     QmpSession session = { 0, };
     QDict *req = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, dispatch_cmd_return);
+    qmp_session_init(&session, &qmp_commands, NULL, dispatch_cmd_return);
     qdict_put_str(req, "exec-oob", "test-flags-command");
 
     qmp_dispatch(&session, QOBJECT(req), true);
@@ -157,7 +157,8 @@ static void test_dispatch_cmd_failure(void)
     QDict *req = qdict_new();
     QDict *args = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, dispatch_cmd_failure_return);
+    qmp_session_init(&session, &qmp_commands, NULL,
+                     dispatch_cmd_failure_return);
     qdict_put_str(req, "execute", "user_def_cmd2");
 
     qmp_dispatch(&session, QOBJECT(req), false);
@@ -183,7 +184,7 @@ static void test_dispatch_cmd_success_response(void)
     QmpSession session = { 0, };
     QDict *req = qdict_new();
 
-    qmp_session_init(&session, &qmp_commands, (QmpDispatchReturn *)abort);
+    qmp_session_init(&session, &qmp_commands, NULL, (QmpDispatchReturn *)abort);
     qdict_put_str(req, "execute", "cmd-success-response");
 
     qmp_dispatch(&session, QOBJECT(req), false);
@@ -204,7 +205,7 @@ static QObject *test_qmp_dispatch(QDict *req)
     QmpSession session = { 0, };
     QObject *ret;
 
-    qmp_session_init(&session, &qmp_commands, dispatch_return);
+    qmp_session_init(&session, &qmp_commands, NULL, dispatch_return);
     qmp_dispatch(&session, QOBJECT(req), false);
     ret = dispatch_ret;
     dispatch_ret = NULL;
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 07/20] qga: simplify dispatch_return_cb
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Fold send_response().

qobject_to_json() can't return NULL (it will crash if allocation
failed, either in memcpy() or abort from g_realloc()).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qga/main.c | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/qga/main.c b/qga/main.c
index 14e418f9a5..51ab418128 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -522,8 +522,9 @@ fail:
 #endif
 }
 
-static int send_response(GAState *s, const QDict *rsp)
+static void dispatch_return_cb(QmpSession *session, QDict *rsp)
 {
+    GAState *s = container_of(session, GAState, session);
     const char *buf;
     QString *payload_qstr, *response_qstr;
     GIOStatus status;
@@ -531,9 +532,6 @@ static int send_response(GAState *s, const QDict *rsp)
     g_assert(rsp && s->channel);
 
     payload_qstr = qobject_to_json(QOBJECT(rsp));
-    if (!payload_qstr) {
-        return -EINVAL;
-    }
 
     if (s->delimit_response) {
         s->delimit_response = false;
@@ -550,18 +548,7 @@ static int send_response(GAState *s, const QDict *rsp)
     status = ga_channel_write_all(s->channel, buf, strlen(buf));
     qobject_unref(response_qstr);
     if (status != G_IO_STATUS_NORMAL) {
-        return -EIO;
-    }
-
-    return 0;
-}
-
-static void dispatch_return_cb(QmpSession *session, QDict *rsp)
-{
-    GAState *s = container_of(session, GAState, session);
-    int ret = send_response(s, rsp);
-    if (ret < 0) {
-        g_warning("error sending response: %s", strerror(-ret));
+        g_warning("Failed sending response");
     }
 }
 
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 07/20] qga: simplify dispatch_return_cb
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Fold send_response().

qobject_to_json() can't return NULL (it will crash if allocation
failed, either in memcpy() or abort from g_realloc()).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qga/main.c | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/qga/main.c b/qga/main.c
index 14e418f9a5..51ab418128 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -522,8 +522,9 @@ fail:
 #endif
 }
 
-static int send_response(GAState *s, const QDict *rsp)
+static void dispatch_return_cb(QmpSession *session, QDict *rsp)
 {
+    GAState *s = container_of(session, GAState, session);
     const char *buf;
     QString *payload_qstr, *response_qstr;
     GIOStatus status;
@@ -531,9 +532,6 @@ static int send_response(GAState *s, const QDict *rsp)
     g_assert(rsp && s->channel);
 
     payload_qstr = qobject_to_json(QOBJECT(rsp));
-    if (!payload_qstr) {
-        return -EINVAL;
-    }
 
     if (s->delimit_response) {
         s->delimit_response = false;
@@ -550,18 +548,7 @@ static int send_response(GAState *s, const QDict *rsp)
     status = ga_channel_write_all(s->channel, buf, strlen(buf));
     qobject_unref(response_qstr);
     if (status != G_IO_STATUS_NORMAL) {
-        return -EIO;
-    }
-
-    return 0;
-}
-
-static void dispatch_return_cb(QmpSession *session, QDict *rsp)
-{
-    GAState *s = container_of(session, GAState, session);
-    int ret = send_response(s, rsp);
-    if (ret < 0) {
-        g_warning("error sending response: %s", strerror(-ret));
+        g_warning("Failed sending response");
     }
 }
 
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 08/20] QmpSession: introduce QmpReturn
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

QmpReturn (and associated functions) is used during synchronous
dispatch return for now. It helps to factor out some code for
handling a reply context.

In the following patches, the QmpReturn will be the basis upon which
asynchronous reply will be handled: it will hold the context for a QMP
command reply.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 34 ++++++++++++++++-
 monitor.c                   |  6 +--
 qapi/qmp-dispatch.c         | 74 ++++++++++++++++++++++---------------
 3 files changed, 79 insertions(+), 35 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index b3ca6c9ff2..6c0d21968e 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -17,6 +17,8 @@
 #include "qemu/queue.h"
 #include "qapi/qmp/json-parser.h"
 
+typedef struct QmpReturn QmpReturn;
+
 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);
 
 typedef enum QmpCommandOptions
@@ -47,6 +49,37 @@ struct QmpSession {
     QmpDispatchReturn *return_cb;
 };
 
+struct QmpReturn {
+    QmpSession *session;
+    QDict *rsp;
+};
+
+/**
+ * qmp_return_new:
+ *
+ * Allocates and initializes a QmpReturn.
+ */
+QmpReturn *qmp_return_new(QmpSession *session, const QObject *req);
+
+/**
+ * qmp_return_free:
+ *
+ * Free a QmpReturn. This shouldn't be needed if you actually return
+ * with qmp_return{_error}.
+ */
+void qmp_return_free(QmpReturn *qret);
+
+/**
+ * qmp_return{_error}:
+ *
+ * Construct the command reply, and call the
+ * return_cb() associated with the session.
+ *
+ * Finally, free the QmpReturn.
+ */
+void qmp_return(QmpReturn *qret, QObject *rsp);
+void qmp_return_error(QmpReturn *qret, Error *err);
+
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
@@ -67,7 +100,6 @@ void qmp_enable_command(QmpCommandList *cmds, const char *name);
 bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
-QDict *qmp_error_response(Error *err);
 void qmp_dispatch(QmpSession *session, QObject *request,
                   bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
diff --git a/monitor.c b/monitor.c
index 71aad8d1ae..b1c4647a37 100644
--- a/monitor.c
+++ b/monitor.c
@@ -4175,7 +4175,6 @@ static QMPRequest *monitor_qmp_requests_pop_any_with_lock(void)
 static void monitor_qmp_bh_dispatcher(void *data)
 {
     QMPRequest *req_obj = monitor_qmp_requests_pop_any_with_lock();
-    QDict *rsp;
     bool need_resume;
     Monitor *mon;
 
@@ -4194,11 +4193,10 @@ static void monitor_qmp_bh_dispatcher(void *data)
         trace_monitor_qmp_cmd_in_band(qobject_get_try_str(id) ?: "");
         monitor_qmp_dispatch(mon, req_obj->req);
     } else {
+        QmpSession *session = &req_obj->mon->qmp.session;
         assert(req_obj->err);
-        rsp = qmp_error_response(req_obj->err);
+        qmp_return_error(qmp_return_new(session, req_obj->req), req_obj->err);
         req_obj->err = NULL;
-        qmp_send_response(req_obj->mon, rsp);
-        qobject_unref(rsp);
     }
 
     if (need_resume) {
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index f2c376d005..405cb291b1 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -19,6 +19,46 @@
 #include "qapi/qmp/qbool.h"
 #include "sysemu/sysemu.h"
 
+QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
+{
+    QmpReturn *qret = g_new0(QmpReturn, 1);
+    const QDict *req = qobject_to(QDict, request);
+    QObject *id = req ? qdict_get(req, "id") : NULL;
+
+    qret->session = session;
+    qret->rsp = qdict_new();
+    if (id) {
+        qobject_ref(id);
+        qdict_put_obj(qret->rsp, "id", id);
+    }
+
+    return qret;
+}
+
+void qmp_return_free(QmpReturn *qret)
+{
+    qobject_unref(qret->rsp);
+    g_free(qret);
+}
+
+void qmp_return(QmpReturn *qret, QObject *rsp)
+{
+    qdict_put_obj(qret->rsp, "return", rsp ?: QOBJECT(qdict_new()));
+    qret->session->return_cb(qret->session, qret->rsp);
+    qmp_return_free(qret);
+}
+
+void qmp_return_error(QmpReturn *qret, Error *err)
+{
+    qdict_put_obj(qret->rsp, "error",
+                  qobject_from_jsonf_nofail("{ 'class': %s, 'desc': %s }",
+                      QapiErrorClass_str(error_get_class(err)),
+                      error_get_pretty(err)));
+    error_free(err);
+    qret->session->return_cb(qret->session, qret->rsp);
+    qmp_return_free(qret);
+}
+
 static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
                                      Error **errp)
 {
@@ -143,17 +183,6 @@ static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
     return ret;
 }
 
-QDict *qmp_error_response(Error *err)
-{
-    QDict *rsp;
-
-    rsp = qdict_from_jsonf_nofail("{ 'error': { 'class': %s, 'desc': %s } }",
-                                  QapiErrorClass_str(error_get_class(err)),
-                                  error_get_pretty(err));
-    error_free(err);
-    return rsp;
-}
-
 /*
  * Does @qdict look like a command to be run out-of-band?
  */
@@ -170,9 +199,7 @@ static void qmp_json_emit(void *opaque, QObject *obj, Error *err)
     assert(!obj != !err);
 
     if (err) {
-        QDict *rsp = qmp_error_response(err);
-        session->return_cb(session, rsp);
-        qobject_unref(rsp);
+        qmp_return_error(qmp_return_new(session, obj), err);
     } else {
         qmp_dispatch(session, obj, false);
     }
@@ -208,25 +235,12 @@ void qmp_session_destroy(QmpSession *session)
 void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
 {
     Error *err = NULL;
-    QDict *dict = qobject_to(QDict, request);
-    QObject *ret, *id = dict ? qdict_get(dict, "id") : NULL;
-    QDict *rsp;
+    QObject *ret;
 
     ret = do_qmp_dispatch(session->cmds, request, allow_oob, &err);
     if (err) {
-        rsp = qmp_error_response(err);
+        qmp_return_error(qmp_return_new(session, request), err);
     } else if (ret) {
-        rsp = qdict_new();
-        qdict_put_obj(rsp, "return", ret);
-    } else {
-        /* Can only happen for commands with QCO_NO_SUCCESS_RESP */
-        return;
+        qmp_return(qmp_return_new(session, request), ret);
     }
-
-    if (id) {
-        qdict_put_obj(rsp, "id", qobject_ref(id));
-    }
-
-    session->return_cb(session, rsp);
-    qobject_unref(rsp);
 }
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 08/20] QmpSession: introduce QmpReturn
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

QmpReturn (and associated functions) is used during synchronous
dispatch return for now. It helps to factor out some code for
handling a reply context.

In the following patches, the QmpReturn will be the basis upon which
asynchronous reply will be handled: it will hold the context for a QMP
command reply.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 34 ++++++++++++++++-
 monitor.c                   |  6 +--
 qapi/qmp-dispatch.c         | 74 ++++++++++++++++++++++---------------
 3 files changed, 79 insertions(+), 35 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index b3ca6c9ff2..6c0d21968e 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -17,6 +17,8 @@
 #include "qemu/queue.h"
 #include "qapi/qmp/json-parser.h"
 
+typedef struct QmpReturn QmpReturn;
+
 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);
 
 typedef enum QmpCommandOptions
@@ -47,6 +49,37 @@ struct QmpSession {
     QmpDispatchReturn *return_cb;
 };
 
+struct QmpReturn {
+    QmpSession *session;
+    QDict *rsp;
+};
+
+/**
+ * qmp_return_new:
+ *
+ * Allocates and initializes a QmpReturn.
+ */
+QmpReturn *qmp_return_new(QmpSession *session, const QObject *req);
+
+/**
+ * qmp_return_free:
+ *
+ * Free a QmpReturn. This shouldn't be needed if you actually return
+ * with qmp_return{_error}.
+ */
+void qmp_return_free(QmpReturn *qret);
+
+/**
+ * qmp_return{_error}:
+ *
+ * Construct the command reply, and call the
+ * return_cb() associated with the session.
+ *
+ * Finally, free the QmpReturn.
+ */
+void qmp_return(QmpReturn *qret, QObject *rsp);
+void qmp_return_error(QmpReturn *qret, Error *err);
+
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
@@ -67,7 +100,6 @@ void qmp_enable_command(QmpCommandList *cmds, const char *name);
 bool qmp_command_is_enabled(const QmpCommand *cmd);
 const char *qmp_command_name(const QmpCommand *cmd);
 bool qmp_has_success_response(const QmpCommand *cmd);
-QDict *qmp_error_response(Error *err);
 void qmp_dispatch(QmpSession *session, QObject *request,
                   bool allow_oob);
 bool qmp_is_oob(const QDict *dict);
diff --git a/monitor.c b/monitor.c
index 71aad8d1ae..b1c4647a37 100644
--- a/monitor.c
+++ b/monitor.c
@@ -4175,7 +4175,6 @@ static QMPRequest *monitor_qmp_requests_pop_any_with_lock(void)
 static void monitor_qmp_bh_dispatcher(void *data)
 {
     QMPRequest *req_obj = monitor_qmp_requests_pop_any_with_lock();
-    QDict *rsp;
     bool need_resume;
     Monitor *mon;
 
@@ -4194,11 +4193,10 @@ static void monitor_qmp_bh_dispatcher(void *data)
         trace_monitor_qmp_cmd_in_band(qobject_get_try_str(id) ?: "");
         monitor_qmp_dispatch(mon, req_obj->req);
     } else {
+        QmpSession *session = &req_obj->mon->qmp.session;
         assert(req_obj->err);
-        rsp = qmp_error_response(req_obj->err);
+        qmp_return_error(qmp_return_new(session, req_obj->req), req_obj->err);
         req_obj->err = NULL;
-        qmp_send_response(req_obj->mon, rsp);
-        qobject_unref(rsp);
     }
 
     if (need_resume) {
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index f2c376d005..405cb291b1 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -19,6 +19,46 @@
 #include "qapi/qmp/qbool.h"
 #include "sysemu/sysemu.h"
 
+QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
+{
+    QmpReturn *qret = g_new0(QmpReturn, 1);
+    const QDict *req = qobject_to(QDict, request);
+    QObject *id = req ? qdict_get(req, "id") : NULL;
+
+    qret->session = session;
+    qret->rsp = qdict_new();
+    if (id) {
+        qobject_ref(id);
+        qdict_put_obj(qret->rsp, "id", id);
+    }
+
+    return qret;
+}
+
+void qmp_return_free(QmpReturn *qret)
+{
+    qobject_unref(qret->rsp);
+    g_free(qret);
+}
+
+void qmp_return(QmpReturn *qret, QObject *rsp)
+{
+    qdict_put_obj(qret->rsp, "return", rsp ?: QOBJECT(qdict_new()));
+    qret->session->return_cb(qret->session, qret->rsp);
+    qmp_return_free(qret);
+}
+
+void qmp_return_error(QmpReturn *qret, Error *err)
+{
+    qdict_put_obj(qret->rsp, "error",
+                  qobject_from_jsonf_nofail("{ 'class': %s, 'desc': %s }",
+                      QapiErrorClass_str(error_get_class(err)),
+                      error_get_pretty(err)));
+    error_free(err);
+    qret->session->return_cb(qret->session, qret->rsp);
+    qmp_return_free(qret);
+}
+
 static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
                                      Error **errp)
 {
@@ -143,17 +183,6 @@ static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
     return ret;
 }
 
-QDict *qmp_error_response(Error *err)
-{
-    QDict *rsp;
-
-    rsp = qdict_from_jsonf_nofail("{ 'error': { 'class': %s, 'desc': %s } }",
-                                  QapiErrorClass_str(error_get_class(err)),
-                                  error_get_pretty(err));
-    error_free(err);
-    return rsp;
-}
-
 /*
  * Does @qdict look like a command to be run out-of-band?
  */
@@ -170,9 +199,7 @@ static void qmp_json_emit(void *opaque, QObject *obj, Error *err)
     assert(!obj != !err);
 
     if (err) {
-        QDict *rsp = qmp_error_response(err);
-        session->return_cb(session, rsp);
-        qobject_unref(rsp);
+        qmp_return_error(qmp_return_new(session, obj), err);
     } else {
         qmp_dispatch(session, obj, false);
     }
@@ -208,25 +235,12 @@ void qmp_session_destroy(QmpSession *session)
 void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
 {
     Error *err = NULL;
-    QDict *dict = qobject_to(QDict, request);
-    QObject *ret, *id = dict ? qdict_get(dict, "id") : NULL;
-    QDict *rsp;
+    QObject *ret;
 
     ret = do_qmp_dispatch(session->cmds, request, allow_oob, &err);
     if (err) {
-        rsp = qmp_error_response(err);
+        qmp_return_error(qmp_return_new(session, request), err);
     } else if (ret) {
-        rsp = qdict_new();
-        qdict_put_obj(rsp, "return", ret);
-    } else {
-        /* Can only happen for commands with QCO_NO_SUCCESS_RESP */
-        return;
+        qmp_return(qmp_return_new(session, request), ret);
     }
-
-    if (id) {
-        qdict_put_obj(rsp, "id", qobject_ref(id));
-    }
-
-    session->return_cb(session, rsp);
-    qobject_unref(rsp);
 }
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 09/20] qmp: simplify qmp_return_error()
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

It's simple, probably more efficient, to hand-craft the dict.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/qmp-dispatch.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 405cb291b1..5f75dc27bd 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -50,10 +50,10 @@ void qmp_return(QmpReturn *qret, QObject *rsp)
 
 void qmp_return_error(QmpReturn *qret, Error *err)
 {
-    qdict_put_obj(qret->rsp, "error",
-                  qobject_from_jsonf_nofail("{ 'class': %s, 'desc': %s }",
-                      QapiErrorClass_str(error_get_class(err)),
-                      error_get_pretty(err)));
+    QDict *qdict = qdict_new();
+    qdict_put_str(qdict, "class", QapiErrorClass_str(error_get_class(err)));
+    qdict_put_str(qdict, "desc", error_get_pretty(err));
+    qdict_put_obj(qret->rsp, "error", QOBJECT(qdict));
     error_free(err);
     qret->session->return_cb(qret->session, qret->rsp);
     qmp_return_free(qret);
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 09/20] qmp: simplify qmp_return_error()
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

It's simple, probably more efficient, to hand-craft the dict.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/qmp-dispatch.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 405cb291b1..5f75dc27bd 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -50,10 +50,10 @@ void qmp_return(QmpReturn *qret, QObject *rsp)
 
 void qmp_return_error(QmpReturn *qret, Error *err)
 {
-    qdict_put_obj(qret->rsp, "error",
-                  qobject_from_jsonf_nofail("{ 'class': %s, 'desc': %s }",
-                      QapiErrorClass_str(error_get_class(err)),
-                      error_get_pretty(err)));
+    QDict *qdict = qdict_new();
+    qdict_put_str(qdict, "class", QapiErrorClass_str(error_get_class(err)));
+    qdict_put_str(qdict, "desc", error_get_pretty(err));
+    qdict_put_obj(qret->rsp, "error", QOBJECT(qdict));
     error_free(err);
     qret->session->return_cb(qret->session, qret->rsp);
     qmp_return_free(qret);
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 10/20] QmpSession: keep a queue of pending commands
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

The following commit will introduce asynchronous commands. Let's keep
the session aware of the pending commands, so we can do interesting
things like order the replies, or cancel pending operations when the
client is gone.

The queue needs a lock, since QmpReturn may be called from any thread.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  4 ++++
 qapi/qmp-dispatch.c         | 32 ++++++++++++++++++++++++++++++--
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 6c0d21968e..7c9de9780d 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -16,6 +16,7 @@
 
 #include "qemu/queue.h"
 #include "qapi/qmp/json-parser.h"
+#include "qemu/thread.h"
 
 typedef struct QmpReturn QmpReturn;
 
@@ -47,11 +48,14 @@ struct QmpSession {
     const QmpCommandList *cmds;
     JSONMessageParser parser;
     QmpDispatchReturn *return_cb;
+    QemuMutex pending_lock;
+    QTAILQ_HEAD(, QmpReturn) pending;
 };
 
 struct QmpReturn {
     QmpSession *session;
     QDict *rsp;
+    QTAILQ_ENTRY(QmpReturn) entry;
 };
 
 /**
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 5f75dc27bd..4699a6715b 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -32,11 +32,24 @@ QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
         qdict_put_obj(qret->rsp, "id", id);
     }
 
+    qemu_mutex_lock(&session->pending_lock);
+    QTAILQ_INSERT_TAIL(&session->pending, qret, entry);
+    qemu_mutex_unlock(&session->pending_lock);
+
     return qret;
 }
 
 void qmp_return_free(QmpReturn *qret)
 {
+    QmpSession *session = qret->session;
+
+    if (session) {
+        qemu_mutex_lock(&session->pending_lock);
+    }
+    QTAILQ_REMOVE(&session->pending, qret, entry);
+    if (session) {
+        qemu_mutex_unlock(&session->pending_lock);
+    }
     qobject_unref(qret->rsp);
     g_free(qret);
 }
@@ -44,7 +57,9 @@ void qmp_return_free(QmpReturn *qret)
 void qmp_return(QmpReturn *qret, QObject *rsp)
 {
     qdict_put_obj(qret->rsp, "return", rsp ?: QOBJECT(qdict_new()));
-    qret->session->return_cb(qret->session, qret->rsp);
+    if (qret->session) {
+        qret->session->return_cb(qret->session, qret->rsp);
+    }
     qmp_return_free(qret);
 }
 
@@ -55,7 +70,9 @@ void qmp_return_error(QmpReturn *qret, Error *err)
     qdict_put_str(qdict, "desc", error_get_pretty(err));
     qdict_put_obj(qret->rsp, "error", QOBJECT(qdict));
     error_free(err);
-    qret->session->return_cb(qret->session, qret->rsp);
+    if (qret->session) {
+        qret->session->return_cb(qret->session, qret->rsp);
+    }
     qmp_return_free(qret);
 }
 
@@ -219,17 +236,28 @@ void qmp_session_init(QmpSession *session,
                              session, NULL);
     session->cmds = cmds;
     session->return_cb = return_cb;
+    qemu_mutex_init(&session->pending_lock);
+    QTAILQ_INIT(&session->pending);
 }
 
 void qmp_session_destroy(QmpSession *session)
 {
+    QmpReturn *ret, *next;
+
     if (!session->return_cb) {
         return;
     }
 
+    qemu_mutex_lock(&session->pending_lock);
+    QTAILQ_FOREACH_SAFE(ret, &session->pending, entry, next) {
+        ret->session = NULL;
+        QTAILQ_REMOVE(&session->pending, ret, entry);
+    }
+    qemu_mutex_unlock(&session->pending_lock);
     session->cmds = NULL;
     session->return_cb = NULL;
     json_message_parser_destroy(&session->parser);
+    qemu_mutex_destroy(&session->pending_lock);
 }
 
 void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 10/20] QmpSession: keep a queue of pending commands
@ 2019-04-09 16:09   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

The following commit will introduce asynchronous commands. Let's keep
the session aware of the pending commands, so we can do interesting
things like order the replies, or cancel pending operations when the
client is gone.

The queue needs a lock, since QmpReturn may be called from any thread.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  4 ++++
 qapi/qmp-dispatch.c         | 32 ++++++++++++++++++++++++++++++--
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 6c0d21968e..7c9de9780d 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -16,6 +16,7 @@
 
 #include "qemu/queue.h"
 #include "qapi/qmp/json-parser.h"
+#include "qemu/thread.h"
 
 typedef struct QmpReturn QmpReturn;
 
@@ -47,11 +48,14 @@ struct QmpSession {
     const QmpCommandList *cmds;
     JSONMessageParser parser;
     QmpDispatchReturn *return_cb;
+    QemuMutex pending_lock;
+    QTAILQ_HEAD(, QmpReturn) pending;
 };
 
 struct QmpReturn {
     QmpSession *session;
     QDict *rsp;
+    QTAILQ_ENTRY(QmpReturn) entry;
 };
 
 /**
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 5f75dc27bd..4699a6715b 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -32,11 +32,24 @@ QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
         qdict_put_obj(qret->rsp, "id", id);
     }
 
+    qemu_mutex_lock(&session->pending_lock);
+    QTAILQ_INSERT_TAIL(&session->pending, qret, entry);
+    qemu_mutex_unlock(&session->pending_lock);
+
     return qret;
 }
 
 void qmp_return_free(QmpReturn *qret)
 {
+    QmpSession *session = qret->session;
+
+    if (session) {
+        qemu_mutex_lock(&session->pending_lock);
+    }
+    QTAILQ_REMOVE(&session->pending, qret, entry);
+    if (session) {
+        qemu_mutex_unlock(&session->pending_lock);
+    }
     qobject_unref(qret->rsp);
     g_free(qret);
 }
@@ -44,7 +57,9 @@ void qmp_return_free(QmpReturn *qret)
 void qmp_return(QmpReturn *qret, QObject *rsp)
 {
     qdict_put_obj(qret->rsp, "return", rsp ?: QOBJECT(qdict_new()));
-    qret->session->return_cb(qret->session, qret->rsp);
+    if (qret->session) {
+        qret->session->return_cb(qret->session, qret->rsp);
+    }
     qmp_return_free(qret);
 }
 
@@ -55,7 +70,9 @@ void qmp_return_error(QmpReturn *qret, Error *err)
     qdict_put_str(qdict, "desc", error_get_pretty(err));
     qdict_put_obj(qret->rsp, "error", QOBJECT(qdict));
     error_free(err);
-    qret->session->return_cb(qret->session, qret->rsp);
+    if (qret->session) {
+        qret->session->return_cb(qret->session, qret->rsp);
+    }
     qmp_return_free(qret);
 }
 
@@ -219,17 +236,28 @@ void qmp_session_init(QmpSession *session,
                              session, NULL);
     session->cmds = cmds;
     session->return_cb = return_cb;
+    qemu_mutex_init(&session->pending_lock);
+    QTAILQ_INIT(&session->pending);
 }
 
 void qmp_session_destroy(QmpSession *session)
 {
+    QmpReturn *ret, *next;
+
     if (!session->return_cb) {
         return;
     }
 
+    qemu_mutex_lock(&session->pending_lock);
+    QTAILQ_FOREACH_SAFE(ret, &session->pending, entry, next) {
+        ret->session = NULL;
+        QTAILQ_REMOVE(&session->pending, ret, entry);
+    }
+    qemu_mutex_unlock(&session->pending_lock);
     session->cmds = NULL;
     session->return_cb = NULL;
     json_message_parser_destroy(&session->parser);
+    qemu_mutex_destroy(&session->pending_lock);
 }
 
 void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 11/20] QmpSession: return orderly
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

QEMU will gain support for asynchronous commands, and may thus finish
commands in various order. However, the clients expect replies in
order. Let's enforce ordering of replies in QmpReturn: starting from
the older command, process each pending QmpReturn, and return until
reaching one that is unfinished.

Or if the command is OOB, it should return immediately.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  2 ++
 qapi/qmp-dispatch.c         | 61 ++++++++++++++++++++++++++++++-------
 tests/test-qmp-cmds.c       | 33 ++++++++++++++++++++
 3 files changed, 85 insertions(+), 11 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 7c9de9780d..92d6fd1afb 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -55,6 +55,8 @@ struct QmpSession {
 struct QmpReturn {
     QmpSession *session;
     QDict *rsp;
+    bool oob;
+    bool finished;
     QTAILQ_ENTRY(QmpReturn) entry;
 };
 
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 4699a6715b..546a6c9f7b 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -25,6 +25,7 @@ QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
     const QDict *req = qobject_to(QDict, request);
     QObject *id = req ? qdict_get(req, "id") : NULL;
 
+    qret->oob = req ? qmp_is_oob(req) : false;
     qret->session = session;
     qret->rsp = qdict_new();
     if (id) {
@@ -39,6 +40,15 @@ QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
     return qret;
 }
 
+static void qmp_return_free_with_lock(QmpReturn *qret)
+{
+    if (qret->session) {
+        QTAILQ_REMOVE(&qret->session->pending, qret, entry);
+    }
+    qobject_unref(qret->rsp);
+    g_free(qret);
+}
+
 void qmp_return_free(QmpReturn *qret)
 {
     QmpSession *session = qret->session;
@@ -46,21 +56,53 @@ void qmp_return_free(QmpReturn *qret)
     if (session) {
         qemu_mutex_lock(&session->pending_lock);
     }
-    QTAILQ_REMOVE(&session->pending, qret, entry);
+
+    qmp_return_free_with_lock(qret);
+
     if (session) {
         qemu_mutex_unlock(&session->pending_lock);
     }
-    qobject_unref(qret->rsp);
-    g_free(qret);
+}
+
+static void qmp_return_orderly(QmpReturn *qret)
+{
+    QmpSession *session = qret->session;
+    QmpReturn *ret, *next;
+
+    if (!session) {
+        /* the session was destroyed before return, discard */
+        qmp_return_free(qret);
+        return;
+    }
+    if (qret->oob) {
+        session->return_cb(session, qret->rsp);
+        qmp_return_free(qret);
+        return;
+    }
+
+    qret->finished = true;
+
+    qemu_mutex_lock(&session->pending_lock);
+    /*
+     * Process the list of pending and call return_cb until reaching
+     * an unfinished.
+     */
+    QTAILQ_FOREACH_SAFE(ret, &session->pending, entry, next) {
+        if (!ret->finished) {
+            break;
+        }
+        session->return_cb(session, ret->rsp);
+        ret->session = session;
+        qmp_return_free_with_lock(ret);
+    }
+
+    qemu_mutex_unlock(&session->pending_lock);
 }
 
 void qmp_return(QmpReturn *qret, QObject *rsp)
 {
     qdict_put_obj(qret->rsp, "return", rsp ?: QOBJECT(qdict_new()));
-    if (qret->session) {
-        qret->session->return_cb(qret->session, qret->rsp);
-    }
-    qmp_return_free(qret);
+    qmp_return_orderly(qret);
 }
 
 void qmp_return_error(QmpReturn *qret, Error *err)
@@ -70,10 +112,7 @@ void qmp_return_error(QmpReturn *qret, Error *err)
     qdict_put_str(qdict, "desc", error_get_pretty(err));
     qdict_put_obj(qret->rsp, "error", QOBJECT(qdict));
     error_free(err);
-    if (qret->session) {
-        qret->session->return_cb(qret->session, qret->rsp);
-    }
-    qmp_return_free(qret);
+    qmp_return_orderly(qret);
 }
 
 static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index b4d0b0440a..c4593552e3 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -327,6 +327,38 @@ static void test_dealloc_partial(void)
     qapi_free_UserDefTwo(ud2);
 }
 
+typedef struct QmpReturnOrderly {
+    QmpSession session;
+    int returns;
+} QmpReturnOrderly;
+
+static void dispatch_return_orderly(QmpSession *session, QDict *resp)
+{
+    QmpReturnOrderly *o = container_of(session, QmpReturnOrderly, session);
+
+    o->returns++;
+}
+
+static void test_qmp_return_orderly(void)
+{
+    QDict *dict = qdict_new();
+    QmpReturnOrderly o = { 0, };
+    QmpReturn *r1, *r2, *r3;
+
+    qmp_session_init(&o.session, &qmp_commands, NULL, dispatch_return_orderly);
+    r1 = qmp_return_new(&o.session, NULL);
+    qdict_put_str(dict, "exec-oob", "test");
+    r2 = qmp_return_new(&o.session, QOBJECT(dict));
+    r3 = qmp_return_new(&o.session, NULL);
+    qmp_return(r3, NULL);
+    g_assert_cmpint(o.returns, ==, 0);
+    qmp_return(r2, NULL);
+    g_assert_cmpint(o.returns, ==, 1);
+    qmp_return(r1, NULL);
+    g_assert_cmpint(o.returns, ==, 3);
+    qmp_session_destroy(&o.session);
+    qobject_unref(dict);
+}
 
 int main(int argc, char **argv)
 {
@@ -340,6 +372,7 @@ int main(int argc, char **argv)
                     test_dispatch_cmd_success_response);
     g_test_add_func("/qmp/dealloc_types", test_dealloc_types);
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
+    g_test_add_func("/qmp/return_orderly", test_qmp_return_orderly);
 
     test_qmp_init_marshal(&qmp_commands);
     g_test_run();
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 11/20] QmpSession: return orderly
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

QEMU will gain support for asynchronous commands, and may thus finish
commands in various order. However, the clients expect replies in
order. Let's enforce ordering of replies in QmpReturn: starting from
the older command, process each pending QmpReturn, and return until
reaching one that is unfinished.

Or if the command is OOB, it should return immediately.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  2 ++
 qapi/qmp-dispatch.c         | 61 ++++++++++++++++++++++++++++++-------
 tests/test-qmp-cmds.c       | 33 ++++++++++++++++++++
 3 files changed, 85 insertions(+), 11 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 7c9de9780d..92d6fd1afb 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -55,6 +55,8 @@ struct QmpSession {
 struct QmpReturn {
     QmpSession *session;
     QDict *rsp;
+    bool oob;
+    bool finished;
     QTAILQ_ENTRY(QmpReturn) entry;
 };
 
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 4699a6715b..546a6c9f7b 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -25,6 +25,7 @@ QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
     const QDict *req = qobject_to(QDict, request);
     QObject *id = req ? qdict_get(req, "id") : NULL;
 
+    qret->oob = req ? qmp_is_oob(req) : false;
     qret->session = session;
     qret->rsp = qdict_new();
     if (id) {
@@ -39,6 +40,15 @@ QmpReturn *qmp_return_new(QmpSession *session, const QObject *request)
     return qret;
 }
 
+static void qmp_return_free_with_lock(QmpReturn *qret)
+{
+    if (qret->session) {
+        QTAILQ_REMOVE(&qret->session->pending, qret, entry);
+    }
+    qobject_unref(qret->rsp);
+    g_free(qret);
+}
+
 void qmp_return_free(QmpReturn *qret)
 {
     QmpSession *session = qret->session;
@@ -46,21 +56,53 @@ void qmp_return_free(QmpReturn *qret)
     if (session) {
         qemu_mutex_lock(&session->pending_lock);
     }
-    QTAILQ_REMOVE(&session->pending, qret, entry);
+
+    qmp_return_free_with_lock(qret);
+
     if (session) {
         qemu_mutex_unlock(&session->pending_lock);
     }
-    qobject_unref(qret->rsp);
-    g_free(qret);
+}
+
+static void qmp_return_orderly(QmpReturn *qret)
+{
+    QmpSession *session = qret->session;
+    QmpReturn *ret, *next;
+
+    if (!session) {
+        /* the session was destroyed before return, discard */
+        qmp_return_free(qret);
+        return;
+    }
+    if (qret->oob) {
+        session->return_cb(session, qret->rsp);
+        qmp_return_free(qret);
+        return;
+    }
+
+    qret->finished = true;
+
+    qemu_mutex_lock(&session->pending_lock);
+    /*
+     * Process the list of pending and call return_cb until reaching
+     * an unfinished.
+     */
+    QTAILQ_FOREACH_SAFE(ret, &session->pending, entry, next) {
+        if (!ret->finished) {
+            break;
+        }
+        session->return_cb(session, ret->rsp);
+        ret->session = session;
+        qmp_return_free_with_lock(ret);
+    }
+
+    qemu_mutex_unlock(&session->pending_lock);
 }
 
 void qmp_return(QmpReturn *qret, QObject *rsp)
 {
     qdict_put_obj(qret->rsp, "return", rsp ?: QOBJECT(qdict_new()));
-    if (qret->session) {
-        qret->session->return_cb(qret->session, qret->rsp);
-    }
-    qmp_return_free(qret);
+    qmp_return_orderly(qret);
 }
 
 void qmp_return_error(QmpReturn *qret, Error *err)
@@ -70,10 +112,7 @@ void qmp_return_error(QmpReturn *qret, Error *err)
     qdict_put_str(qdict, "desc", error_get_pretty(err));
     qdict_put_obj(qret->rsp, "error", QOBJECT(qdict));
     error_free(err);
-    if (qret->session) {
-        qret->session->return_cb(qret->session, qret->rsp);
-    }
-    qmp_return_free(qret);
+    qmp_return_orderly(qret);
 }
 
 static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index b4d0b0440a..c4593552e3 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -327,6 +327,38 @@ static void test_dealloc_partial(void)
     qapi_free_UserDefTwo(ud2);
 }
 
+typedef struct QmpReturnOrderly {
+    QmpSession session;
+    int returns;
+} QmpReturnOrderly;
+
+static void dispatch_return_orderly(QmpSession *session, QDict *resp)
+{
+    QmpReturnOrderly *o = container_of(session, QmpReturnOrderly, session);
+
+    o->returns++;
+}
+
+static void test_qmp_return_orderly(void)
+{
+    QDict *dict = qdict_new();
+    QmpReturnOrderly o = { 0, };
+    QmpReturn *r1, *r2, *r3;
+
+    qmp_session_init(&o.session, &qmp_commands, NULL, dispatch_return_orderly);
+    r1 = qmp_return_new(&o.session, NULL);
+    qdict_put_str(dict, "exec-oob", "test");
+    r2 = qmp_return_new(&o.session, QOBJECT(dict));
+    r3 = qmp_return_new(&o.session, NULL);
+    qmp_return(r3, NULL);
+    g_assert_cmpint(o.returns, ==, 0);
+    qmp_return(r2, NULL);
+    g_assert_cmpint(o.returns, ==, 1);
+    qmp_return(r1, NULL);
+    g_assert_cmpint(o.returns, ==, 3);
+    qmp_session_destroy(&o.session);
+    qobject_unref(dict);
+}
 
 int main(int argc, char **argv)
 {
@@ -340,6 +372,7 @@ int main(int argc, char **argv)
                     test_dispatch_cmd_success_response);
     g_test_add_func("/qmp/dealloc_types", test_dealloc_types);
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
+    g_test_add_func("/qmp/return_orderly", test_qmp_return_orderly);
 
     test_qmp_init_marshal(&qmp_commands);
     g_test_run();
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 12/20] qmp: introduce asynchronous command type
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Add a new type of command, QmpCommandFuncAsync: those commands can
return later thanks to QmpReturn. This commit introduces the new type
and register function and teach qmp_dipatch() to call it without
qmp_return(). The async_fn callback will be responsible for calling
qmp_return(), either synchronously or asynchronously.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 10 +++++++++-
 qapi/qmp-dispatch.c         | 27 ++++++++++++++++-----------
 qapi/qmp-registry.c         | 27 ++++++++++++++++++++++++---
 3 files changed, 49 insertions(+), 15 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 92d6fd1afb..6aef0abc70 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -21,6 +21,7 @@
 typedef struct QmpReturn QmpReturn;
 
 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);
+typedef void (QmpCommandAsyncFunc)(QDict *, QmpReturn *);
 
 typedef enum QmpCommandOptions
 {
@@ -28,12 +29,16 @@ typedef enum QmpCommandOptions
     QCO_NO_SUCCESS_RESP       =  (1U << 0),
     QCO_ALLOW_OOB             =  (1U << 1),
     QCO_ALLOW_PRECONFIG       =  (1U << 2),
+    QCO_ASYNC                 =  (1U << 3),
 } QmpCommandOptions;
 
 typedef struct QmpCommand
 {
     const char *name;
-    QmpCommandFunc *fn;
+    union {
+        QmpCommandFunc *fn;
+        QmpCommandAsyncFunc *async_fn;
+    };
     QmpCommandOptions options;
     QTAILQ_ENTRY(QmpCommand) node;
     bool enabled;
@@ -88,6 +93,9 @@ void qmp_return_error(QmpReturn *qret, Error *err);
 
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
+void qmp_register_async_command(QmpCommandList *cmds, const char *name,
+                                QmpCommandAsyncFunc *fn,
+                                QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
 void qmp_session_init(QmpSession *session,
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 546a6c9f7b..1f493af67a 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -171,7 +171,7 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
     return dict;
 }
 
-static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
+static QObject *do_qmp_dispatch(QmpSession *session, QObject *request,
                                 bool allow_oob, Error **errp)
 {
     Error *local_err = NULL;
@@ -193,7 +193,7 @@ static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
         command = qdict_get_str(dict, "exec-oob");
         oob = true;
     }
-    cmd = qmp_find_command(cmds, command);
+    cmd = qmp_find_command(session->cmds, command);
     if (cmd == NULL) {
         error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
                   "The command %s has not been found", command);
@@ -224,14 +224,19 @@ static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
         qobject_ref(args);
     }
 
-    cmd->fn(args, &ret, &local_err);
-    if (local_err) {
-        error_propagate(errp, local_err);
-    } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
-        g_assert(!ret);
-    } else if (!ret) {
-        /* TODO turn into assertion */
-        ret = QOBJECT(qdict_new());
+
+    if (cmd->options & QCO_ASYNC) {
+        cmd->async_fn(args, qmp_return_new(session, request));
+    } else {
+        cmd->fn(args, &ret, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+        } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
+            g_assert(!ret);
+        } else if (!ret) {
+            /* TODO turn into assertion */
+            ret = QOBJECT(qdict_new());
+        }
     }
 
     qobject_unref(args);
@@ -304,7 +309,7 @@ void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
     Error *err = NULL;
     QObject *ret;
 
-    ret = do_qmp_dispatch(session->cmds, request, allow_oob, &err);
+    ret = do_qmp_dispatch(session, request, allow_oob, &err);
     if (err) {
         qmp_return_error(qmp_return_new(session, request), err);
     } else if (ret) {
diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c
index d0f9a1d3e3..0f3d521ce5 100644
--- a/qapi/qmp-registry.c
+++ b/qapi/qmp-registry.c
@@ -15,16 +15,37 @@
 #include "qemu/osdep.h"
 #include "qapi/qmp/dispatch.h"
 
-void qmp_register_command(QmpCommandList *cmds, const char *name,
-                          QmpCommandFunc *fn, QmpCommandOptions options)
+
+static QmpCommand *qmp_command_new(QmpCommandList *cmds, const char *name,
+                                   QmpCommandOptions options)
 {
     QmpCommand *cmd = g_malloc0(sizeof(*cmd));
 
     cmd->name = name;
-    cmd->fn = fn;
     cmd->enabled = true;
     cmd->options = options;
     QTAILQ_INSERT_TAIL(cmds, cmd, node);
+
+    return cmd;
+}
+
+
+void qmp_register_command(QmpCommandList *cmds, const char *name,
+                          QmpCommandFunc *fn, QmpCommandOptions options)
+{
+    QmpCommand *cmd = qmp_command_new(cmds, name, options);
+
+    assert(!(options & QCO_ASYNC));
+    cmd->fn = fn;
+}
+
+void qmp_register_async_command(QmpCommandList *cmds, const char *name,
+                            QmpCommandAsyncFunc *fn, QmpCommandOptions options)
+{
+    QmpCommand *cmd = qmp_command_new(cmds, name, options);
+
+    assert(options & QCO_ASYNC);
+    cmd->async_fn = fn;
 }
 
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds, const char *name)
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 12/20] qmp: introduce asynchronous command type
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Add a new type of command, QmpCommandFuncAsync: those commands can
return later thanks to QmpReturn. This commit introduces the new type
and register function and teach qmp_dipatch() to call it without
qmp_return(). The async_fn callback will be responsible for calling
qmp_return(), either synchronously or asynchronously.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h | 10 +++++++++-
 qapi/qmp-dispatch.c         | 27 ++++++++++++++++-----------
 qapi/qmp-registry.c         | 27 ++++++++++++++++++++++++---
 3 files changed, 49 insertions(+), 15 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 92d6fd1afb..6aef0abc70 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -21,6 +21,7 @@
 typedef struct QmpReturn QmpReturn;
 
 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);
+typedef void (QmpCommandAsyncFunc)(QDict *, QmpReturn *);
 
 typedef enum QmpCommandOptions
 {
@@ -28,12 +29,16 @@ typedef enum QmpCommandOptions
     QCO_NO_SUCCESS_RESP       =  (1U << 0),
     QCO_ALLOW_OOB             =  (1U << 1),
     QCO_ALLOW_PRECONFIG       =  (1U << 2),
+    QCO_ASYNC                 =  (1U << 3),
 } QmpCommandOptions;
 
 typedef struct QmpCommand
 {
     const char *name;
-    QmpCommandFunc *fn;
+    union {
+        QmpCommandFunc *fn;
+        QmpCommandAsyncFunc *async_fn;
+    };
     QmpCommandOptions options;
     QTAILQ_ENTRY(QmpCommand) node;
     bool enabled;
@@ -88,6 +93,9 @@ void qmp_return_error(QmpReturn *qret, Error *err);
 
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
+void qmp_register_async_command(QmpCommandList *cmds, const char *name,
+                                QmpCommandAsyncFunc *fn,
+                                QmpCommandOptions options);
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds,
                                    const char *name);
 void qmp_session_init(QmpSession *session,
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 546a6c9f7b..1f493af67a 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -171,7 +171,7 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
     return dict;
 }
 
-static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
+static QObject *do_qmp_dispatch(QmpSession *session, QObject *request,
                                 bool allow_oob, Error **errp)
 {
     Error *local_err = NULL;
@@ -193,7 +193,7 @@ static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
         command = qdict_get_str(dict, "exec-oob");
         oob = true;
     }
-    cmd = qmp_find_command(cmds, command);
+    cmd = qmp_find_command(session->cmds, command);
     if (cmd == NULL) {
         error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
                   "The command %s has not been found", command);
@@ -224,14 +224,19 @@ static QObject *do_qmp_dispatch(const QmpCommandList *cmds, QObject *request,
         qobject_ref(args);
     }
 
-    cmd->fn(args, &ret, &local_err);
-    if (local_err) {
-        error_propagate(errp, local_err);
-    } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
-        g_assert(!ret);
-    } else if (!ret) {
-        /* TODO turn into assertion */
-        ret = QOBJECT(qdict_new());
+
+    if (cmd->options & QCO_ASYNC) {
+        cmd->async_fn(args, qmp_return_new(session, request));
+    } else {
+        cmd->fn(args, &ret, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+        } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
+            g_assert(!ret);
+        } else if (!ret) {
+            /* TODO turn into assertion */
+            ret = QOBJECT(qdict_new());
+        }
     }
 
     qobject_unref(args);
@@ -304,7 +309,7 @@ void qmp_dispatch(QmpSession *session, QObject *request, bool allow_oob)
     Error *err = NULL;
     QObject *ret;
 
-    ret = do_qmp_dispatch(session->cmds, request, allow_oob, &err);
+    ret = do_qmp_dispatch(session, request, allow_oob, &err);
     if (err) {
         qmp_return_error(qmp_return_new(session, request), err);
     } else if (ret) {
diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c
index d0f9a1d3e3..0f3d521ce5 100644
--- a/qapi/qmp-registry.c
+++ b/qapi/qmp-registry.c
@@ -15,16 +15,37 @@
 #include "qemu/osdep.h"
 #include "qapi/qmp/dispatch.h"
 
-void qmp_register_command(QmpCommandList *cmds, const char *name,
-                          QmpCommandFunc *fn, QmpCommandOptions options)
+
+static QmpCommand *qmp_command_new(QmpCommandList *cmds, const char *name,
+                                   QmpCommandOptions options)
 {
     QmpCommand *cmd = g_malloc0(sizeof(*cmd));
 
     cmd->name = name;
-    cmd->fn = fn;
     cmd->enabled = true;
     cmd->options = options;
     QTAILQ_INSERT_TAIL(cmds, cmd, node);
+
+    return cmd;
+}
+
+
+void qmp_register_command(QmpCommandList *cmds, const char *name,
+                          QmpCommandFunc *fn, QmpCommandOptions options)
+{
+    QmpCommand *cmd = qmp_command_new(cmds, name, options);
+
+    assert(!(options & QCO_ASYNC));
+    cmd->fn = fn;
+}
+
+void qmp_register_async_command(QmpCommandList *cmds, const char *name,
+                            QmpCommandAsyncFunc *fn, QmpCommandOptions options)
+{
+    QmpCommand *cmd = qmp_command_new(cmds, name, options);
+
+    assert(options & QCO_ASYNC);
+    cmd->async_fn = fn;
 }
 
 const QmpCommand *qmp_find_command(const QmpCommandList *cmds, const char *name)
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 13/20] scripts: learn 'async' qapi commands
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Commands with the 'async' key will be registered as async type (see
related commit), and will allow a synchronous (in scope callback) or
asynchronous return (out-of-scope when ready, in idle etc) by keeping
the given QmpReturn and calling qmp_return function later.

Ex:
  { 'command': 'foo-async,
    'data': {'arg': 'str'},
    'returns': 'Foo',
    'async': true }

generates the following marshaller:

void qmp_marshal_foo_async(QDict *args, QmpReturn *qret)
{
    Error *err = NULL;
    Visitor *v;
    q_obj_foo_async_arg arg = {0};

    v = qmp_input_visitor_new(QOBJECT(args), true);
    visit_start_struct(v, NULL, NULL, 0, &err);
    if (err) {
        goto out;
    }
    visit_type_q_obj_foo_async_arg_members(v, &arg, &err);
    if (!err) {
        visit_check_struct(v, &err);
    }
    visit_end_struct(v, NULL);
    if (err) {
        goto out;
    }

    qmp_foo_async(arg.arg, qret);

out:
    if (err) {
        qmp_return_error(qret, err);
    }
    visit_free(v);
    v = qapi_dealloc_visitor_new();
    visit_start_struct(v, NULL, NULL, 0, NULL);
    visit_type_q_obj_foo_async_arg_members(v, &arg, NULL);
    visit_end_struct(v, NULL);
    visit_free(v);
}

and a return helper:

void qmp_foo_async_return(QmpReturn *qret, Foo *ret_in)
{
    Error *err = NULL;
    QObject *ret_out = NULL;

    qmp_marshal_output_Foo(ret_in, &ret_out, &err);

    if (err) {
        qmp_return_error(qret, err);
    } else {
        qmp_return(qret, ret_out);
    }
}

The dispatched function may call the return helper within the calling
scope or delay the return. To return an error, it can call
qmp_return_error() directly instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/commands.py                | 151 ++++++++++++++++++++----
 scripts/qapi/common.py                  |  15 ++-
 scripts/qapi/doc.py                     |   3 +-
 scripts/qapi/introspect.py              |   3 +-
 tests/test-qmp-cmds.c                   |  60 ++++++++++
 tests/qapi-schema/qapi-schema-test.json |   5 +
 tests/qapi-schema/qapi-schema-test.out  |   8 ++
 tests/qapi-schema/test-qapi.py          |   8 +-
 8 files changed, 218 insertions(+), 35 deletions(-)

diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index 6d66bf6aa3..74a7ec112c 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -16,18 +16,36 @@ See the COPYING file in the top-level directory.
 from qapi.common import *
 
 
-def gen_command_decl(name, arg_type, boxed, ret_type):
-    return mcgen('''
-%(c_type)s qmp_%(c_name)s(%(params)s);
+def gen_command_decl(name, arg_type, boxed, ret_type, success_response, async):
+    if async:
+        extra = "QmpReturn *qret"
+    else:
+        extra = 'Error **errp'
+
+    if async:
+        ret = mcgen('''
+void qmp_%(name)s(%(params)s);
 ''',
-                 c_type=(ret_type and ret_type.c_type()) or 'void',
-                 c_name=c_name(name),
-                 params=build_params(arg_type, boxed, 'Error **errp'))
+                     name=c_name(name),
+                     params=build_params(arg_type, boxed, extra))
+        if success_response:
+            ret += mcgen('''
+void qmp_%(name)s_return(QmpReturn *qret%(c_type)s);
+''',
+                        c_type=(", " + ret_type.c_type() if ret_type else ""),
+                        name=c_name(name))
 
+        return ret
+    else:
+        return mcgen('''
+%(c_type)s qmp_%(c_name)s(%(params)s);
+''',
+                     c_type=(ret_type and ret_type.c_type()) or 'void',
+                     c_name=c_name(name),
+                     params=build_params(arg_type, boxed, extra))
 
-def gen_call(name, arg_type, boxed, ret_type):
-    ret = ''
 
+def gen_argstr(arg_type, boxed):
     argstr = ''
     if boxed:
         assert arg_type and not arg_type.is_empty()
@@ -39,6 +57,13 @@ def gen_call(name, arg_type, boxed, ret_type):
                 argstr += 'arg.has_%s, ' % c_name(memb.name)
             argstr += 'arg.%s, ' % c_name(memb.name)
 
+    return argstr
+
+
+def gen_call(name, arg_type, boxed, ret_type):
+    ret = ''
+
+    argstr = gen_argstr(arg_type, boxed)
     lhs = ''
     if ret_type:
         lhs = 'retval = '
@@ -60,6 +85,50 @@ def gen_call(name, arg_type, boxed, ret_type):
     return ret
 
 
+def gen_async_call(name, arg_type, boxed):
+    argstr = gen_argstr(arg_type, boxed)
+
+    push_indent()
+    ret = mcgen('''
+
+qmp_%(c_name)s(%(args)sqret);
+''',
+                c_name=c_name(name), args=argstr)
+
+    pop_indent()
+    return ret
+
+
+def gen_async_return(name, ret_type):
+    if ret_type:
+        return mcgen('''
+
+void qmp_%(c_name)s_return(QmpReturn *qret, %(ret_type)s ret_in)
+{
+    Error *err = NULL;
+    QObject *ret_out = NULL;
+
+    qmp_marshal_output_%(ret_c_name)s(ret_in, &ret_out, &err);
+
+    if (err) {
+        qmp_return_error(qret, err);
+    } else {
+        qmp_return(qret, ret_out);
+    }
+}
+''',
+                     c_name=c_name(name),
+                     ret_type=ret_type.c_type(), ret_c_name=ret_type.c_name())
+    else:
+        return mcgen('''
+
+void qmp_%(c_name)s_return(QmpReturn *qret)
+{
+    qmp_return(qret, QOBJECT(qdict_new()));
+}
+''',
+                     c_name=c_name(name))
+
 def gen_marshal_output(ret_type):
     return mcgen('''
 
@@ -83,19 +152,22 @@ static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out,
                  c_type=ret_type.c_type(), c_name=ret_type.c_name())
 
 
-def build_marshal_proto(name):
-    return ('void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
-            % c_name(name))
+def build_marshal_proto(name, async):
+    if async:
+        tmpl = 'void qmp_marshal_%s(QDict *args, QmpReturn *qret)'
+    else:
+        tmpl = 'void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
+    return tmpl % c_name(name)
 
 
-def gen_marshal_decl(name):
+def gen_marshal_decl(name, async):
     return mcgen('''
 %(proto)s;
 ''',
-                 proto=build_marshal_proto(name))
+                 proto=build_marshal_proto(name, async))
 
 
-def gen_marshal(name, arg_type, boxed, ret_type):
+def gen_marshal(name, arg_type, boxed, ret_type, async):
     have_args = arg_type and not arg_type.is_empty()
 
     ret = mcgen('''
@@ -104,9 +176,9 @@ def gen_marshal(name, arg_type, boxed, ret_type):
 {
     Error *err = NULL;
 ''',
-                proto=build_marshal_proto(name))
+                proto=build_marshal_proto(name, async))
 
-    if ret_type:
+    if ret_type and not async:
         ret += mcgen('''
     %(c_type)s retval;
 ''',
@@ -153,12 +225,28 @@ def gen_marshal(name, arg_type, boxed, ret_type):
     }
 ''')
 
-    ret += gen_call(name, arg_type, boxed, ret_type)
+    if async:
+        ret += gen_async_call(name, arg_type, boxed)
+    else:
+        ret += gen_call(name, arg_type, boxed, ret_type)
 
     ret += mcgen('''
 
 out:
+''')
+
+    if async:
+         ret += mcgen('''
+    if (err) {
+        qmp_return_error(qret, err);
+    }
+''')
+    else:
+        ret += mcgen('''
     error_propagate(errp, err);
+''')
+
+    ret += mcgen('''
     visit_free(v);
 ''')
 
@@ -193,7 +281,8 @@ out:
     return ret
 
 
-def gen_register_command(name, success_response, allow_oob, allow_preconfig):
+def gen_register_command(name, success_response, allow_oob, allow_preconfig,
+                         async):
     options = []
 
     if not success_response:
@@ -202,17 +291,24 @@ def gen_register_command(name, success_response, allow_oob, allow_preconfig):
         options += ['QCO_ALLOW_OOB']
     if allow_preconfig:
         options += ['QCO_ALLOW_PRECONFIG']
+    if async:
+        options += ['QCO_ASYNC']
 
     if not options:
         options = ['QCO_NO_OPTIONS']
 
     options = " | ".join(options)
 
+    if async:
+        regfn = 'qmp_register_async_command'
+    else:
+        regfn = 'qmp_register_command'
+
     ret = mcgen('''
-    qmp_register_command(cmds, "%(name)s",
+    %(regfn)s(cmds, "%(name)s",
                          qmp_marshal_%(c_name)s, %(opts)s);
 ''',
-                name=name, c_name=c_name(name),
+                regfn=regfn, name=name, c_name=c_name(name),
                 opts=options)
     return ret
 
@@ -278,7 +374,8 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
         genc.add(gen_registry(self._regy.get_content(), self._prefix))
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         if not gen:
             return
         # FIXME: If T is a user-defined type, the user is responsible
@@ -292,11 +389,15 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
                            self._genh, self._genc, self._regy):
                 self._genc.add(gen_marshal_output(ret_type))
         with ifcontext(ifcond, self._genh, self._genc, self._regy):
-            self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
-            self._genh.add(gen_marshal_decl(name))
-            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
+            self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type,
+                                            success_response, async))
+            self._genh.add(gen_marshal_decl(name, async))
+            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type, async))
+            if async and success_response:
+                self._genc.add(gen_async_return(name, ret_type))
             self._regy.add(gen_register_command(name, success_response,
-                                                allow_oob, allow_preconfig))
+                                                allow_oob, allow_preconfig,
+                                                async))
 
 
 def gen_commands(schema, output_dir, prefix):
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index f07869ec73..1bca5fc150 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -993,7 +993,7 @@ def check_exprs(exprs):
             meta = 'command'
             check_keys(expr_elem, 'command', [],
                        ['data', 'returns', 'gen', 'success-response',
-                        'boxed', 'allow-oob', 'allow-preconfig', 'if'])
+                        'boxed', 'allow-oob', 'allow-preconfig', 'if', 'async'])
             normalize_members(expr.get('data'))
         elif 'event' in expr:
             meta = 'event'
@@ -1136,7 +1136,8 @@ class QAPISchemaVisitor(object):
         pass
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         pass
 
     def visit_event(self, name, info, ifcond, arg_type, boxed):
@@ -1533,7 +1534,8 @@ class QAPISchemaAlternateType(QAPISchemaType):
 
 class QAPISchemaCommand(QAPISchemaEntity):
     def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
-                 gen, success_response, boxed, allow_oob, allow_preconfig):
+                 gen, success_response, boxed, allow_oob, allow_preconfig,
+                 async):
         QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
         assert not arg_type or isinstance(arg_type, str)
         assert not ret_type or isinstance(ret_type, str)
@@ -1546,6 +1548,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
         self.boxed = boxed
         self.allow_oob = allow_oob
         self.allow_preconfig = allow_preconfig
+        self.async = async
 
     def check(self, schema):
         QAPISchemaEntity.check(self, schema)
@@ -1572,7 +1575,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
                               self.arg_type, self.ret_type,
                               self.gen, self.success_response,
                               self.boxed, self.allow_oob,
-                              self.allow_preconfig)
+                              self.allow_preconfig, self.async)
 
 
 class QAPISchemaEvent(QAPISchemaEntity):
@@ -1820,6 +1823,7 @@ class QAPISchema(object):
         allow_oob = expr.get('allow-oob', False)
         allow_preconfig = expr.get('allow-preconfig', False)
         ifcond = expr.get('if')
+        async = expr.get('async', False)
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
                 name, info, doc, ifcond, 'arg', self._make_members(data, info))
@@ -1828,7 +1832,8 @@ class QAPISchema(object):
             rets = self._make_array_type(rets[0], info)
         self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
                                            gen, success_response,
-                                           boxed, allow_oob, allow_preconfig))
+                                           boxed, allow_oob, allow_preconfig,
+                                           async))
 
     def _def_event(self, expr, info, doc):
         name = expr['event']
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 5c8c136899..0f51a7bff3 100755
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -236,7 +236,8 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
                                body=texi_entity(doc, 'Members', ifcond)))
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         doc = self.cur_doc
         if boxed:
             body = texi_body(doc)
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index f7f2ca07e4..7f3ea88c72 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -202,7 +202,8 @@ const QLitObject %(c_name)s = %(c_string)s;
                            for m in variants.variants]}, ifcond)
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         arg_type = arg_type or self._schema.the_empty_object_type
         ret_type = ret_type or self._schema.the_empty_object_type
         obj = {'arg-type': self._use_type(arg_type),
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index c4593552e3..58cd09ab08 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -36,6 +36,28 @@ void qmp_cmd_success_response(Error **errp)
 {
 }
 
+static gboolean cmd_async_idle(gpointer user_data)
+{
+    QmpReturn *qret = user_data;
+
+    qmp_cmd_async_return(qret, g_new0(Empty2, 1));
+
+    return G_SOURCE_REMOVE;
+}
+
+void qmp_cmd_async(const char *filename, QmpReturn *qret)
+{
+    g_idle_add(cmd_async_idle, qret);
+}
+
+void qmp_cmd_success_response_async(const char *filename, QmpReturn *qret)
+{
+    Error *err = NULL;
+
+    error_setg(&err, "no response, but error ok");
+    qmp_return_error(qret, err);
+}
+
 Empty2 *qmp_user_def_cmd0(Error **errp)
 {
     return g_new0(Empty2, 1);
@@ -360,6 +382,43 @@ static void test_qmp_return_orderly(void)
     qobject_unref(dict);
 }
 
+typedef struct QmpReturnAsync {
+    QmpSession session;
+    GMainLoop *loop;
+} QmpReturnAsync;
+
+static void dispatch_return_async(QmpSession *session, QDict *resp)
+{
+    QmpReturnAsync *a = container_of(session, QmpReturnAsync, session);
+
+    g_main_loop_quit(a->loop);
+    g_main_loop_unref(a->loop);
+    a->loop = NULL;
+}
+
+static void test_qmp_return_async(void)
+{
+    QmpReturnAsync a = { 0, };
+    QDict *args = qdict_new();
+    QDict *req = qdict_new();
+
+    a.loop = g_main_loop_new(NULL, TRUE);
+    qmp_session_init(&a.session, &qmp_commands,
+                    NULL, dispatch_return_async);
+
+    qdict_put_str(args, "filename", "test-filename");
+    qdict_put_str(req, "execute", "cmd-async");
+    qdict_put(req, "arguments", args);
+    qmp_dispatch(&a.session, QOBJECT(req), false);
+    g_assert(a.loop);
+
+    g_main_loop_run(a.loop);
+    g_assert(!a.loop);
+
+    qmp_session_destroy(&a.session);
+    qobject_unref(req);
+}
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
@@ -373,6 +432,7 @@ int main(int argc, char **argv)
     g_test_add_func("/qmp/dealloc_types", test_dealloc_types);
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
     g_test_add_func("/qmp/return_orderly", test_qmp_return_orderly);
+    g_test_add_func("/qmp/return_async", test_qmp_return_async);
 
     test_qmp_init_marshal(&qmp_commands);
     g_test_run();
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 0952c68734..d10ce39215 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -143,6 +143,11 @@
 
 { 'command': 'cmd-success-response', 'data': {}, 'success-response': false }
 
+{ 'command': 'cmd-async', 'data': {'filename': 'str'},
+  'returns': 'Empty2', 'async': true }
+{ 'command': 'cmd-success-response-async', 'data': {'filename': 'str'},
+  'async': true, 'success-response': false}
+
 # Returning a non-dictionary requires a name from the whitelist
 { 'command': 'guest-get-time', 'data': {'a': 'int', '*b': 'int' },
   'returns': 'int' }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 77fb1e1aa9..d652ebf75d 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -208,6 +208,14 @@ command user_def_cmd2 q_obj_user_def_cmd2-arg -> UserDefTwo
    gen=True success_response=True boxed=False oob=False preconfig=False
 command cmd-success-response None -> None
    gen=True success_response=False boxed=False oob=False preconfig=False
+object q_obj_cmd-async-arg
+    member filename: str optional=False
+command cmd-async q_obj_cmd-async-arg -> Empty2
+   gen=True success_response=True boxed=False oob=False preconfig=False async=True
+object q_obj_cmd-success-response-async-arg
+    member filename: str optional=False
+command cmd-success-response-async q_obj_cmd-success-response-async-arg -> None
+   gen=True success_response=False boxed=False oob=False preconfig=False async=True
 object q_obj_guest-get-time-arg
     member a: int optional=False
     member b: int optional=True
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index d21fca01fc..0d0cef479c 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -55,12 +55,14 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
         self._print_if(ifcond)
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         print('command %s %s -> %s'
               % (name, arg_type and arg_type.name,
                  ret_type and ret_type.name))
-        print('   gen=%s success_response=%s boxed=%s oob=%s preconfig=%s'
-              % (gen, success_response, boxed, allow_oob, allow_preconfig))
+        print('   gen=%s success_response=%s boxed=%s oob=%s preconfig=%s%s'
+              % (gen, success_response, boxed, allow_oob, allow_preconfig,
+                 ' async=True' if async else ''))
         self._print_if(ifcond)
 
     def visit_event(self, name, info, ifcond, arg_type, boxed):
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 13/20] scripts: learn 'async' qapi commands
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Commands with the 'async' key will be registered as async type (see
related commit), and will allow a synchronous (in scope callback) or
asynchronous return (out-of-scope when ready, in idle etc) by keeping
the given QmpReturn and calling qmp_return function later.

Ex:
  { 'command': 'foo-async,
    'data': {'arg': 'str'},
    'returns': 'Foo',
    'async': true }

generates the following marshaller:

void qmp_marshal_foo_async(QDict *args, QmpReturn *qret)
{
    Error *err = NULL;
    Visitor *v;
    q_obj_foo_async_arg arg = {0};

    v = qmp_input_visitor_new(QOBJECT(args), true);
    visit_start_struct(v, NULL, NULL, 0, &err);
    if (err) {
        goto out;
    }
    visit_type_q_obj_foo_async_arg_members(v, &arg, &err);
    if (!err) {
        visit_check_struct(v, &err);
    }
    visit_end_struct(v, NULL);
    if (err) {
        goto out;
    }

    qmp_foo_async(arg.arg, qret);

out:
    if (err) {
        qmp_return_error(qret, err);
    }
    visit_free(v);
    v = qapi_dealloc_visitor_new();
    visit_start_struct(v, NULL, NULL, 0, NULL);
    visit_type_q_obj_foo_async_arg_members(v, &arg, NULL);
    visit_end_struct(v, NULL);
    visit_free(v);
}

and a return helper:

void qmp_foo_async_return(QmpReturn *qret, Foo *ret_in)
{
    Error *err = NULL;
    QObject *ret_out = NULL;

    qmp_marshal_output_Foo(ret_in, &ret_out, &err);

    if (err) {
        qmp_return_error(qret, err);
    } else {
        qmp_return(qret, ret_out);
    }
}

The dispatched function may call the return helper within the calling
scope or delay the return. To return an error, it can call
qmp_return_error() directly instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/commands.py                | 151 ++++++++++++++++++++----
 scripts/qapi/common.py                  |  15 ++-
 scripts/qapi/doc.py                     |   3 +-
 scripts/qapi/introspect.py              |   3 +-
 tests/test-qmp-cmds.c                   |  60 ++++++++++
 tests/qapi-schema/qapi-schema-test.json |   5 +
 tests/qapi-schema/qapi-schema-test.out  |   8 ++
 tests/qapi-schema/test-qapi.py          |   8 +-
 8 files changed, 218 insertions(+), 35 deletions(-)

diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index 6d66bf6aa3..74a7ec112c 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -16,18 +16,36 @@ See the COPYING file in the top-level directory.
 from qapi.common import *
 
 
-def gen_command_decl(name, arg_type, boxed, ret_type):
-    return mcgen('''
-%(c_type)s qmp_%(c_name)s(%(params)s);
+def gen_command_decl(name, arg_type, boxed, ret_type, success_response, async):
+    if async:
+        extra = "QmpReturn *qret"
+    else:
+        extra = 'Error **errp'
+
+    if async:
+        ret = mcgen('''
+void qmp_%(name)s(%(params)s);
 ''',
-                 c_type=(ret_type and ret_type.c_type()) or 'void',
-                 c_name=c_name(name),
-                 params=build_params(arg_type, boxed, 'Error **errp'))
+                     name=c_name(name),
+                     params=build_params(arg_type, boxed, extra))
+        if success_response:
+            ret += mcgen('''
+void qmp_%(name)s_return(QmpReturn *qret%(c_type)s);
+''',
+                        c_type=(", " + ret_type.c_type() if ret_type else ""),
+                        name=c_name(name))
 
+        return ret
+    else:
+        return mcgen('''
+%(c_type)s qmp_%(c_name)s(%(params)s);
+''',
+                     c_type=(ret_type and ret_type.c_type()) or 'void',
+                     c_name=c_name(name),
+                     params=build_params(arg_type, boxed, extra))
 
-def gen_call(name, arg_type, boxed, ret_type):
-    ret = ''
 
+def gen_argstr(arg_type, boxed):
     argstr = ''
     if boxed:
         assert arg_type and not arg_type.is_empty()
@@ -39,6 +57,13 @@ def gen_call(name, arg_type, boxed, ret_type):
                 argstr += 'arg.has_%s, ' % c_name(memb.name)
             argstr += 'arg.%s, ' % c_name(memb.name)
 
+    return argstr
+
+
+def gen_call(name, arg_type, boxed, ret_type):
+    ret = ''
+
+    argstr = gen_argstr(arg_type, boxed)
     lhs = ''
     if ret_type:
         lhs = 'retval = '
@@ -60,6 +85,50 @@ def gen_call(name, arg_type, boxed, ret_type):
     return ret
 
 
+def gen_async_call(name, arg_type, boxed):
+    argstr = gen_argstr(arg_type, boxed)
+
+    push_indent()
+    ret = mcgen('''
+
+qmp_%(c_name)s(%(args)sqret);
+''',
+                c_name=c_name(name), args=argstr)
+
+    pop_indent()
+    return ret
+
+
+def gen_async_return(name, ret_type):
+    if ret_type:
+        return mcgen('''
+
+void qmp_%(c_name)s_return(QmpReturn *qret, %(ret_type)s ret_in)
+{
+    Error *err = NULL;
+    QObject *ret_out = NULL;
+
+    qmp_marshal_output_%(ret_c_name)s(ret_in, &ret_out, &err);
+
+    if (err) {
+        qmp_return_error(qret, err);
+    } else {
+        qmp_return(qret, ret_out);
+    }
+}
+''',
+                     c_name=c_name(name),
+                     ret_type=ret_type.c_type(), ret_c_name=ret_type.c_name())
+    else:
+        return mcgen('''
+
+void qmp_%(c_name)s_return(QmpReturn *qret)
+{
+    qmp_return(qret, QOBJECT(qdict_new()));
+}
+''',
+                     c_name=c_name(name))
+
 def gen_marshal_output(ret_type):
     return mcgen('''
 
@@ -83,19 +152,22 @@ static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out,
                  c_type=ret_type.c_type(), c_name=ret_type.c_name())
 
 
-def build_marshal_proto(name):
-    return ('void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
-            % c_name(name))
+def build_marshal_proto(name, async):
+    if async:
+        tmpl = 'void qmp_marshal_%s(QDict *args, QmpReturn *qret)'
+    else:
+        tmpl = 'void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
+    return tmpl % c_name(name)
 
 
-def gen_marshal_decl(name):
+def gen_marshal_decl(name, async):
     return mcgen('''
 %(proto)s;
 ''',
-                 proto=build_marshal_proto(name))
+                 proto=build_marshal_proto(name, async))
 
 
-def gen_marshal(name, arg_type, boxed, ret_type):
+def gen_marshal(name, arg_type, boxed, ret_type, async):
     have_args = arg_type and not arg_type.is_empty()
 
     ret = mcgen('''
@@ -104,9 +176,9 @@ def gen_marshal(name, arg_type, boxed, ret_type):
 {
     Error *err = NULL;
 ''',
-                proto=build_marshal_proto(name))
+                proto=build_marshal_proto(name, async))
 
-    if ret_type:
+    if ret_type and not async:
         ret += mcgen('''
     %(c_type)s retval;
 ''',
@@ -153,12 +225,28 @@ def gen_marshal(name, arg_type, boxed, ret_type):
     }
 ''')
 
-    ret += gen_call(name, arg_type, boxed, ret_type)
+    if async:
+        ret += gen_async_call(name, arg_type, boxed)
+    else:
+        ret += gen_call(name, arg_type, boxed, ret_type)
 
     ret += mcgen('''
 
 out:
+''')
+
+    if async:
+         ret += mcgen('''
+    if (err) {
+        qmp_return_error(qret, err);
+    }
+''')
+    else:
+        ret += mcgen('''
     error_propagate(errp, err);
+''')
+
+    ret += mcgen('''
     visit_free(v);
 ''')
 
@@ -193,7 +281,8 @@ out:
     return ret
 
 
-def gen_register_command(name, success_response, allow_oob, allow_preconfig):
+def gen_register_command(name, success_response, allow_oob, allow_preconfig,
+                         async):
     options = []
 
     if not success_response:
@@ -202,17 +291,24 @@ def gen_register_command(name, success_response, allow_oob, allow_preconfig):
         options += ['QCO_ALLOW_OOB']
     if allow_preconfig:
         options += ['QCO_ALLOW_PRECONFIG']
+    if async:
+        options += ['QCO_ASYNC']
 
     if not options:
         options = ['QCO_NO_OPTIONS']
 
     options = " | ".join(options)
 
+    if async:
+        regfn = 'qmp_register_async_command'
+    else:
+        regfn = 'qmp_register_command'
+
     ret = mcgen('''
-    qmp_register_command(cmds, "%(name)s",
+    %(regfn)s(cmds, "%(name)s",
                          qmp_marshal_%(c_name)s, %(opts)s);
 ''',
-                name=name, c_name=c_name(name),
+                regfn=regfn, name=name, c_name=c_name(name),
                 opts=options)
     return ret
 
@@ -278,7 +374,8 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
         genc.add(gen_registry(self._regy.get_content(), self._prefix))
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         if not gen:
             return
         # FIXME: If T is a user-defined type, the user is responsible
@@ -292,11 +389,15 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
                            self._genh, self._genc, self._regy):
                 self._genc.add(gen_marshal_output(ret_type))
         with ifcontext(ifcond, self._genh, self._genc, self._regy):
-            self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
-            self._genh.add(gen_marshal_decl(name))
-            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
+            self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type,
+                                            success_response, async))
+            self._genh.add(gen_marshal_decl(name, async))
+            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type, async))
+            if async and success_response:
+                self._genc.add(gen_async_return(name, ret_type))
             self._regy.add(gen_register_command(name, success_response,
-                                                allow_oob, allow_preconfig))
+                                                allow_oob, allow_preconfig,
+                                                async))
 
 
 def gen_commands(schema, output_dir, prefix):
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index f07869ec73..1bca5fc150 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -993,7 +993,7 @@ def check_exprs(exprs):
             meta = 'command'
             check_keys(expr_elem, 'command', [],
                        ['data', 'returns', 'gen', 'success-response',
-                        'boxed', 'allow-oob', 'allow-preconfig', 'if'])
+                        'boxed', 'allow-oob', 'allow-preconfig', 'if', 'async'])
             normalize_members(expr.get('data'))
         elif 'event' in expr:
             meta = 'event'
@@ -1136,7 +1136,8 @@ class QAPISchemaVisitor(object):
         pass
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         pass
 
     def visit_event(self, name, info, ifcond, arg_type, boxed):
@@ -1533,7 +1534,8 @@ class QAPISchemaAlternateType(QAPISchemaType):
 
 class QAPISchemaCommand(QAPISchemaEntity):
     def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
-                 gen, success_response, boxed, allow_oob, allow_preconfig):
+                 gen, success_response, boxed, allow_oob, allow_preconfig,
+                 async):
         QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
         assert not arg_type or isinstance(arg_type, str)
         assert not ret_type or isinstance(ret_type, str)
@@ -1546,6 +1548,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
         self.boxed = boxed
         self.allow_oob = allow_oob
         self.allow_preconfig = allow_preconfig
+        self.async = async
 
     def check(self, schema):
         QAPISchemaEntity.check(self, schema)
@@ -1572,7 +1575,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
                               self.arg_type, self.ret_type,
                               self.gen, self.success_response,
                               self.boxed, self.allow_oob,
-                              self.allow_preconfig)
+                              self.allow_preconfig, self.async)
 
 
 class QAPISchemaEvent(QAPISchemaEntity):
@@ -1820,6 +1823,7 @@ class QAPISchema(object):
         allow_oob = expr.get('allow-oob', False)
         allow_preconfig = expr.get('allow-preconfig', False)
         ifcond = expr.get('if')
+        async = expr.get('async', False)
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
                 name, info, doc, ifcond, 'arg', self._make_members(data, info))
@@ -1828,7 +1832,8 @@ class QAPISchema(object):
             rets = self._make_array_type(rets[0], info)
         self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
                                            gen, success_response,
-                                           boxed, allow_oob, allow_preconfig))
+                                           boxed, allow_oob, allow_preconfig,
+                                           async))
 
     def _def_event(self, expr, info, doc):
         name = expr['event']
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 5c8c136899..0f51a7bff3 100755
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -236,7 +236,8 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
                                body=texi_entity(doc, 'Members', ifcond)))
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         doc = self.cur_doc
         if boxed:
             body = texi_body(doc)
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index f7f2ca07e4..7f3ea88c72 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -202,7 +202,8 @@ const QLitObject %(c_name)s = %(c_string)s;
                            for m in variants.variants]}, ifcond)
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         arg_type = arg_type or self._schema.the_empty_object_type
         ret_type = ret_type or self._schema.the_empty_object_type
         obj = {'arg-type': self._use_type(arg_type),
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index c4593552e3..58cd09ab08 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -36,6 +36,28 @@ void qmp_cmd_success_response(Error **errp)
 {
 }
 
+static gboolean cmd_async_idle(gpointer user_data)
+{
+    QmpReturn *qret = user_data;
+
+    qmp_cmd_async_return(qret, g_new0(Empty2, 1));
+
+    return G_SOURCE_REMOVE;
+}
+
+void qmp_cmd_async(const char *filename, QmpReturn *qret)
+{
+    g_idle_add(cmd_async_idle, qret);
+}
+
+void qmp_cmd_success_response_async(const char *filename, QmpReturn *qret)
+{
+    Error *err = NULL;
+
+    error_setg(&err, "no response, but error ok");
+    qmp_return_error(qret, err);
+}
+
 Empty2 *qmp_user_def_cmd0(Error **errp)
 {
     return g_new0(Empty2, 1);
@@ -360,6 +382,43 @@ static void test_qmp_return_orderly(void)
     qobject_unref(dict);
 }
 
+typedef struct QmpReturnAsync {
+    QmpSession session;
+    GMainLoop *loop;
+} QmpReturnAsync;
+
+static void dispatch_return_async(QmpSession *session, QDict *resp)
+{
+    QmpReturnAsync *a = container_of(session, QmpReturnAsync, session);
+
+    g_main_loop_quit(a->loop);
+    g_main_loop_unref(a->loop);
+    a->loop = NULL;
+}
+
+static void test_qmp_return_async(void)
+{
+    QmpReturnAsync a = { 0, };
+    QDict *args = qdict_new();
+    QDict *req = qdict_new();
+
+    a.loop = g_main_loop_new(NULL, TRUE);
+    qmp_session_init(&a.session, &qmp_commands,
+                    NULL, dispatch_return_async);
+
+    qdict_put_str(args, "filename", "test-filename");
+    qdict_put_str(req, "execute", "cmd-async");
+    qdict_put(req, "arguments", args);
+    qmp_dispatch(&a.session, QOBJECT(req), false);
+    g_assert(a.loop);
+
+    g_main_loop_run(a.loop);
+    g_assert(!a.loop);
+
+    qmp_session_destroy(&a.session);
+    qobject_unref(req);
+}
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
@@ -373,6 +432,7 @@ int main(int argc, char **argv)
     g_test_add_func("/qmp/dealloc_types", test_dealloc_types);
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
     g_test_add_func("/qmp/return_orderly", test_qmp_return_orderly);
+    g_test_add_func("/qmp/return_async", test_qmp_return_async);
 
     test_qmp_init_marshal(&qmp_commands);
     g_test_run();
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 0952c68734..d10ce39215 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -143,6 +143,11 @@
 
 { 'command': 'cmd-success-response', 'data': {}, 'success-response': false }
 
+{ 'command': 'cmd-async', 'data': {'filename': 'str'},
+  'returns': 'Empty2', 'async': true }
+{ 'command': 'cmd-success-response-async', 'data': {'filename': 'str'},
+  'async': true, 'success-response': false}
+
 # Returning a non-dictionary requires a name from the whitelist
 { 'command': 'guest-get-time', 'data': {'a': 'int', '*b': 'int' },
   'returns': 'int' }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 77fb1e1aa9..d652ebf75d 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -208,6 +208,14 @@ command user_def_cmd2 q_obj_user_def_cmd2-arg -> UserDefTwo
    gen=True success_response=True boxed=False oob=False preconfig=False
 command cmd-success-response None -> None
    gen=True success_response=False boxed=False oob=False preconfig=False
+object q_obj_cmd-async-arg
+    member filename: str optional=False
+command cmd-async q_obj_cmd-async-arg -> Empty2
+   gen=True success_response=True boxed=False oob=False preconfig=False async=True
+object q_obj_cmd-success-response-async-arg
+    member filename: str optional=False
+command cmd-success-response-async q_obj_cmd-success-response-async-arg -> None
+   gen=True success_response=False boxed=False oob=False preconfig=False async=True
 object q_obj_guest-get-time-arg
     member a: int optional=False
     member b: int optional=True
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index d21fca01fc..0d0cef479c 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -55,12 +55,14 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
         self._print_if(ifcond)
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
-                      success_response, boxed, allow_oob, allow_preconfig):
+                      success_response, boxed, allow_oob, allow_preconfig,
+                      async):
         print('command %s %s -> %s'
               % (name, arg_type and arg_type.name,
                  ret_type and ret_type.name))
-        print('   gen=%s success_response=%s boxed=%s oob=%s preconfig=%s'
-              % (gen, success_response, boxed, allow_oob, allow_preconfig))
+        print('   gen=%s success_response=%s boxed=%s oob=%s preconfig=%s%s'
+              % (gen, success_response, boxed, allow_oob, allow_preconfig,
+                 ' async=True' if async else ''))
         self._print_if(ifcond)
 
     def visit_event(self, name, info, ifcond, arg_type, boxed):
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 14/20] qmp: add qmp_return_is_cancelled()
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

If the client is gone, and the session finished, no need to
return. The async handler can use this information to avoid
unnecessary work and exit earlier.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  8 ++++++++
 qapi/qmp-dispatch.c         | 10 ++++++++++
 tests/test-qmp-cmds.c       | 39 ++++++++++++++++++++++++++++++++++++-
 3 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 6aef0abc70..6673902e95 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -91,6 +91,14 @@ void qmp_return_free(QmpReturn *qret);
 void qmp_return(QmpReturn *qret, QObject *rsp);
 void qmp_return_error(QmpReturn *qret, Error *err);
 
+/*
+ * @qmp_return_is_cancelled:
+ *
+ * Return true if the QmpReturn is cancelled, and free the QmpReturn
+ * in this case.
+ */
+bool qmp_return_is_cancelled(QmpReturn *qret);
+
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 void qmp_register_async_command(QmpCommandList *cmds, const char *name,
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 1f493af67a..8653c17901 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -64,6 +64,16 @@ void qmp_return_free(QmpReturn *qret)
     }
 }
 
+bool qmp_return_is_cancelled(QmpReturn *qret)
+{
+    if (!qret->session) {
+        qmp_return_free(qret);
+        return true;
+    }
+
+    return false;
+}
+
 static void qmp_return_orderly(QmpReturn *qret)
 {
     QmpSession *session = qret->session;
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 58cd09ab08..97958a9d78 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -36,17 +36,29 @@ void qmp_cmd_success_response(Error **errp)
 {
 }
 
+static GMainLoop *loop;
+
 static gboolean cmd_async_idle(gpointer user_data)
 {
     QmpReturn *qret = user_data;
 
-    qmp_cmd_async_return(qret, g_new0(Empty2, 1));
+    if (!qret->session) {
+        g_assert(qmp_return_is_cancelled(qret));
+        g_main_loop_quit(loop);
+        g_main_loop_unref(loop);
+        loop = NULL;
+    } else {
+        qmp_cmd_async_return(qret, g_new0(Empty2, 1));
+    }
 
     return G_SOURCE_REMOVE;
 }
 
 void qmp_cmd_async(const char *filename, QmpReturn *qret)
 {
+    if (g_str_equal(filename, "cancel")) {
+        qmp_session_destroy(qret->session);
+    }
     g_idle_add(cmd_async_idle, qret);
 }
 
@@ -419,6 +431,30 @@ static void test_qmp_return_async(void)
     qobject_unref(req);
 }
 
+static void test_qmp_return_async_cancel(void)
+{
+    QmpReturnAsync a = { 0, };
+    QDict *args = qdict_new();
+    QDict *req = qdict_new();
+
+    a.loop = g_main_loop_new(NULL, TRUE);
+    qmp_session_init(&a.session, &qmp_commands,
+                     NULL, dispatch_return_async);
+
+    qdict_put_str(args, "filename", "cancel");
+    qdict_put_str(req, "execute", "cmd-async");
+    qdict_put(req, "arguments", args);
+    qmp_dispatch(&a.session, QOBJECT(req), false);
+    g_assert(a.loop);
+
+    loop = a.loop;
+    g_main_loop_run(loop);
+    g_assert(!loop);
+
+    qmp_session_destroy(&a.session);
+    qobject_unref(req);
+}
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
@@ -433,6 +469,7 @@ int main(int argc, char **argv)
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
     g_test_add_func("/qmp/return_orderly", test_qmp_return_orderly);
     g_test_add_func("/qmp/return_async", test_qmp_return_async);
+    g_test_add_func("/qmp/return_async_cancel", test_qmp_return_async_cancel);
 
     test_qmp_init_marshal(&qmp_commands);
     g_test_run();
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 14/20] qmp: add qmp_return_is_cancelled()
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

If the client is gone, and the session finished, no need to
return. The async handler can use this information to avoid
unnecessary work and exit earlier.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qapi/qmp/dispatch.h |  8 ++++++++
 qapi/qmp-dispatch.c         | 10 ++++++++++
 tests/test-qmp-cmds.c       | 39 ++++++++++++++++++++++++++++++++++++-
 3 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 6aef0abc70..6673902e95 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -91,6 +91,14 @@ void qmp_return_free(QmpReturn *qret);
 void qmp_return(QmpReturn *qret, QObject *rsp);
 void qmp_return_error(QmpReturn *qret, Error *err);
 
+/*
+ * @qmp_return_is_cancelled:
+ *
+ * Return true if the QmpReturn is cancelled, and free the QmpReturn
+ * in this case.
+ */
+bool qmp_return_is_cancelled(QmpReturn *qret);
+
 void qmp_register_command(QmpCommandList *cmds, const char *name,
                           QmpCommandFunc *fn, QmpCommandOptions options);
 void qmp_register_async_command(QmpCommandList *cmds, const char *name,
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 1f493af67a..8653c17901 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -64,6 +64,16 @@ void qmp_return_free(QmpReturn *qret)
     }
 }
 
+bool qmp_return_is_cancelled(QmpReturn *qret)
+{
+    if (!qret->session) {
+        qmp_return_free(qret);
+        return true;
+    }
+
+    return false;
+}
+
 static void qmp_return_orderly(QmpReturn *qret)
 {
     QmpSession *session = qret->session;
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 58cd09ab08..97958a9d78 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -36,17 +36,29 @@ void qmp_cmd_success_response(Error **errp)
 {
 }
 
+static GMainLoop *loop;
+
 static gboolean cmd_async_idle(gpointer user_data)
 {
     QmpReturn *qret = user_data;
 
-    qmp_cmd_async_return(qret, g_new0(Empty2, 1));
+    if (!qret->session) {
+        g_assert(qmp_return_is_cancelled(qret));
+        g_main_loop_quit(loop);
+        g_main_loop_unref(loop);
+        loop = NULL;
+    } else {
+        qmp_cmd_async_return(qret, g_new0(Empty2, 1));
+    }
 
     return G_SOURCE_REMOVE;
 }
 
 void qmp_cmd_async(const char *filename, QmpReturn *qret)
 {
+    if (g_str_equal(filename, "cancel")) {
+        qmp_session_destroy(qret->session);
+    }
     g_idle_add(cmd_async_idle, qret);
 }
 
@@ -419,6 +431,30 @@ static void test_qmp_return_async(void)
     qobject_unref(req);
 }
 
+static void test_qmp_return_async_cancel(void)
+{
+    QmpReturnAsync a = { 0, };
+    QDict *args = qdict_new();
+    QDict *req = qdict_new();
+
+    a.loop = g_main_loop_new(NULL, TRUE);
+    qmp_session_init(&a.session, &qmp_commands,
+                     NULL, dispatch_return_async);
+
+    qdict_put_str(args, "filename", "cancel");
+    qdict_put_str(req, "execute", "cmd-async");
+    qdict_put(req, "arguments", args);
+    qmp_dispatch(&a.session, QOBJECT(req), false);
+    g_assert(a.loop);
+
+    loop = a.loop;
+    g_main_loop_run(loop);
+    g_assert(!loop);
+
+    qmp_session_destroy(&a.session);
+    qobject_unref(req);
+}
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
@@ -433,6 +469,7 @@ int main(int argc, char **argv)
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
     g_test_add_func("/qmp/return_orderly", test_qmp_return_orderly);
     g_test_add_func("/qmp/return_async", test_qmp_return_async);
+    g_test_add_func("/qmp/return_async_cancel", test_qmp_return_async_cancel);
 
     test_qmp_init_marshal(&qmp_commands);
     g_test_run();
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 15/20] monitor: add qmp_return_get_monitor()
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

If necessary, add an helper that can be used to retrieve the
associated monitor. This is useful for asynchronous commands that may
have to update cur_mon for various reasons.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/monitor/monitor.h | 3 +++
 monitor.c                 | 6 ++++++
 2 files changed, 9 insertions(+)

diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index c1b40a9cac..5f25cd2616 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -5,6 +5,7 @@
 #include "block/block.h"
 #include "qapi/qapi-types-misc.h"
 #include "qemu/readline.h"
+#include "qapi/qmp/dispatch.h"
 
 extern __thread Monitor *cur_mon;
 
@@ -51,4 +52,6 @@ int monitor_fdset_dup_fd_find(int dup_fd);
 void monitor_vfprintf(FILE *stream,
                       const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
 
+Monitor *qmp_return_get_monitor(QmpReturn *qret);
+
 #endif /* MONITOR_H */
diff --git a/monitor.c b/monitor.c
index b1c4647a37..76bc2f8c7c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -324,6 +324,12 @@ bool monitor_cur_is_qmp(void)
     return cur_mon && monitor_is_qmp(cur_mon);
 }
 
+Monitor *qmp_return_get_monitor(QmpReturn *qret)
+{
+    return qret->session ?
+        container_of(qret->session, Monitor, qmp.session) : NULL;
+}
+
 void monitor_read_command(Monitor *mon, int show_prompt)
 {
     if (!mon->rs)
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 15/20] monitor: add qmp_return_get_monitor()
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

If necessary, add an helper that can be used to retrieve the
associated monitor. This is useful for asynchronous commands that may
have to update cur_mon for various reasons.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/monitor/monitor.h | 3 +++
 monitor.c                 | 6 ++++++
 2 files changed, 9 insertions(+)

diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index c1b40a9cac..5f25cd2616 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -5,6 +5,7 @@
 #include "block/block.h"
 #include "qapi/qapi-types-misc.h"
 #include "qemu/readline.h"
+#include "qapi/qmp/dispatch.h"
 
 extern __thread Monitor *cur_mon;
 
@@ -51,4 +52,6 @@ int monitor_fdset_dup_fd_find(int dup_fd);
 void monitor_vfprintf(FILE *stream,
                       const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
 
+Monitor *qmp_return_get_monitor(QmpReturn *qret);
+
 #endif /* MONITOR_H */
diff --git a/monitor.c b/monitor.c
index b1c4647a37..76bc2f8c7c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -324,6 +324,12 @@ bool monitor_cur_is_qmp(void)
     return cur_mon && monitor_is_qmp(cur_mon);
 }
 
+Monitor *qmp_return_get_monitor(QmpReturn *qret)
+{
+    return qret->session ?
+        container_of(qret->session, Monitor, qmp.session) : NULL;
+}
+
 void monitor_read_command(Monitor *mon, int show_prompt)
 {
     if (!mon->rs)
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 16/20] console: add graphic_hw_update_done()
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Add a function to be called when a graphic update is done.

Declare the QXL renderer as async: render_update_cookie_num counts the
number of outstanding updates, and graphic_hw_update_done() is called
when it reaches none.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h    | 2 ++
 hw/display/qxl-render.c | 9 +++++++--
 hw/display/qxl.c        | 1 +
 ui/console.c            | 9 +++++++++
 4 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index fef900db76..9415adadfe 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -364,6 +364,7 @@ static inline void console_write_ch(console_ch_t *dest, uint32_t ch)
 typedef struct GraphicHwOps {
     void (*invalidate)(void *opaque);
     void (*gfx_update)(void *opaque);
+    bool gfx_update_async; /* if true, calls graphic_hw_update_done() */
     void (*text_update)(void *opaque, console_ch_t *text);
     void (*update_interval)(void *opaque, uint64_t interval);
     int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
@@ -379,6 +380,7 @@ void graphic_console_set_hwops(QemuConsole *con,
 void graphic_console_close(QemuConsole *con);
 
 void graphic_hw_update(QemuConsole *con);
+void graphic_hw_update_done(QemuConsole *con);
 void graphic_hw_invalidate(QemuConsole *con);
 void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
 void graphic_hw_gl_block(QemuConsole *con, bool block);
diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
index 14ad2b352d..102fa0b7e9 100644
--- a/hw/display/qxl-render.c
+++ b/hw/display/qxl-render.c
@@ -108,7 +108,7 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
                                                 qxl->guest_primary.surface.mem,
                                                 MEMSLOT_GROUP_GUEST);
         if (!qxl->guest_primary.data) {
-            return;
+            goto end;
         }
         qxl_set_rect_to_surface(qxl, &qxl->dirty[0]);
         qxl->num_dirty_rects = 1;
@@ -136,7 +136,7 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
     }
 
     if (!qxl->guest_primary.data) {
-        return;
+        goto end;
     }
     for (i = 0; i < qxl->num_dirty_rects; i++) {
         if (qemu_spice_rect_is_empty(qxl->dirty+i)) {
@@ -157,6 +157,11 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
                        qxl->dirty[i].bottom - qxl->dirty[i].top);
     }
     qxl->num_dirty_rects = 0;
+
+end:
+    if (qxl->render_update_cookie_num == 0) {
+        graphic_hw_update_done(qxl->ssd.dcl.con);
+    }
 }
 
 /*
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index c8ce5781e0..884e24766c 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -1182,6 +1182,7 @@ static const QXLInterface qxl_interface = {
 
 static const GraphicHwOps qxl_ops = {
     .gfx_update  = qxl_hw_update,
+    .gfx_update_async = true,
 };
 
 static void qxl_enter_vga_mode(PCIQXLDevice *d)
diff --git a/ui/console.c b/ui/console.c
index 6d2282d3e9..3a7f71021f 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -258,13 +258,22 @@ static void gui_setup_refresh(DisplayState *ds)
     ds->have_text = have_text;
 }
 
+void graphic_hw_update_done(QemuConsole *con)
+{
+}
+
 void graphic_hw_update(QemuConsole *con)
 {
+    bool async = false;
     if (!con) {
         con = active_console;
     }
     if (con && con->hw_ops->gfx_update) {
         con->hw_ops->gfx_update(con->hw);
+        async = con->hw_ops->gfx_update_async;
+    }
+    if (!async) {
+        graphic_hw_update_done(con);
     }
 }
 
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 16/20] console: add graphic_hw_update_done()
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Add a function to be called when a graphic update is done.

Declare the QXL renderer as async: render_update_cookie_num counts the
number of outstanding updates, and graphic_hw_update_done() is called
when it reaches none.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/ui/console.h    | 2 ++
 hw/display/qxl-render.c | 9 +++++++--
 hw/display/qxl.c        | 1 +
 ui/console.c            | 9 +++++++++
 4 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/include/ui/console.h b/include/ui/console.h
index fef900db76..9415adadfe 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -364,6 +364,7 @@ static inline void console_write_ch(console_ch_t *dest, uint32_t ch)
 typedef struct GraphicHwOps {
     void (*invalidate)(void *opaque);
     void (*gfx_update)(void *opaque);
+    bool gfx_update_async; /* if true, calls graphic_hw_update_done() */
     void (*text_update)(void *opaque, console_ch_t *text);
     void (*update_interval)(void *opaque, uint64_t interval);
     int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
@@ -379,6 +380,7 @@ void graphic_console_set_hwops(QemuConsole *con,
 void graphic_console_close(QemuConsole *con);
 
 void graphic_hw_update(QemuConsole *con);
+void graphic_hw_update_done(QemuConsole *con);
 void graphic_hw_invalidate(QemuConsole *con);
 void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
 void graphic_hw_gl_block(QemuConsole *con, bool block);
diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
index 14ad2b352d..102fa0b7e9 100644
--- a/hw/display/qxl-render.c
+++ b/hw/display/qxl-render.c
@@ -108,7 +108,7 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
                                                 qxl->guest_primary.surface.mem,
                                                 MEMSLOT_GROUP_GUEST);
         if (!qxl->guest_primary.data) {
-            return;
+            goto end;
         }
         qxl_set_rect_to_surface(qxl, &qxl->dirty[0]);
         qxl->num_dirty_rects = 1;
@@ -136,7 +136,7 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
     }
 
     if (!qxl->guest_primary.data) {
-        return;
+        goto end;
     }
     for (i = 0; i < qxl->num_dirty_rects; i++) {
         if (qemu_spice_rect_is_empty(qxl->dirty+i)) {
@@ -157,6 +157,11 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
                        qxl->dirty[i].bottom - qxl->dirty[i].top);
     }
     qxl->num_dirty_rects = 0;
+
+end:
+    if (qxl->render_update_cookie_num == 0) {
+        graphic_hw_update_done(qxl->ssd.dcl.con);
+    }
 }
 
 /*
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index c8ce5781e0..884e24766c 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -1182,6 +1182,7 @@ static const QXLInterface qxl_interface = {
 
 static const GraphicHwOps qxl_ops = {
     .gfx_update  = qxl_hw_update,
+    .gfx_update_async = true,
 };
 
 static void qxl_enter_vga_mode(PCIQXLDevice *d)
diff --git a/ui/console.c b/ui/console.c
index 6d2282d3e9..3a7f71021f 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -258,13 +258,22 @@ static void gui_setup_refresh(DisplayState *ds)
     ds->have_text = have_text;
 }
 
+void graphic_hw_update_done(QemuConsole *con)
+{
+}
+
 void graphic_hw_update(QemuConsole *con)
 {
+    bool async = false;
     if (!con) {
         con = active_console;
     }
     if (con && con->hw_ops->gfx_update) {
         con->hw_ops->gfx_update(con->hw);
+        async = con->hw_ops->gfx_update_async;
+    }
+    if (!async) {
+        graphic_hw_update_done(con);
     }
 }
 
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Make screendump asynchronous to provide correct screendumps.

For now, HMP doesn't have async support, so it has to remain
synchronous and potentially incorrect to avoid races (following
patches will add HMP asynchronous commands)

Fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=1230527

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/ui.json         |   3 +-
 include/ui/console.h |   3 ++
 hmp.c                |   2 +-
 ui/console.c         | 103 +++++++++++++++++++++++++++++++++++++++----
 4 files changed, 100 insertions(+), 11 deletions(-)

diff --git a/qapi/ui.json b/qapi/ui.json
index 59e412139a..cbb3979172 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -96,7 +96,8 @@
 #
 ##
 { 'command': 'screendump',
-  'data': {'filename': 'str', '*device': 'str', '*head': 'int'} }
+  'data': {'filename': 'str', '*device': 'str', '*head': 'int'},
+  'async': true }
 
 ##
 # == Spice
diff --git a/include/ui/console.h b/include/ui/console.h
index 9415adadfe..2649c3cbfe 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -74,6 +74,9 @@ typedef struct MouseTransformInfo {
 } MouseTransformInfo;
 
 void hmp_mouse_set(Monitor *mon, const QDict *qdict);
+void hmp_screendump_sync(const char *filename,
+                         bool has_device, const char *device,
+                         bool has_head, int64_t head, Error **errp);
 
 /* keysym is a unicode code except for special keys (see QEMU_KEY_xxx
    constants) */
diff --git a/hmp.c b/hmp.c
index 8eec768088..9428c90c5d 100644
--- a/hmp.c
+++ b/hmp.c
@@ -2380,7 +2380,7 @@ void hmp_screendump(Monitor *mon, const QDict *qdict)
     int64_t head = qdict_get_try_int(qdict, "head", 0);
     Error *err = NULL;
 
-    qmp_screendump(filename, id != NULL, id, id != NULL, head, &err);
+    hmp_screendump_sync(filename, id != NULL, id, id != NULL, head, &err);
     hmp_handle_error(mon, &err);
 }
 
diff --git a/ui/console.c b/ui/console.c
index 3a7f71021f..620cd14b80 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -32,6 +32,7 @@
 #include "chardev/char-fe.h"
 #include "trace.h"
 #include "exec/memory.h"
+#include "monitor/monitor.h"
 
 #define DEFAULT_BACKSCROLL 512
 #define CONSOLE_CURSOR_PERIOD 500
@@ -116,6 +117,12 @@ typedef enum {
     TEXT_CONSOLE_FIXED_SIZE
 } console_type_t;
 
+struct qmp_screendump {
+    gchar *filename;
+    QmpReturn *ret;
+    QLIST_ENTRY(qmp_screendump) link;
+};
+
 struct QemuConsole {
     Object parent;
 
@@ -166,6 +173,8 @@ struct QemuConsole {
     uint8_t out_fifo_buf[16];
     QEMUTimer *kbd_timer;
 
+    QLIST_HEAD(, qmp_screendump) qmp_screendumps;
+
     QTAILQ_ENTRY(QemuConsole) next;
 };
 
@@ -192,6 +201,8 @@ static void dpy_refresh(DisplayState *s);
 static DisplayState *get_alloc_displaystate(void);
 static void text_console_update_cursor_timer(void);
 static void text_console_update_cursor(void *opaque);
+static void ppm_save(const char *filename, DisplaySurface *ds,
+                     Error **errp);
 
 static void gui_update(void *opaque)
 {
@@ -258,8 +269,48 @@ static void gui_setup_refresh(DisplayState *ds)
     ds->have_text = have_text;
 }
 
+static void qmp_screendump_finish(QemuConsole *con, struct qmp_screendump *dump)
+{
+    Error *err = NULL;
+    DisplaySurface *surface;
+    Monitor *prev_mon = cur_mon;
+
+    if (qmp_return_is_cancelled(dump->ret)) {
+        goto cleanup;
+    }
+
+    cur_mon = qmp_return_get_monitor(dump->ret);
+    surface = qemu_console_surface(con);
+    if (!surface) {
+        error_setg(&err, "no surface");
+    } else {
+        /*
+         * FIXME: async save with coroutine? it would have to copy or
+         * lock the surface.
+         */
+        ppm_save(dump->filename, surface, &err);
+    }
+
+    if (err) {
+        qmp_return_error(dump->ret, err);
+    } else {
+        qmp_screendump_return(dump->ret);
+    }
+    cur_mon = prev_mon;
+
+cleanup:
+    g_free(dump->filename);
+    QLIST_REMOVE(dump, link);
+    g_free(dump);
+}
+
 void graphic_hw_update_done(QemuConsole *con)
 {
+    struct qmp_screendump *dump, *next;
+
+    QLIST_FOREACH_SAFE(dump, &con->qmp_screendumps, link, next) {
+        qmp_screendump_finish(con, dump);
+    }
 }
 
 void graphic_hw_update(QemuConsole *con)
@@ -355,30 +406,41 @@ write_err:
     goto out;
 }
 
-void qmp_screendump(const char *filename, bool has_device, const char *device,
-                    bool has_head, int64_t head, Error **errp)
+
+static QemuConsole *get_console(bool has_device, const char *device,
+                                bool has_head, int64_t head, Error **errp)
 {
-    QemuConsole *con;
-    DisplaySurface *surface;
+    QemuConsole *con = NULL;
 
     if (has_device) {
         con = qemu_console_lookup_by_device_name(device, has_head ? head : 0,
                                                  errp);
-        if (!con) {
-            return;
-        }
     } else {
         if (has_head) {
             error_setg(errp, "'head' must be specified together with 'device'");
-            return;
+            return NULL;
         }
         con = qemu_console_lookup_by_index(0);
         if (!con) {
             error_setg(errp, "There is no console to take a screendump from");
-            return;
         }
     }
 
+    return con;
+}
+
+void hmp_screendump_sync(const char *filename,
+                         bool has_device, const char *device,
+                         bool has_head, int64_t head, Error **errp)
+{
+    DisplaySurface *surface;
+    QemuConsole *con = get_console(has_device, device, has_head, head, errp);
+
+    if (!con) {
+        return;
+    }
+    /* This may not complete the drawing with Spice, you may have
+     * glitches or outdated dumps, use qmp instead! */
     graphic_hw_update(con);
     surface = qemu_console_surface(con);
     if (!surface) {
@@ -389,6 +451,28 @@ void qmp_screendump(const char *filename, bool has_device, const char *device,
     ppm_save(filename, surface, errp);
 }
 
+void qmp_screendump(const char *filename,
+                    bool has_device, const char *device,
+                    bool has_head, int64_t head,
+                    QmpReturn *qret)
+{
+    Error *err = NULL;
+    struct qmp_screendump *dump = NULL;
+    QemuConsole *con = get_console(has_device, device, has_head, head, &err);
+
+    if (!con) {
+        qmp_return_error(qret, err);
+        return;
+    }
+
+    dump = g_new(struct qmp_screendump, 1);
+    dump->filename = g_strdup(filename);
+    dump->ret = qret;
+    QLIST_INSERT_HEAD(&con->qmp_screendumps, dump, link);
+
+    graphic_hw_update(con);
+}
+
 void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata)
 {
     if (!con) {
@@ -1293,6 +1377,7 @@ static QemuConsole *new_console(DisplayState *ds, console_type_t console_type,
     obj = object_new(TYPE_QEMU_CONSOLE);
     s = QEMU_CONSOLE(obj);
     s->head = head;
+    QLIST_INIT(&s->qmp_screendumps);
     object_property_add_link(obj, "device", TYPE_DEVICE,
                              (Object **)&s->device,
                              object_property_allow_set_link,
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Make screendump asynchronous to provide correct screendumps.

For now, HMP doesn't have async support, so it has to remain
synchronous and potentially incorrect to avoid races (following
patches will add HMP asynchronous commands)

Fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=1230527

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/ui.json         |   3 +-
 include/ui/console.h |   3 ++
 hmp.c                |   2 +-
 ui/console.c         | 103 +++++++++++++++++++++++++++++++++++++++----
 4 files changed, 100 insertions(+), 11 deletions(-)

diff --git a/qapi/ui.json b/qapi/ui.json
index 59e412139a..cbb3979172 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -96,7 +96,8 @@
 #
 ##
 { 'command': 'screendump',
-  'data': {'filename': 'str', '*device': 'str', '*head': 'int'} }
+  'data': {'filename': 'str', '*device': 'str', '*head': 'int'},
+  'async': true }
 
 ##
 # == Spice
diff --git a/include/ui/console.h b/include/ui/console.h
index 9415adadfe..2649c3cbfe 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -74,6 +74,9 @@ typedef struct MouseTransformInfo {
 } MouseTransformInfo;
 
 void hmp_mouse_set(Monitor *mon, const QDict *qdict);
+void hmp_screendump_sync(const char *filename,
+                         bool has_device, const char *device,
+                         bool has_head, int64_t head, Error **errp);
 
 /* keysym is a unicode code except for special keys (see QEMU_KEY_xxx
    constants) */
diff --git a/hmp.c b/hmp.c
index 8eec768088..9428c90c5d 100644
--- a/hmp.c
+++ b/hmp.c
@@ -2380,7 +2380,7 @@ void hmp_screendump(Monitor *mon, const QDict *qdict)
     int64_t head = qdict_get_try_int(qdict, "head", 0);
     Error *err = NULL;
 
-    qmp_screendump(filename, id != NULL, id, id != NULL, head, &err);
+    hmp_screendump_sync(filename, id != NULL, id, id != NULL, head, &err);
     hmp_handle_error(mon, &err);
 }
 
diff --git a/ui/console.c b/ui/console.c
index 3a7f71021f..620cd14b80 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -32,6 +32,7 @@
 #include "chardev/char-fe.h"
 #include "trace.h"
 #include "exec/memory.h"
+#include "monitor/monitor.h"
 
 #define DEFAULT_BACKSCROLL 512
 #define CONSOLE_CURSOR_PERIOD 500
@@ -116,6 +117,12 @@ typedef enum {
     TEXT_CONSOLE_FIXED_SIZE
 } console_type_t;
 
+struct qmp_screendump {
+    gchar *filename;
+    QmpReturn *ret;
+    QLIST_ENTRY(qmp_screendump) link;
+};
+
 struct QemuConsole {
     Object parent;
 
@@ -166,6 +173,8 @@ struct QemuConsole {
     uint8_t out_fifo_buf[16];
     QEMUTimer *kbd_timer;
 
+    QLIST_HEAD(, qmp_screendump) qmp_screendumps;
+
     QTAILQ_ENTRY(QemuConsole) next;
 };
 
@@ -192,6 +201,8 @@ static void dpy_refresh(DisplayState *s);
 static DisplayState *get_alloc_displaystate(void);
 static void text_console_update_cursor_timer(void);
 static void text_console_update_cursor(void *opaque);
+static void ppm_save(const char *filename, DisplaySurface *ds,
+                     Error **errp);
 
 static void gui_update(void *opaque)
 {
@@ -258,8 +269,48 @@ static void gui_setup_refresh(DisplayState *ds)
     ds->have_text = have_text;
 }
 
+static void qmp_screendump_finish(QemuConsole *con, struct qmp_screendump *dump)
+{
+    Error *err = NULL;
+    DisplaySurface *surface;
+    Monitor *prev_mon = cur_mon;
+
+    if (qmp_return_is_cancelled(dump->ret)) {
+        goto cleanup;
+    }
+
+    cur_mon = qmp_return_get_monitor(dump->ret);
+    surface = qemu_console_surface(con);
+    if (!surface) {
+        error_setg(&err, "no surface");
+    } else {
+        /*
+         * FIXME: async save with coroutine? it would have to copy or
+         * lock the surface.
+         */
+        ppm_save(dump->filename, surface, &err);
+    }
+
+    if (err) {
+        qmp_return_error(dump->ret, err);
+    } else {
+        qmp_screendump_return(dump->ret);
+    }
+    cur_mon = prev_mon;
+
+cleanup:
+    g_free(dump->filename);
+    QLIST_REMOVE(dump, link);
+    g_free(dump);
+}
+
 void graphic_hw_update_done(QemuConsole *con)
 {
+    struct qmp_screendump *dump, *next;
+
+    QLIST_FOREACH_SAFE(dump, &con->qmp_screendumps, link, next) {
+        qmp_screendump_finish(con, dump);
+    }
 }
 
 void graphic_hw_update(QemuConsole *con)
@@ -355,30 +406,41 @@ write_err:
     goto out;
 }
 
-void qmp_screendump(const char *filename, bool has_device, const char *device,
-                    bool has_head, int64_t head, Error **errp)
+
+static QemuConsole *get_console(bool has_device, const char *device,
+                                bool has_head, int64_t head, Error **errp)
 {
-    QemuConsole *con;
-    DisplaySurface *surface;
+    QemuConsole *con = NULL;
 
     if (has_device) {
         con = qemu_console_lookup_by_device_name(device, has_head ? head : 0,
                                                  errp);
-        if (!con) {
-            return;
-        }
     } else {
         if (has_head) {
             error_setg(errp, "'head' must be specified together with 'device'");
-            return;
+            return NULL;
         }
         con = qemu_console_lookup_by_index(0);
         if (!con) {
             error_setg(errp, "There is no console to take a screendump from");
-            return;
         }
     }
 
+    return con;
+}
+
+void hmp_screendump_sync(const char *filename,
+                         bool has_device, const char *device,
+                         bool has_head, int64_t head, Error **errp)
+{
+    DisplaySurface *surface;
+    QemuConsole *con = get_console(has_device, device, has_head, head, errp);
+
+    if (!con) {
+        return;
+    }
+    /* This may not complete the drawing with Spice, you may have
+     * glitches or outdated dumps, use qmp instead! */
     graphic_hw_update(con);
     surface = qemu_console_surface(con);
     if (!surface) {
@@ -389,6 +451,28 @@ void qmp_screendump(const char *filename, bool has_device, const char *device,
     ppm_save(filename, surface, errp);
 }
 
+void qmp_screendump(const char *filename,
+                    bool has_device, const char *device,
+                    bool has_head, int64_t head,
+                    QmpReturn *qret)
+{
+    Error *err = NULL;
+    struct qmp_screendump *dump = NULL;
+    QemuConsole *con = get_console(has_device, device, has_head, head, &err);
+
+    if (!con) {
+        qmp_return_error(qret, err);
+        return;
+    }
+
+    dump = g_new(struct qmp_screendump, 1);
+    dump->filename = g_strdup(filename);
+    dump->ret = qret;
+    QLIST_INSERT_HEAD(&con->qmp_screendumps, dump, link);
+
+    graphic_hw_update(con);
+}
+
 void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata)
 {
     if (!con) {
@@ -1293,6 +1377,7 @@ static QemuConsole *new_console(DisplayState *ds, console_type_t console_type,
     obj = object_new(TYPE_QEMU_CONSOLE);
     s = QEMU_CONSOLE(obj);
     s->head = head;
+    QLIST_INIT(&s->qmp_screendumps);
     object_property_add_link(obj, "device", TYPE_DEVICE,
                              (Object **)&s->device,
                              object_property_allow_set_link,
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 18/20] monitor: start making qmp_human_monitor_command() asynchronous
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

This prepares the work for HMP commands to be asynchronous.

Start making QMP human-monitor-command asynchronous, although
QmpReturn is used synchronously on error or after
handle_hmp_command().

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/misc.json |  3 ++-
 monitor.c      | 14 ++++++++------
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/qapi/misc.json b/qapi/misc.json
index 8b3ca4fdd3..f8fd4824fb 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -1344,7 +1344,8 @@
 ##
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
-  'returns': 'str' }
+  'returns': 'str',
+  'async': true }
 
 ##
 # @ObjectPropertyInfo:
diff --git a/monitor.c b/monitor.c
index 76bc2f8c7c..477ccd852d 100644
--- a/monitor.c
+++ b/monitor.c
@@ -743,8 +743,8 @@ static void monitor_data_destroy(Monitor *mon)
     g_queue_free(mon->qmp.qmp_requests);
 }
 
-char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
-                                int64_t cpu_index, Error **errp)
+void qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
+                               int64_t cpu_index, QmpReturn *qret)
 {
     char *output = NULL;
     Monitor *old_mon, hmp;
@@ -757,15 +757,15 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     if (has_cpu_index) {
         int ret = monitor_set_cpu(cpu_index);
         if (ret < 0) {
-            cur_mon = old_mon;
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
+            Error *err = NULL;
+            error_setg(&err, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
                        "a CPU number");
+            qmp_return_error(qret, err);
             goto out;
         }
     }
 
     handle_hmp_command(&hmp, command_line);
-    cur_mon = old_mon;
 
     qemu_mutex_lock(&hmp.mon_lock);
     if (qstring_get_length(hmp.outbuf) > 0) {
@@ -775,9 +775,11 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     }
     qemu_mutex_unlock(&hmp.mon_lock);
 
+    qmp_human_monitor_command_return(qret, output);
+
 out:
+    cur_mon = old_mon;
     monitor_data_destroy(&hmp);
-    return output;
 }
 
 static int compare_cmd(const char *name, const char *list)
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 18/20] monitor: start making qmp_human_monitor_command() asynchronous
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

This prepares the work for HMP commands to be asynchronous.

Start making QMP human-monitor-command asynchronous, although
QmpReturn is used synchronously on error or after
handle_hmp_command().

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/misc.json |  3 ++-
 monitor.c      | 14 ++++++++------
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/qapi/misc.json b/qapi/misc.json
index 8b3ca4fdd3..f8fd4824fb 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -1344,7 +1344,8 @@
 ##
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
-  'returns': 'str' }
+  'returns': 'str',
+  'async': true }
 
 ##
 # @ObjectPropertyInfo:
diff --git a/monitor.c b/monitor.c
index 76bc2f8c7c..477ccd852d 100644
--- a/monitor.c
+++ b/monitor.c
@@ -743,8 +743,8 @@ static void monitor_data_destroy(Monitor *mon)
     g_queue_free(mon->qmp.qmp_requests);
 }
 
-char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
-                                int64_t cpu_index, Error **errp)
+void qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
+                               int64_t cpu_index, QmpReturn *qret)
 {
     char *output = NULL;
     Monitor *old_mon, hmp;
@@ -757,15 +757,15 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     if (has_cpu_index) {
         int ret = monitor_set_cpu(cpu_index);
         if (ret < 0) {
-            cur_mon = old_mon;
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
+            Error *err = NULL;
+            error_setg(&err, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
                        "a CPU number");
+            qmp_return_error(qret, err);
             goto out;
         }
     }
 
     handle_hmp_command(&hmp, command_line);
-    cur_mon = old_mon;
 
     qemu_mutex_lock(&hmp.mon_lock);
     if (qstring_get_length(hmp.outbuf) > 0) {
@@ -775,9 +775,11 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     }
     qemu_mutex_unlock(&hmp.mon_lock);
 
+    qmp_human_monitor_command_return(qret, output);
+
 out:
+    cur_mon = old_mon;
     monitor_data_destroy(&hmp);
-    return output;
 }
 
 static int compare_cmd(const char *name, const char *list)
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 19/20] monitor: teach HMP about asynchronous commands
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

Similar to how we handle both synchronous and asynchronous commands in
QMP, HMP gains a new async_cmd() that will allow the command to
complete asynchronously. For interactive reasons, and command
ordering, the HMP monitor is suspended until the asynchronous command
completes.

It is expected that HMP async commands will be implemented re-using
QMP async commands counterparts, so it reuses the QmpSession/QmpReturn
for context handling (instead of introducing HmpSession/HmpReturn and
having to convert from one to the other as we call QMP counterparts).

hmp_dispatch_return_cb() will handle printing the result to the
current monitor. It may have different ways to print the QmpReturn
result to the current monitor. Currently, only error reporting is
implemented.

QMP human-monitor-command is modified to deal with an async HMP
commands too. It creates a temporary session, and the return callback
will return asynchronously to the original QMP command and destroy the
temporary monitor when hmp->for_qmp_command is set.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 monitor.c | 103 +++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 82 insertions(+), 21 deletions(-)

diff --git a/monitor.c b/monitor.c
index 477ccd852d..4943c7635c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -130,13 +130,17 @@ typedef struct mon_cmd_t {
     const char *params;
     const char *help;
     const char *flags; /* p=preconfig */
-    void (*cmd)(Monitor *mon, const QDict *qdict);
+    union {
+        void (*cmd)(Monitor *mon, const QDict *qdict);
+        void (*async_cmd)(Monitor *mon, const QDict *qdict, QmpReturn *qret);
+    };
     /* @sub_table is a list of 2nd level of commands. If it does not exist,
      * cmd should be used. If it exists, sub_table[?].cmd should be
      * used, and cmd of 1st level plays the role of help function.
      */
     struct mon_cmd_t *sub_table;
     void (*command_completion)(ReadLineState *rs, int nb_args, const char *str);
+    bool async;
 } mon_cmd_t;
 
 /* file descriptors passed via SCM_RIGHTS */
@@ -207,6 +211,7 @@ struct Monitor {
     int suspend_cnt;            /* Needs to be accessed atomically */
     bool skip_flush;
     bool use_io_thread;
+    QmpReturn *for_qmp_command;
 
     /*
      * State used only in the thread "owning" the monitor.
@@ -707,7 +712,7 @@ static void monitor_qapi_event_init(void)
                                                 qapi_event_throttle_equal);
 }
 
-static void handle_hmp_command(Monitor *mon, const char *cmdline);
+static bool handle_hmp_command(Monitor *mon, const char *cmdline);
 
 static void monitor_iothread_init(void);
 
@@ -743,16 +748,70 @@ static void monitor_data_destroy(Monitor *mon)
     g_queue_free(mon->qmp.qmp_requests);
 }
 
+static void free_hmp_monitor(void *opaque)
+{
+    Monitor *hmp = opaque;
+
+    qmp_session_destroy(&hmp->qmp.session);
+    monitor_data_destroy(hmp);
+    g_free(hmp);
+}
+
+static AioContext *monitor_get_aio_context(void)
+{
+    return iothread_get_aio_context(mon_iothread);
+}
+
+static void qmp_human_monitor_command_finish(Monitor *hmp, QmpReturn *qret)
+{
+    char *output;
+
+    qemu_mutex_lock(&hmp->mon_lock);
+    if (qstring_get_length(hmp->outbuf) > 0) {
+        output = g_strdup(qstring_get_str(hmp->outbuf));
+    } else {
+        output = g_strdup("");
+    }
+    qemu_mutex_unlock(&hmp->mon_lock);
+
+    qmp_human_monitor_command_return(qret, output);
+
+    if (hmp->for_qmp_command) {
+        aio_bh_schedule_oneshot(monitor_get_aio_context(),
+                                free_hmp_monitor, hmp);
+    }
+}
+
+static void hmp_dispatch_return_cb(QmpSession *session, QDict *rsp)
+{
+    Monitor *hmp = container_of(session, Monitor, qmp.session);
+    QDict *err = qdict_get_qdict(rsp, "error");
+    Monitor *old_mon = cur_mon;
+
+    cur_mon = hmp;
+    if (err) {
+        error_report("%s", qdict_get_str(err, "desc"));
+    } /* XXX: else, report depending on command */
+
+    if (hmp->for_qmp_command) {
+        qmp_human_monitor_command_finish(hmp, hmp->for_qmp_command);
+    } else {
+        monitor_resume(hmp);
+    }
+    cur_mon = old_mon;
+}
+
 void qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
                                int64_t cpu_index, QmpReturn *qret)
 {
-    char *output = NULL;
-    Monitor *old_mon, hmp;
+    Monitor *old_mon, *hmp = g_new0(Monitor, 1);
 
-    monitor_data_init(&hmp, true, false);
+    monitor_data_init(hmp, true, false);
+    qmp_session_init(&hmp->qmp.session, NULL, NULL, hmp_dispatch_return_cb);
+    hmp->for_qmp_command = qret;
 
     old_mon = cur_mon;
-    cur_mon = &hmp;
+    cur_mon = hmp;
 
     if (has_cpu_index) {
         int ret = monitor_set_cpu(cpu_index);
@@ -761,25 +820,17 @@ void qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
             error_setg(&err, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
                        "a CPU number");
             qmp_return_error(qret, err);
+            monitor_data_destroy(hmp);
             goto out;
         }
     }
 
-    handle_hmp_command(&hmp, command_line);
-
-    qemu_mutex_lock(&hmp.mon_lock);
-    if (qstring_get_length(hmp.outbuf) > 0) {
-        output = g_strdup(qstring_get_str(hmp.outbuf));
-    } else {
-        output = g_strdup("");
+    if (!handle_hmp_command(hmp, command_line)) {
+        qmp_human_monitor_command_finish(hmp, qret);
     }
-    qemu_mutex_unlock(&hmp.mon_lock);
-
-    qmp_human_monitor_command_return(qret, output);
 
 out:
     cur_mon = old_mon;
-    monitor_data_destroy(&hmp);
 }
 
 static int compare_cmd(const char *name, const char *list)
@@ -3438,7 +3489,7 @@ fail:
     return NULL;
 }
 
-static void handle_hmp_command(Monitor *mon, const char *cmdline)
+static bool handle_hmp_command(Monitor *mon, const char *cmdline)
 {
     QDict *qdict;
     const mon_cmd_t *cmd;
@@ -3448,7 +3499,7 @@ static void handle_hmp_command(Monitor *mon, const char *cmdline)
 
     cmd = monitor_parse_command(mon, cmdline, &cmdline, mon->cmd_table);
     if (!cmd) {
-        return;
+        return false;
     }
 
     qdict = monitor_parse_arguments(mon, &cmdline, cmd);
@@ -3458,11 +3509,19 @@ static void handle_hmp_command(Monitor *mon, const char *cmdline)
         }
         monitor_printf(mon, "Try \"help %.*s\" for more information\n",
                        (int)(cmdline - cmd_start), cmd_start);
-        return;
+        return false;
     }
 
-    cmd->cmd(mon, qdict);
+    if (cmd->async) {
+        QmpReturn *qret = qmp_return_new(&mon->qmp.session, NULL);
+        monitor_suspend(mon);
+        cmd->async_cmd(mon, qdict, qret);
+    } else {
+        cmd->cmd(mon, qdict);
+    }
     qobject_unref(qdict);
+
+    return cmd->async;
 }
 
 static void cmd_completion(Monitor *mon, const char *name, const char *list)
@@ -4638,6 +4697,8 @@ void monitor_init(Chardev *chr, int flags)
                                      NULL, mon, NULL, true);
         }
     } else {
+        qmp_session_init(&mon->qmp.session,
+                         NULL, NULL, hmp_dispatch_return_cb);
         qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, monitor_read,
                                  monitor_event, NULL, mon, NULL, true);
     }
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 19/20] monitor: teach HMP about asynchronous commands
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

Similar to how we handle both synchronous and asynchronous commands in
QMP, HMP gains a new async_cmd() that will allow the command to
complete asynchronously. For interactive reasons, and command
ordering, the HMP monitor is suspended until the asynchronous command
completes.

It is expected that HMP async commands will be implemented re-using
QMP async commands counterparts, so it reuses the QmpSession/QmpReturn
for context handling (instead of introducing HmpSession/HmpReturn and
having to convert from one to the other as we call QMP counterparts).

hmp_dispatch_return_cb() will handle printing the result to the
current monitor. It may have different ways to print the QmpReturn
result to the current monitor. Currently, only error reporting is
implemented.

QMP human-monitor-command is modified to deal with an async HMP
commands too. It creates a temporary session, and the return callback
will return asynchronously to the original QMP command and destroy the
temporary monitor when hmp->for_qmp_command is set.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 monitor.c | 103 +++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 82 insertions(+), 21 deletions(-)

diff --git a/monitor.c b/monitor.c
index 477ccd852d..4943c7635c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -130,13 +130,17 @@ typedef struct mon_cmd_t {
     const char *params;
     const char *help;
     const char *flags; /* p=preconfig */
-    void (*cmd)(Monitor *mon, const QDict *qdict);
+    union {
+        void (*cmd)(Monitor *mon, const QDict *qdict);
+        void (*async_cmd)(Monitor *mon, const QDict *qdict, QmpReturn *qret);
+    };
     /* @sub_table is a list of 2nd level of commands. If it does not exist,
      * cmd should be used. If it exists, sub_table[?].cmd should be
      * used, and cmd of 1st level plays the role of help function.
      */
     struct mon_cmd_t *sub_table;
     void (*command_completion)(ReadLineState *rs, int nb_args, const char *str);
+    bool async;
 } mon_cmd_t;
 
 /* file descriptors passed via SCM_RIGHTS */
@@ -207,6 +211,7 @@ struct Monitor {
     int suspend_cnt;            /* Needs to be accessed atomically */
     bool skip_flush;
     bool use_io_thread;
+    QmpReturn *for_qmp_command;
 
     /*
      * State used only in the thread "owning" the monitor.
@@ -707,7 +712,7 @@ static void monitor_qapi_event_init(void)
                                                 qapi_event_throttle_equal);
 }
 
-static void handle_hmp_command(Monitor *mon, const char *cmdline);
+static bool handle_hmp_command(Monitor *mon, const char *cmdline);
 
 static void monitor_iothread_init(void);
 
@@ -743,16 +748,70 @@ static void monitor_data_destroy(Monitor *mon)
     g_queue_free(mon->qmp.qmp_requests);
 }
 
+static void free_hmp_monitor(void *opaque)
+{
+    Monitor *hmp = opaque;
+
+    qmp_session_destroy(&hmp->qmp.session);
+    monitor_data_destroy(hmp);
+    g_free(hmp);
+}
+
+static AioContext *monitor_get_aio_context(void)
+{
+    return iothread_get_aio_context(mon_iothread);
+}
+
+static void qmp_human_monitor_command_finish(Monitor *hmp, QmpReturn *qret)
+{
+    char *output;
+
+    qemu_mutex_lock(&hmp->mon_lock);
+    if (qstring_get_length(hmp->outbuf) > 0) {
+        output = g_strdup(qstring_get_str(hmp->outbuf));
+    } else {
+        output = g_strdup("");
+    }
+    qemu_mutex_unlock(&hmp->mon_lock);
+
+    qmp_human_monitor_command_return(qret, output);
+
+    if (hmp->for_qmp_command) {
+        aio_bh_schedule_oneshot(monitor_get_aio_context(),
+                                free_hmp_monitor, hmp);
+    }
+}
+
+static void hmp_dispatch_return_cb(QmpSession *session, QDict *rsp)
+{
+    Monitor *hmp = container_of(session, Monitor, qmp.session);
+    QDict *err = qdict_get_qdict(rsp, "error");
+    Monitor *old_mon = cur_mon;
+
+    cur_mon = hmp;
+    if (err) {
+        error_report("%s", qdict_get_str(err, "desc"));
+    } /* XXX: else, report depending on command */
+
+    if (hmp->for_qmp_command) {
+        qmp_human_monitor_command_finish(hmp, hmp->for_qmp_command);
+    } else {
+        monitor_resume(hmp);
+    }
+    cur_mon = old_mon;
+}
+
 void qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
                                int64_t cpu_index, QmpReturn *qret)
 {
-    char *output = NULL;
-    Monitor *old_mon, hmp;
+    Monitor *old_mon, *hmp = g_new0(Monitor, 1);
 
-    monitor_data_init(&hmp, true, false);
+    monitor_data_init(hmp, true, false);
+    qmp_session_init(&hmp->qmp.session, NULL, NULL, hmp_dispatch_return_cb);
+    hmp->for_qmp_command = qret;
 
     old_mon = cur_mon;
-    cur_mon = &hmp;
+    cur_mon = hmp;
 
     if (has_cpu_index) {
         int ret = monitor_set_cpu(cpu_index);
@@ -761,25 +820,17 @@ void qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
             error_setg(&err, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
                        "a CPU number");
             qmp_return_error(qret, err);
+            monitor_data_destroy(hmp);
             goto out;
         }
     }
 
-    handle_hmp_command(&hmp, command_line);
-
-    qemu_mutex_lock(&hmp.mon_lock);
-    if (qstring_get_length(hmp.outbuf) > 0) {
-        output = g_strdup(qstring_get_str(hmp.outbuf));
-    } else {
-        output = g_strdup("");
+    if (!handle_hmp_command(hmp, command_line)) {
+        qmp_human_monitor_command_finish(hmp, qret);
     }
-    qemu_mutex_unlock(&hmp.mon_lock);
-
-    qmp_human_monitor_command_return(qret, output);
 
 out:
     cur_mon = old_mon;
-    monitor_data_destroy(&hmp);
 }
 
 static int compare_cmd(const char *name, const char *list)
@@ -3438,7 +3489,7 @@ fail:
     return NULL;
 }
 
-static void handle_hmp_command(Monitor *mon, const char *cmdline)
+static bool handle_hmp_command(Monitor *mon, const char *cmdline)
 {
     QDict *qdict;
     const mon_cmd_t *cmd;
@@ -3448,7 +3499,7 @@ static void handle_hmp_command(Monitor *mon, const char *cmdline)
 
     cmd = monitor_parse_command(mon, cmdline, &cmdline, mon->cmd_table);
     if (!cmd) {
-        return;
+        return false;
     }
 
     qdict = monitor_parse_arguments(mon, &cmdline, cmd);
@@ -3458,11 +3509,19 @@ static void handle_hmp_command(Monitor *mon, const char *cmdline)
         }
         monitor_printf(mon, "Try \"help %.*s\" for more information\n",
                        (int)(cmdline - cmd_start), cmd_start);
-        return;
+        return false;
     }
 
-    cmd->cmd(mon, qdict);
+    if (cmd->async) {
+        QmpReturn *qret = qmp_return_new(&mon->qmp.session, NULL);
+        monitor_suspend(mon);
+        cmd->async_cmd(mon, qdict, qret);
+    } else {
+        cmd->cmd(mon, qdict);
+    }
     qobject_unref(qdict);
+
+    return cmd->async;
 }
 
 static void cmd_completion(Monitor *mon, const char *name, const char *list)
@@ -4638,6 +4697,8 @@ void monitor_init(Chardev *chr, int flags)
                                      NULL, mon, NULL, true);
         }
     } else {
+        qmp_session_init(&mon->qmp.session,
+                         NULL, NULL, hmp_dispatch_return_cb);
         qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, monitor_read,
                                  monitor_event, NULL, mon, NULL, true);
     }
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 20/20] hmp: call the asynchronous QMP screendump to fix outdated/glitches
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Eric Blake, Markus Armbruster, Dr. David Alan Gilbert,
	Gerd Hoffmann, Michael Roth, Marc-André Lureau

In order to fix the bad screendumps (same as rhbz#1230527), call into
the asynchonous version of the QMP command.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 hmp.h           |  3 ++-
 hmp.c           |  6 ++----
 ui/console.c    | 22 ----------------------
 hmp-commands.hx |  3 ++-
 4 files changed, 6 insertions(+), 28 deletions(-)

diff --git a/hmp.h b/hmp.h
index 43617f2646..4059c8bea9 100644
--- a/hmp.h
+++ b/hmp.h
@@ -16,6 +16,7 @@
 
 #include "qemu-common.h"
 #include "qemu/readline.h"
+#include "qapi/qmp/dispatch.h"
 
 void hmp_info_name(Monitor *mon, const QDict *qdict);
 void hmp_info_version(Monitor *mon, const QDict *qdict);
@@ -102,7 +103,7 @@ void hmp_netdev_del(Monitor *mon, const QDict *qdict);
 void hmp_getfd(Monitor *mon, const QDict *qdict);
 void hmp_closefd(Monitor *mon, const QDict *qdict);
 void hmp_sendkey(Monitor *mon, const QDict *qdict);
-void hmp_screendump(Monitor *mon, const QDict *qdict);
+void hmp_screendump_async(Monitor *mon, const QDict *qdict, QmpReturn *qret);
 void hmp_nbd_server_start(Monitor *mon, const QDict *qdict);
 void hmp_nbd_server_add(Monitor *mon, const QDict *qdict);
 void hmp_nbd_server_remove(Monitor *mon, const QDict *qdict);
diff --git a/hmp.c b/hmp.c
index 9428c90c5d..60d2bc1c76 100644
--- a/hmp.c
+++ b/hmp.c
@@ -2373,15 +2373,13 @@ err_out:
     goto out;
 }
 
-void hmp_screendump(Monitor *mon, const QDict *qdict)
+void hmp_screendump_async(Monitor *mon, const QDict *qdict, QmpReturn *qret)
 {
     const char *filename = qdict_get_str(qdict, "filename");
     const char *id = qdict_get_try_str(qdict, "device");
     int64_t head = qdict_get_try_int(qdict, "head", 0);
-    Error *err = NULL;
 
-    hmp_screendump_sync(filename, id != NULL, id, id != NULL, head, &err);
-    hmp_handle_error(mon, &err);
+    qmp_screendump(filename, id != NULL, id, id != NULL, head, qret);
 }
 
 void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
diff --git a/ui/console.c b/ui/console.c
index 620cd14b80..c259cf68aa 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -429,28 +429,6 @@ static QemuConsole *get_console(bool has_device, const char *device,
     return con;
 }
 
-void hmp_screendump_sync(const char *filename,
-                         bool has_device, const char *device,
-                         bool has_head, int64_t head, Error **errp)
-{
-    DisplaySurface *surface;
-    QemuConsole *con = get_console(has_device, device, has_head, head, errp);
-
-    if (!con) {
-        return;
-    }
-    /* This may not complete the drawing with Spice, you may have
-     * glitches or outdated dumps, use qmp instead! */
-    graphic_hw_update(con);
-    surface = qemu_console_surface(con);
-    if (!surface) {
-        error_setg(errp, "no surface");
-        return;
-    }
-
-    ppm_save(filename, surface, errp);
-}
-
 void qmp_screendump(const char *filename,
                     bool has_device, const char *device,
                     bool has_head, int64_t head,
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 9b4035965c..638b59e764 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -278,7 +278,8 @@ ETEXI
         .params     = "filename [device [head]]",
         .help       = "save screen from head 'head' of display device 'device' "
                       "into PPM image 'filename'",
-        .cmd        = hmp_screendump,
+        .async_cmd  = hmp_screendump_async,
+        .async      = true,
     },
 
 STEXI
-- 
2.21.0.196.g041f5ea1cf

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [Qemu-devel] [PATCH v4 20/20] hmp: call the asynchronous QMP screendump to fix outdated/glitches
@ 2019-04-09 16:10   ` Marc-André Lureau
  0 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-04-09 16:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: Michael Roth, Markus Armbruster, Gerd Hoffmann,
	Marc-André Lureau, Dr. David Alan Gilbert

In order to fix the bad screendumps (same as rhbz#1230527), call into
the asynchonous version of the QMP command.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 hmp.h           |  3 ++-
 hmp.c           |  6 ++----
 ui/console.c    | 22 ----------------------
 hmp-commands.hx |  3 ++-
 4 files changed, 6 insertions(+), 28 deletions(-)

diff --git a/hmp.h b/hmp.h
index 43617f2646..4059c8bea9 100644
--- a/hmp.h
+++ b/hmp.h
@@ -16,6 +16,7 @@
 
 #include "qemu-common.h"
 #include "qemu/readline.h"
+#include "qapi/qmp/dispatch.h"
 
 void hmp_info_name(Monitor *mon, const QDict *qdict);
 void hmp_info_version(Monitor *mon, const QDict *qdict);
@@ -102,7 +103,7 @@ void hmp_netdev_del(Monitor *mon, const QDict *qdict);
 void hmp_getfd(Monitor *mon, const QDict *qdict);
 void hmp_closefd(Monitor *mon, const QDict *qdict);
 void hmp_sendkey(Monitor *mon, const QDict *qdict);
-void hmp_screendump(Monitor *mon, const QDict *qdict);
+void hmp_screendump_async(Monitor *mon, const QDict *qdict, QmpReturn *qret);
 void hmp_nbd_server_start(Monitor *mon, const QDict *qdict);
 void hmp_nbd_server_add(Monitor *mon, const QDict *qdict);
 void hmp_nbd_server_remove(Monitor *mon, const QDict *qdict);
diff --git a/hmp.c b/hmp.c
index 9428c90c5d..60d2bc1c76 100644
--- a/hmp.c
+++ b/hmp.c
@@ -2373,15 +2373,13 @@ err_out:
     goto out;
 }
 
-void hmp_screendump(Monitor *mon, const QDict *qdict)
+void hmp_screendump_async(Monitor *mon, const QDict *qdict, QmpReturn *qret)
 {
     const char *filename = qdict_get_str(qdict, "filename");
     const char *id = qdict_get_try_str(qdict, "device");
     int64_t head = qdict_get_try_int(qdict, "head", 0);
-    Error *err = NULL;
 
-    hmp_screendump_sync(filename, id != NULL, id, id != NULL, head, &err);
-    hmp_handle_error(mon, &err);
+    qmp_screendump(filename, id != NULL, id, id != NULL, head, qret);
 }
 
 void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
diff --git a/ui/console.c b/ui/console.c
index 620cd14b80..c259cf68aa 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -429,28 +429,6 @@ static QemuConsole *get_console(bool has_device, const char *device,
     return con;
 }
 
-void hmp_screendump_sync(const char *filename,
-                         bool has_device, const char *device,
-                         bool has_head, int64_t head, Error **errp)
-{
-    DisplaySurface *surface;
-    QemuConsole *con = get_console(has_device, device, has_head, head, errp);
-
-    if (!con) {
-        return;
-    }
-    /* This may not complete the drawing with Spice, you may have
-     * glitches or outdated dumps, use qmp instead! */
-    graphic_hw_update(con);
-    surface = qemu_console_surface(con);
-    if (!surface) {
-        error_setg(errp, "no surface");
-        return;
-    }
-
-    ppm_save(filename, surface, errp);
-}
-
 void qmp_screendump(const char *filename,
                     bool has_device, const char *device,
                     bool has_head, int64_t head,
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 9b4035965c..638b59e764 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -278,7 +278,8 @@ ETEXI
         .params     = "filename [device [head]]",
         .help       = "save screen from head 'head' of display device 'device' "
                       "into PPM image 'filename'",
-        .cmd        = hmp_screendump,
+        .async_cmd  = hmp_screendump_async,
+        .async      = true,
     },
 
 STEXI
-- 
2.21.0.196.g041f5ea1cf



^ permalink raw reply related	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
@ 2019-04-09 19:05   ` no-reply
  0 siblings, 0 replies; 64+ messages in thread
From: no-reply @ 2019-04-09 19:05 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: fam, qemu-devel, mdroth, armbru, kraxel, dgilbert

Patchew URL: https://patchew.org/QEMU/20190409161009.6322-1-marcandre.lureau@redhat.com/



Hi,

This series failed the asan build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
time make docker-test-debug@fedora TARGET_LIST=x86_64-softmmu J=14 NETWORK=1
=== TEST SCRIPT END ===




The full log is available at
http://patchew.org/logs/20190409161009.6322-1-marcandre.lureau@redhat.com/testing.asan/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
@ 2019-04-09 19:05   ` no-reply
  0 siblings, 0 replies; 64+ messages in thread
From: no-reply @ 2019-04-09 19:05 UTC (permalink / raw)
  To: marcandre.lureau
  Cc: fam, mdroth, armbru, qemu-devel, kraxel, marcandre.lureau, dgilbert

Patchew URL: https://patchew.org/QEMU/20190409161009.6322-1-marcandre.lureau@redhat.com/



Hi,

This series failed the asan build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
time make docker-test-debug@fedora TARGET_LIST=x86_64-softmmu J=14 NETWORK=1
=== TEST SCRIPT END ===




The full log is available at
http://patchew.org/logs/20190409161009.6322-1-marcandre.lureau@redhat.com/testing.asan/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
@ 2019-04-09 19:07   ` no-reply
  0 siblings, 0 replies; 64+ messages in thread
From: no-reply @ 2019-04-09 19:07 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: fam, qemu-devel, mdroth, armbru, kraxel, dgilbert

Patchew URL: https://patchew.org/QEMU/20190409161009.6322-1-marcandre.lureau@redhat.com/



Hi,

This series failed the docker-mingw@fedora build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
time make docker-test-mingw@fedora SHOW_ENV=1 J=14 NETWORK=1
=== TEST SCRIPT END ===




The full log is available at
http://patchew.org/logs/20190409161009.6322-1-marcandre.lureau@redhat.com/testing.docker-mingw@fedora/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
@ 2019-04-09 19:07   ` no-reply
  0 siblings, 0 replies; 64+ messages in thread
From: no-reply @ 2019-04-09 19:07 UTC (permalink / raw)
  To: marcandre.lureau
  Cc: fam, mdroth, armbru, qemu-devel, kraxel, marcandre.lureau, dgilbert

Patchew URL: https://patchew.org/QEMU/20190409161009.6322-1-marcandre.lureau@redhat.com/



Hi,

This series failed the docker-mingw@fedora build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
time make docker-test-mingw@fedora SHOW_ENV=1 J=14 NETWORK=1
=== TEST SCRIPT END ===




The full log is available at
http://patchew.org/logs/20190409161009.6322-1-marcandre.lureau@redhat.com/testing.docker-mingw@fedora/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 16/20] console: add graphic_hw_update_done()
@ 2019-04-10  8:43     ` Gerd Hoffmann
  0 siblings, 0 replies; 64+ messages in thread
From: Gerd Hoffmann @ 2019-04-10  8:43 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	Dr. David Alan Gilbert, Michael Roth

On Tue, Apr 09, 2019 at 06:10:05PM +0200, Marc-André Lureau wrote:
> Add a function to be called when a graphic update is done.
> 
> Declare the QXL renderer as async: render_update_cookie_num counts the
> number of outstanding updates, and graphic_hw_update_done() is called
> when it reaches none.

Reviewed-by: Gerd Hoffmann <kraxel@redhat.com>

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 16/20] console: add graphic_hw_update_done()
@ 2019-04-10  8:43     ` Gerd Hoffmann
  0 siblings, 0 replies; 64+ messages in thread
From: Gerd Hoffmann @ 2019-04-10  8:43 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Michael Roth, qemu-devel, Dr. David Alan Gilbert, Markus Armbruster

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="UTF-8", Size: 360 bytes --]

On Tue, Apr 09, 2019 at 06:10:05PM +0200, Marc-André Lureau wrote:
> Add a function to be called when a graphic update is done.
> 
> Declare the QXL renderer as async: render_update_cookie_num counts the
> number of outstanding updates, and graphic_hw_update_done() is called
> when it reaches none.

Reviewed-by: Gerd Hoffmann <kraxel@redhat.com>



^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous
@ 2019-04-10  8:48     ` Gerd Hoffmann
  0 siblings, 0 replies; 64+ messages in thread
From: Gerd Hoffmann @ 2019-04-10  8:48 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	Dr. David Alan Gilbert, Michael Roth

> +static void qmp_screendump_finish(QemuConsole *con, struct qmp_screendump *dump)
> +{
> +    Error *err = NULL;
> +    DisplaySurface *surface;
> +    Monitor *prev_mon = cur_mon;

Why this is needed?

> +        /*
> +         * FIXME: async save with coroutine? it would have to copy or
> +         * lock the surface.
> +         */
> +        ppm_save(dump->filename, surface, &err);

DisplaySurface is just a thin layer above pixman images these days.
Pixman images are reference counted, so you can
pixman_image_ref(surface->image) to make sure it doesn't disappear
underneath you, then pass the pixman image to ppm_save.

cheers,
  Gerd

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous
@ 2019-04-10  8:48     ` Gerd Hoffmann
  0 siblings, 0 replies; 64+ messages in thread
From: Gerd Hoffmann @ 2019-04-10  8:48 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Michael Roth, qemu-devel, Dr. David Alan Gilbert, Markus Armbruster

> +static void qmp_screendump_finish(QemuConsole *con, struct qmp_screendump *dump)
> +{
> +    Error *err = NULL;
> +    DisplaySurface *surface;
> +    Monitor *prev_mon = cur_mon;

Why this is needed?

> +        /*
> +         * FIXME: async save with coroutine? it would have to copy or
> +         * lock the surface.
> +         */
> +        ppm_save(dump->filename, surface, &err);

DisplaySurface is just a thin layer above pixman images these days.
Pixman images are reference counted, so you can
pixman_image_ref(surface->image) to make sure it doesn't disappear
underneath you, then pass the pixman image to ppm_save.

cheers,
  Gerd



^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-04-09 16:09 ` Marc-André Lureau
                   ` (22 preceding siblings ...)
  (?)
@ 2019-05-17 22:21 ` Marc-André Lureau
  -1 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-05-17 22:21 UTC (permalink / raw)
  To: QEMU
  Cc: Gerd Hoffmann, Michael Roth, Dr. David Alan Gilbert, Markus Armbruster

On Tue, Apr 9, 2019 at 6:12 PM Marc-André Lureau
<marcandre.lureau@redhat.com> wrote:
>
> Hi,
>
> HMP and QMP commands are handled synchronously in qemu today. But
> there are benefits allowing the command handler to re-enter the main
> loop if the command cannot be handled synchronously, or if it is
> long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
> without it.
>
> The common solution is to use a pair of command+event in this case.
> But this approach has a number of issues:
> - you can't "fix" an existing command: you need a new API, and ad-hoc
>   documentation for that command+signal association, and old/broken
>   command deprecation
> - since the reply event is broadcasted and 'id' is used for matching the
>   request, it may conflict with other clients request 'id' space
> - it is arguably less efficient and elegant (weird API, useless return
>   in most cases, broadcast reply, no cancelling on disconnect etc)
>
> The following series implements an async command solution instead. By
> introducing a session context and a command return handler, it can:
> - defer the return, allowing the mainloop to reenter
> - return only to the caller (instead of broadcast events for reply)
> - optionnally allow cancellation when the client is gone
> - track on-going qapi command(s) per client/session
>
> and without introduction of new QMP APIs or client visible change.
>
> Existing qemu commands can be gradually replaced by async:true
> variants when needed, while carefully reviewing the concurrency
> aspects. The async:true commands marshaller helpers are splitted in
> half, the calling and return functions. The command is called with a
> QmpReturn context, that can return immediately or later, using the
> generated return helper, which allows for a step-by-step conversion.
>
> The screendump command is converted to an async:true version to solve
> rhbz#1230527. The command shows basic cancellation (this could be
> extended if needed). It could be further improved to do asynchronous
> IO writes as well.
>
> v4:
> - rebased, mostly adapting to new OOB code
>   (there was not much feedback in v3 for the async command part,
>    but preliminary patches got merged!)
> - drop the RFC status

ping

>
> v3:
> - complete rework, dropping the asynchronous commands visibility from
>   the protocol side entirely (until there is a real need for it)
> - rebased, with a few preliminary cleanup patches
> - teach asynchronous commands to HMP
>
> v2:
> - documentation fixes and improvements
> - fix calling async commands sync without id
> - fix bad hmp monitor assert
> - add a few extra asserts
> - add async with no-id failure and screendump test
>
> Marc-André Lureau (20):
>   qmp: constify QmpCommand and list
>   json-lexer: make it safe to call destroy multiple times
>   qmp: add QmpSession
>   QmpSession: add a return callback
>   QmpSession: add json parser and use it in qga
>   monitor: use qmp session to parse json feed
>   qga: simplify dispatch_return_cb
>   QmpSession: introduce QmpReturn
>   qmp: simplify qmp_return_error()
>   QmpSession: keep a queue of pending commands
>   QmpSession: return orderly
>   qmp: introduce asynchronous command type
>   scripts: learn 'async' qapi commands
>   qmp: add qmp_return_is_cancelled()
>   monitor: add qmp_return_get_monitor()
>   console: add graphic_hw_update_done()
>   console: make screendump asynchronous
>   monitor: start making qmp_human_monitor_command() asynchronous
>   monitor: teach HMP about asynchronous commands
>   hmp: call the asynchronous QMP screendump to fix outdated/glitches
>
>  qapi/misc.json                          |   3 +-
>  qapi/ui.json                            |   3 +-
>  scripts/qapi/commands.py                | 151 ++++++++++++++---
>  scripts/qapi/common.py                  |  15 +-
>  scripts/qapi/doc.py                     |   3 +-
>  scripts/qapi/introspect.py              |   3 +-
>  hmp.h                                   |   3 +-
>  include/monitor/monitor.h               |   3 +
>  include/qapi/qmp/dispatch.h             |  89 +++++++++-
>  include/qapi/qmp/json-parser.h          |   7 +-
>  include/ui/console.h                    |   5 +
>  hmp.c                                   |   6 +-
>  hw/display/qxl-render.c                 |   9 +-
>  hw/display/qxl.c                        |   1 +
>  monitor.c                               | 198 ++++++++++++++--------
>  qapi/qmp-dispatch.c                     | 214 +++++++++++++++++++-----
>  qapi/qmp-registry.c                     |  33 +++-
>  qga/commands.c                          |   2 +-
>  qga/main.c                              |  51 ++----
>  qobject/json-lexer.c                    |   5 +-
>  qobject/json-streamer.c                 |   3 +-
>  tests/test-qmp-cmds.c                   | 206 +++++++++++++++++++----
>  ui/console.c                            | 100 +++++++++--
>  hmp-commands.hx                         |   3 +-
>  tests/qapi-schema/qapi-schema-test.json |   5 +
>  tests/qapi-schema/qapi-schema-test.out  |   8 +
>  tests/qapi-schema/test-qapi.py          |   8 +-
>  27 files changed, 877 insertions(+), 260 deletions(-)
>
> --
> 2.21.0.196.g041f5ea1cf
>
>


-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-04-09 16:09 ` Marc-André Lureau
                   ` (23 preceding siblings ...)
  (?)
@ 2019-05-21 14:17 ` Markus Armbruster
  2019-05-21 14:50   ` Marc-André Lureau
  2019-05-21 20:50   ` John Snow
  -1 siblings, 2 replies; 64+ messages in thread
From: Markus Armbruster @ 2019-05-21 14:17 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Kevin Wolf, Michael Roth, qemu-devel, Dr. David Alan Gilbert,
	Gerd Hoffmann, John Snow

Marc-André, before you invest your time to answer my questions below: my
bandwidth for non-trivial QAPI features like this one is painfully
limited.  To get your QAPI conditionals in, I had to postpone other QAPI
projects.  I don't regret doing that, I'm rather pleased with how QAPI
conditionals turned out.  But I can't keep postponing all other QAPI
projects.  Because of that, this one will be slow going, at best.  Sorry
about that.

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Hi,
>
> HMP and QMP commands are handled synchronously in qemu today. But
> there are benefits allowing the command handler to re-enter the main
> loop if the command cannot be handled synchronously, or if it is
> long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
> without it.
>
> The common solution is to use a pair of command+event in this case.

In particular, background jobs (qapi/jobs.json).  They grew out of block
jobs, and are still used only for "blocky" things.  Using them more
widely would probably make sense.

> But this approach has a number of issues:
> - you can't "fix" an existing command: you need a new API, and ad-hoc
>   documentation for that command+signal association, and old/broken
>   command deprecation

Making a synchronous command asynchronous is an incompatible change.  We
need to let the client needs opt in.  How is that done in this series?

> - since the reply event is broadcasted and 'id' is used for matching the
>   request, it may conflict with other clients request 'id' space

Any event that does that now is broken and needs to be fixed.  The
obvious fix is to include a monitor ID with the command ID.  For events
that can only ever be useful in the context of one particular monitor,
we could unicast to that monitor instead; see below.

Corollary: this is just a fixable bug, not a fundamental advantage of
the async feature.

> - it is arguably less efficient and elegant (weird API, useless return
>   in most cases, broadcast reply, no cancelling on disconnect etc)

The return value is useful for synchronously reporting failure to start
the background task.  I grant you that background tasks may exist that
won't ever fail to start.  I challenge the idea that it's most of them.

Broadcast reply could be avoided by unicasting events.  If I remember
correctly, Peter Xu even posted patches some time ago.  We ended up not
using them, because we found a better solution for the problem at hand.
My point is: this isn't a fundamental problem, it's just the way we
coded things up.

What do you mean by "no cancelling on disconnect"?

I'm ignoring "etc" unless you expand it into something specific.

I'm also not taking the "weird" bait :)

> The following series implements an async command solution instead. By
> introducing a session context and a command return handler, it can:
> - defer the return, allowing the mainloop to reenter
> - return only to the caller (instead of broadcast events for reply)
> - optionnally allow cancellation when the client is gone
> - track on-going qapi command(s) per client/session
>
> and without introduction of new QMP APIs or client visible change.

What do async commands provide that jobs lack?

Why do we want both?

I started to write a feature-by-feature comparison, but realized I don't
have the time to figure out either jobs or async from their (rather
sparse) documentation, let alone from code.

> Existing qemu commands can be gradually replaced by async:true
> variants when needed, while carefully reviewing the concurrency
> aspects. The async:true commands marshaller helpers are splitted in
> half, the calling and return functions. The command is called with a
> QmpReturn context, that can return immediately or later, using the
> generated return helper, which allows for a step-by-step conversion.
>
> The screendump command is converted to an async:true version to solve
> rhbz#1230527. The command shows basic cancellation (this could be
> extended if needed). It could be further improved to do asynchronous
> IO writes as well.

What is "basic cancellation"?

What extension(s) do you have in mind?

What's the impact of screendump writing synchronously?


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-21 14:17 ` Markus Armbruster
@ 2019-05-21 14:50   ` Marc-André Lureau
  2019-05-23  7:52     ` Markus Armbruster
  2019-05-21 20:50   ` John Snow
  1 sibling, 1 reply; 64+ messages in thread
From: Marc-André Lureau @ 2019-05-21 14:50 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, QEMU, Michael Roth, Dr. David Alan Gilbert,
	Gerd Hoffmann, John Snow

Hi

On Tue, May 21, 2019 at 4:18 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Marc-André, before you invest your time to answer my questions below: my
> bandwidth for non-trivial QAPI features like this one is painfully
> limited.  To get your QAPI conditionals in, I had to postpone other QAPI
> projects.  I don't regret doing that, I'm rather pleased with how QAPI
> conditionals turned out.  But I can't keep postponing all other QAPI
> projects.  Because of that, this one will be slow going, at best.  Sorry
> about that.

We have different priorities, fair enough.

>
> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
>
> > Hi,
> >
> > HMP and QMP commands are handled synchronously in qemu today. But
> > there are benefits allowing the command handler to re-enter the main
> > loop if the command cannot be handled synchronously, or if it is
> > long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
> > without it.
> >
> > The common solution is to use a pair of command+event in this case.
>
> In particular, background jobs (qapi/jobs.json).  They grew out of block
> jobs, and are still used only for "blocky" things.  Using them more
> widely would probably make sense.
>
> > But this approach has a number of issues:
> > - you can't "fix" an existing command: you need a new API, and ad-hoc
> >   documentation for that command+signal association, and old/broken
> >   command deprecation
>
> Making a synchronous command asynchronous is an incompatible change.  We
> need to let the client needs opt in.  How is that done in this series?

No change visible on client side. I dropped the async command support
a while ago already, based on your recommendations. I can dig the
archive for the discussion if necessary.

>
> > - since the reply event is broadcasted and 'id' is used for matching the
> >   request, it may conflict with other clients request 'id' space
>
> Any event that does that now is broken and needs to be fixed.  The
> obvious fix is to include a monitor ID with the command ID.  For events
> that can only ever be useful in the context of one particular monitor,
> we could unicast to that monitor instead; see below.
>
> Corollary: this is just a fixable bug, not a fundamental advantage of
> the async feature.

I am just pointing out today drawbacks of turning a function async by
introducing new commands and signals.

>
> > - it is arguably less efficient and elegant (weird API, useless return
> >   in most cases, broadcast reply, no cancelling on disconnect etc)
>
> The return value is useful for synchronously reporting failure to start
> the background task.  I grant you that background tasks may exist that
> won't ever fail to start.  I challenge the idea that it's most of them.
>
> Broadcast reply could be avoided by unicasting events.  If I remember
> correctly, Peter Xu even posted patches some time ago.  We ended up not
> using them, because we found a better solution for the problem at hand.
> My point is: this isn't a fundamental problem, it's just the way we
> coded things up.
>
> What do you mean by "no cancelling on disconnect"?

When the client disconnects, the background task keeps running, and
there is no simple way to know about that event afaik. My proposal has
a simple API for that (see "qmp: add qmp_return_is_cancelled()"
patch).

>
> I'm ignoring "etc" unless you expand it into something specific.
>
> I'm also not taking the "weird" bait :)
> > The following series implements an async command solution instead. By
> > introducing a session context and a command return handler, it can:
> > - defer the return, allowing the mainloop to reenter
> > - return only to the caller (instead of broadcast events for reply)
> > - optionnally allow cancellation when the client is gone
> > - track on-going qapi command(s) per client/session
> >
> > and without introduction of new QMP APIs or client visible change.
>
> What do async commands provide that jobs lack?
>
> Why do we want both?

They are different things, last we discussed it: jobs are geared
toward block device operations, and do not provide simple qmp-level
facilities that I listed above. What I introduce is a way for an
*existing* QMP command to be splitted, so it can re-enter the main
loop sanely (and not by introducing new commands or signals or making
things unnecessarily more complicated).

My proposal is fairly small:
  27 files changed, 877 insertions(+), 260 deletions(-)

Including test, and the qxl screendump fix, which account for about
1/3 of the series.

> I started to write a feature-by-feature comparison, but realized I don't
> have the time to figure out either jobs or async from their (rather
> sparse) documentation, let alone from code.
>
> > Existing qemu commands can be gradually replaced by async:true
> > variants when needed, while carefully reviewing the concurrency
> > aspects. The async:true commands marshaller helpers are splitted in
> > half, the calling and return functions. The command is called with a
> > QmpReturn context, that can return immediately or later, using the
> > generated return helper, which allows for a step-by-step conversion.
> >
> > The screendump command is converted to an async:true version to solve
> > rhbz#1230527. The command shows basic cancellation (this could be
> > extended if needed). It could be further improved to do asynchronous
> > IO writes as well.
>
> What is "basic cancellation"?
> What extension(s) do you have in mind?

It checks for cancellation in a few places, between IO. Full
cancellation would allow to cancel at any time.

>
> What's the impact of screendump writing synchronously?

It can be pretty bad, think about 4k screens. It is 33177600 bytes,
written in PPM format, blocking the main loop..

QMP operation doing large IO (dumps), or blocking on events, could be
switched to this async form without introducing user-visible change,
and with minimal effort compared to jobs.


-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-21 14:17 ` Markus Armbruster
  2019-05-21 14:50   ` Marc-André Lureau
@ 2019-05-21 20:50   ` John Snow
  1 sibling, 0 replies; 64+ messages in thread
From: John Snow @ 2019-05-21 20:50 UTC (permalink / raw)
  To: Markus Armbruster, Marc-André Lureau
  Cc: Kevin Wolf, Gerd Hoffmann, qemu-devel, Dr. David Alan Gilbert,
	Michael Roth



On 5/21/19 10:17 AM, Markus Armbruster wrote:
> Marc-André, before you invest your time to answer my questions below: my
> bandwidth for non-trivial QAPI features like this one is painfully
> limited.  To get your QAPI conditionals in, I had to postpone other QAPI
> projects.  I don't regret doing that, I'm rather pleased with how QAPI
> conditionals turned out.  But I can't keep postponing all other QAPI
> projects.  Because of that, this one will be slow going, at best.  Sorry
> about that.
> 
> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> 
>> Hi,
>>
>> HMP and QMP commands are handled synchronously in qemu today. But
>> there are benefits allowing the command handler to re-enter the main
>> loop if the command cannot be handled synchronously, or if it is
>> long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
>> without it.
>>
>> The common solution is to use a pair of command+event in this case.
> 
> In particular, background jobs (qapi/jobs.json).  They grew out of block
> jobs, and are still used only for "blocky" things.  Using them more
> widely would probably make sense.
> >> But this approach has a number of issues:
>> - you can't "fix" an existing command: you need a new API, and ad-hoc
>>   documentation for that command+signal association, and old/broken
>>   command deprecation
> 
> Making a synchronous command asynchronous is an incompatible change.  We
> need to let the client needs opt in.  How is that done in this series?
> 
>> - since the reply event is broadcasted and 'id' is used for matching the
>>   request, it may conflict with other clients request 'id' space
> 
> Any event that does that now is broken and needs to be fixed.  The
> obvious fix is to include a monitor ID with the command ID.  For events
> that can only ever be useful in the context of one particular monitor,
> we could unicast to that monitor instead; see below.
> 
> Corollary: this is just a fixable bug, not a fundamental advantage of
> the async feature.
> 
>> - it is arguably less efficient and elegant (weird API, useless return
>>   in most cases, broadcast reply, no cancelling on disconnect etc)
> 
> The return value is useful for synchronously reporting failure to start
> the background task.  I grant you that background tasks may exist that
> won't ever fail to start.  I challenge the idea that it's most of them.
> 
> Broadcast reply could be avoided by unicasting events.  If I remember
> correctly, Peter Xu even posted patches some time ago.  We ended up not
> using them, because we found a better solution for the problem at hand.
> My point is: this isn't a fundamental problem, it's just the way we
> coded things up.
> 
> What do you mean by "no cancelling on disconnect"?
> 
> I'm ignoring "etc" unless you expand it into something specific.
> 
> I'm also not taking the "weird" bait :)
> 
>> The following series implements an async command solution instead. By
>> introducing a session context and a command return handler, it can:
>> - defer the return, allowing the mainloop to reenter
>> - return only to the caller (instead of broadcast events for reply)
>> - optionnally allow cancellation when the client is gone
>> - track on-going qapi command(s) per client/session
>>
>> and without introduction of new QMP APIs or client visible change.
> 
> What do async commands provide that jobs lack?
> 
> Why do we want both?
> 
> I started to write a feature-by-feature comparison, but realized I don't
> have the time to figure out either jobs or async from their (rather
> sparse) documentation, let alone from code.
> 

Sorry about that. I still have a todo item from you in my inbox to
improve the documentation there, but I've been focusing on bitmaps
documentation lately instead, but I hope to branch it out as part of my
caring a bit more about docs lately.

I'll keep an eye out here. I don't really want to prohibit things on the
sole basis that they aren't jobs, but I do want to make sure that adding
a new lifecycle paradigm for commands doesn't complicate the jobs code
in accidental ways.

I'll try to look this over too, but I have a bit of a backlog right now.

>> Existing qemu commands can be gradually replaced by async:true
>> variants when needed, while carefully reviewing the concurrency
>> aspects. The async:true commands marshaller helpers are splitted in
>> half, the calling and return functions. The command is called with a
>> QmpReturn context, that can return immediately or later, using the
>> generated return helper, which allows for a step-by-step conversion.
>>
>> The screendump command is converted to an async:true version to solve
>> rhbz#1230527. The command shows basic cancellation (this could be
>> extended if needed). It could be further improved to do asynchronous
>> IO writes as well.
> 
> What is "basic cancellation"?
> 
> What extension(s) do you have in mind?
> 
> What's the impact of screendump writing synchronously?
> 



^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-21 14:50   ` Marc-André Lureau
@ 2019-05-23  7:52     ` Markus Armbruster
  2019-05-25 12:12       ` Marc-André Lureau
  0 siblings, 1 reply; 64+ messages in thread
From: Markus Armbruster @ 2019-05-23  7:52 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Kevin Wolf, Michael Roth, QEMU, Dr. David Alan Gilbert,
	Gerd Hoffmann, John Snow

Marc-André Lureau <marcandre.lureau@gmail.com> writes:

> Hi
>
> On Tue, May 21, 2019 at 4:18 PM Markus Armbruster <armbru@redhat.com> wrote:
>>
>> Marc-André, before you invest your time to answer my questions below: my
>> bandwidth for non-trivial QAPI features like this one is painfully
>> limited.  To get your QAPI conditionals in, I had to postpone other QAPI
>> projects.  I don't regret doing that, I'm rather pleased with how QAPI
>> conditionals turned out.  But I can't keep postponing all other QAPI
>> projects.  Because of that, this one will be slow going, at best.  Sorry
>> about that.
>
> We have different priorities, fair enough.

I wish I could give you better service.  But no use pretending.

>> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
>>
>> > Hi,
>> >
>> > HMP and QMP commands are handled synchronously in qemu today. But
>> > there are benefits allowing the command handler to re-enter the main
>> > loop if the command cannot be handled synchronously, or if it is
>> > long-lasting. Some bugs such as rhbz#1230527 are difficult to solve
>> > without it.
>> >
>> > The common solution is to use a pair of command+event in this case.
>>
>> In particular, background jobs (qapi/jobs.json).  They grew out of block
>> jobs, and are still used only for "blocky" things.  Using them more
>> widely would probably make sense.
>>
>> > But this approach has a number of issues:
>> > - you can't "fix" an existing command: you need a new API, and ad-hoc
>> >   documentation for that command+signal association, and old/broken
>> >   command deprecation
>>
>> Making a synchronous command asynchronous is an incompatible change.  We
>> need to let the client needs opt in.  How is that done in this series?
>
> No change visible on client side. I dropped the async command support
> a while ago already, based on your recommendations. I can dig the
> archive for the discussion if necessary.

Not right now.

>> > - since the reply event is broadcasted and 'id' is used for matching the
>> >   request, it may conflict with other clients request 'id' space
>>
>> Any event that does that now is broken and needs to be fixed.  The
>> obvious fix is to include a monitor ID with the command ID.  For events
>> that can only ever be useful in the context of one particular monitor,
>> we could unicast to that monitor instead; see below.
>>
>> Corollary: this is just a fixable bug, not a fundamental advantage of
>> the async feature.
>
> I am just pointing out today drawbacks of turning a function async by
> introducing new commands and signals.

And I'm just pointing out that some of today's drawbacks could also be
addressed differently :)

>> > - it is arguably less efficient and elegant (weird API, useless return
>> >   in most cases, broadcast reply, no cancelling on disconnect etc)
>>
>> The return value is useful for synchronously reporting failure to start
>> the background task.  I grant you that background tasks may exist that
>> won't ever fail to start.  I challenge the idea that it's most of them.
>>
>> Broadcast reply could be avoided by unicasting events.  If I remember
>> correctly, Peter Xu even posted patches some time ago.  We ended up not
>> using them, because we found a better solution for the problem at hand.
>> My point is: this isn't a fundamental problem, it's just the way we
>> coded things up.
>>
>> What do you mean by "no cancelling on disconnect"?
>
> When the client disconnects, the background task keeps running, and
> there is no simple way to know about that event afaik. My proposal has
> a simple API for that (see "qmp: add qmp_return_is_cancelled()"
> patch).

Auto-cancellation on client disconnect may be exactly what's wanted for
simple use cases.

Jobs are designed with more use cases in mind.  Consider a backup job
that's take some time.  We certainly don't want to cancel it just
because the management application hiccups and disconnects.  Instead, we
want to permit the management application to recover, reconnect, find
the backup job, examine its state, and resume managing it.  To support
this, jobs have a unique ID.  Job cancellation is explicit.

Jobs could acquire a "auto-cancel on disconnect" feature if there's a
need.

I'm not sure how asynchronous commands could support reconnect and
resume.

>> I'm ignoring "etc" unless you expand it into something specific.
>>
>> I'm also not taking the "weird" bait :)
>> > The following series implements an async command solution instead. By
>> > introducing a session context and a command return handler, it can:
>> > - defer the return, allowing the mainloop to reenter
>> > - return only to the caller (instead of broadcast events for reply)
>> > - optionnally allow cancellation when the client is gone
>> > - track on-going qapi command(s) per client/session
>> >
>> > and without introduction of new QMP APIs or client visible change.
>>
>> What do async commands provide that jobs lack?
>>
>> Why do we want both?
>
> They are different things, last we discussed it: jobs are geared
> toward block device operations,

Historical accident.  We've discussed using them for non-blocky stuff,
such as migration.  Of course, discussions are cheap, code is what
counts.

>                                 and do not provide simple qmp-level
> facilities that I listed above. What I introduce is a way for an
> *existing* QMP command to be splitted, so it can re-enter the main
> loop sanely (and not by introducing new commands or signals or making
> things unnecessarily more complicated).
>
> My proposal is fairly small:
>   27 files changed, 877 insertions(+), 260 deletions(-)
>
> Including test, and the qxl screendump fix, which account for about
> 1/3 of the series.
>
>> I started to write a feature-by-feature comparison, but realized I don't
>> have the time to figure out either jobs or async from their (rather
>> sparse) documentation, let alone from code.
>>
>> > Existing qemu commands can be gradually replaced by async:true
>> > variants when needed, while carefully reviewing the concurrency
>> > aspects. The async:true commands marshaller helpers are splitted in
>> > half, the calling and return functions. The command is called with a
>> > QmpReturn context, that can return immediately or later, using the
>> > generated return helper, which allows for a step-by-step conversion.
>> >
>> > The screendump command is converted to an async:true version to solve
>> > rhbz#1230527. The command shows basic cancellation (this could be
>> > extended if needed). It could be further improved to do asynchronous
>> > IO writes as well.
>>
>> What is "basic cancellation"?
>> What extension(s) do you have in mind?
>
> It checks for cancellation in a few places, between IO. Full
> cancellation would allow to cancel at any time.
>
>>
>> What's the impact of screendump writing synchronously?
>
> It can be pretty bad, think about 4k screens. It is 33177600 bytes,
> written in PPM format, blocking the main loop..

My question was specifically about "could be further improved to do
asynchronous IO writes as well".  What's the impact of not having this
improvement?  I *guess* it means that even with the asynchronous
command, the synchronous writes still block "something", but I'm not
sure what "something" may be, and how it could impact behavior.  Hence
my question.

> QMP operation doing large IO (dumps), or blocking on events, could be
> switched to this async form without introducing user-visible change,

Letting the next QMP command start before the current one is done is a
user-visible change.  We can discuss whether the change is harmless.

> and with minimal effort compared to jobs.

To gauge the difference in effort, we'd need actual code to compare.


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-23  7:52     ` Markus Armbruster
@ 2019-05-25 12:12       ` Marc-André Lureau
  2019-05-27  8:18         ` Markus Armbruster
  0 siblings, 1 reply; 64+ messages in thread
From: Marc-André Lureau @ 2019-05-25 12:12 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, QEMU, Michael Roth, Dr. David Alan Gilbert,
	Gerd Hoffmann, John Snow

Hi

On Thu, May 23, 2019 at 9:52 AM Markus Armbruster <armbru@redhat.com> wrote:
> I'm not sure how asynchronous commands could support reconnect and
> resume.

The same way as current commands, including job commands.

>
> >> I'm ignoring "etc" unless you expand it into something specific.
> >>
> >> I'm also not taking the "weird" bait :)
> >> > The following series implements an async command solution instead. By
> >> > introducing a session context and a command return handler, it can:
> >> > - defer the return, allowing the mainloop to reenter
> >> > - return only to the caller (instead of broadcast events for reply)
> >> > - optionnally allow cancellation when the client is gone
> >> > - track on-going qapi command(s) per client/session
> >> >
> >> > and without introduction of new QMP APIs or client visible change.
> >>
> >> What do async commands provide that jobs lack?
> >>
> >> Why do we want both?
> >
> > They are different things, last we discussed it: jobs are geared
> > toward block device operations,
>
> Historical accident.  We've discussed using them for non-blocky stuff,
> such as migration.  Of course, discussions are cheap, code is what
> counts.

Using job API means providing new (& more complex) APIs to client.

The screendump fix here doesn't need new API, it needs new internal
dispatch of QMP commands: the purpose of this series.

Whenever we can solve things on qemu side, I would rather not
deprecate current API.

> >                                 and do not provide simple qmp-level
> > facilities that I listed above. What I introduce is a way for an
> > *existing* QMP command to be splitted, so it can re-enter the main
> > loop sanely (and not by introducing new commands or signals or making
> > things unnecessarily more complicated).
> >
> > My proposal is fairly small:
> >   27 files changed, 877 insertions(+), 260 deletions(-)
> >
> > Including test, and the qxl screendump fix, which account for about
> > 1/3 of the series.
> >
> >> I started to write a feature-by-feature comparison, but realized I don't
> >> have the time to figure out either jobs or async from their (rather
> >> sparse) documentation, let alone from code.
> >>
> >> > Existing qemu commands can be gradually replaced by async:true
> >> > variants when needed, while carefully reviewing the concurrency
> >> > aspects. The async:true commands marshaller helpers are splitted in
> >> > half, the calling and return functions. The command is called with a
> >> > QmpReturn context, that can return immediately or later, using the
> >> > generated return helper, which allows for a step-by-step conversion.
> >> >
> >> > The screendump command is converted to an async:true version to solve
> >> > rhbz#1230527. The command shows basic cancellation (this could be
> >> > extended if needed). It could be further improved to do asynchronous
> >> > IO writes as well.
> >>
> >> What is "basic cancellation"?
> >> What extension(s) do you have in mind?
> >
> > It checks for cancellation in a few places, between IO. Full
> > cancellation would allow to cancel at any time.
> >
> >>
> >> What's the impact of screendump writing synchronously?
> >
> > It can be pretty bad, think about 4k screens. It is 33177600 bytes,
> > written in PPM format, blocking the main loop..
>
> My question was specifically about "could be further improved to do
> asynchronous IO writes as well".  What's the impact of not having this
> improvement?  I *guess* it means that even with the asynchronous
> command, the synchronous writes still block "something", but I'm not
> sure what "something" may be, and how it could impact behavior.  Hence
> my question.

It blocks many things since the BQL is taken.

The goal is not to improve responsiveness at this point, but to fix
the QXL screendump bug, by introducing a split dispatch in QMP
commands: callback for starting, and a separate return function. This
is not rocket science. See below.

>
> > QMP operation doing large IO (dumps), or blocking on events, could be
> > switched to this async form without introducing user-visible change,
>
> Letting the next QMP command start before the current one is done is a
> user-visible change.  We can discuss whether the change is harmless.

Agree, from cover letter:
Existing qemu commands can be gradually replaced by async:true
variants when needed, while carefully reviewing the concurrency
aspects.

>
> > and with minimal effort compared to jobs.
>
> To gauge the difference in effort, we'd need actual code to compare.

It's a no-go to me, you don't want to teach all users out there with
new job API for existing commands when you can improve or fix things
in QEMU.

The QEMU change for the command can't really be simpler than what I
propose. You go from:

qmp_foo() {
  // do foo synchronously and
  return something
}

to:

qmp_foo_async(QmpReturn *r) {
  // do foo asynchronously (or return synchronously)
}

foo_done() {
  qmp_foo_async_return(r, something)
}

See "scripts: learn 'async' qapi commands" for the details.

thanks

-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-25 12:12       ` Marc-André Lureau
@ 2019-05-27  8:18         ` Markus Armbruster
  2019-05-27  9:07           ` Gerd Hoffmann
  0 siblings, 1 reply; 64+ messages in thread
From: Markus Armbruster @ 2019-05-27  8:18 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Kevin Wolf, Michael Roth, QEMU, Dr. David Alan Gilbert,
	Gerd Hoffmann, John Snow

Marc-André Lureau <marcandre.lureau@gmail.com> writes:

> Hi
>
> On Thu, May 23, 2019 at 9:52 AM Markus Armbruster <armbru@redhat.com> wrote:
>> I'm not sure how asynchronous commands could support reconnect and
>> resume.
>
> The same way as current commands, including job commands.

Consider the following scenario: a management application such as
libvirt starts a long-running task with the intent to monitor it until
it finishes.  Half-way through, the management application needs to
disconnect and reconnect for some reason (systemctl restart, or crash &
recover, or whatever).

If the long-running task is a job, the management application can resume
after reconnect: the job's ID is as valid as it was before, and the
commands to query and control the job work as before.

What if it's and asynchronous command?

>> >> I'm ignoring "etc" unless you expand it into something specific.
>> >>
>> >> I'm also not taking the "weird" bait :)
>> >> > The following series implements an async command solution instead. By
>> >> > introducing a session context and a command return handler, it can:
>> >> > - defer the return, allowing the mainloop to reenter
>> >> > - return only to the caller (instead of broadcast events for reply)
>> >> > - optionnally allow cancellation when the client is gone
>> >> > - track on-going qapi command(s) per client/session
>> >> >
>> >> > and without introduction of new QMP APIs or client visible change.
>> >>
>> >> What do async commands provide that jobs lack?
>> >>
>> >> Why do we want both?
>> >
>> > They are different things, last we discussed it: jobs are geared
>> > toward block device operations,
>>
>> Historical accident.  We've discussed using them for non-blocky stuff,
>> such as migration.  Of course, discussions are cheap, code is what
>> counts.
>
> Using job API means providing new (& more complex) APIs to client.
>
> The screendump fix here doesn't need new API, it needs new internal
> dispatch of QMP commands: the purpose of this series.
>
> Whenever we can solve things on qemu side, I would rather not
> deprecate current API.

Making a synchronous command asynchronous definitely changes API.

You could still argue the change is easier to handle for QMP clients
than a replacement by a job.

[...]


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-27  8:18         ` Markus Armbruster
@ 2019-05-27  9:07           ` Gerd Hoffmann
  2019-05-27 13:23             ` Markus Armbruster
  0 siblings, 1 reply; 64+ messages in thread
From: Gerd Hoffmann @ 2019-05-27  9:07 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Michael Roth, QEMU, Dr. David Alan Gilbert,
	Marc-André Lureau, John Snow

On Mon, May 27, 2019 at 10:18:42AM +0200, Markus Armbruster wrote:
> Marc-André Lureau <marcandre.lureau@gmail.com> writes:
> 
> > Hi
> >
> > On Thu, May 23, 2019 at 9:52 AM Markus Armbruster <armbru@redhat.com> wrote:
> >> I'm not sure how asynchronous commands could support reconnect and
> >> resume.
> >
> > The same way as current commands, including job commands.
> 
> Consider the following scenario: a management application such as
> libvirt starts a long-running task with the intent to monitor it until
> it finishes.  Half-way through, the management application needs to
> disconnect and reconnect for some reason (systemctl restart, or crash &
> recover, or whatever).
> 
> If the long-running task is a job, the management application can resume
> after reconnect: the job's ID is as valid as it was before, and the
> commands to query and control the job work as before.
> 
> What if it's and asynchronous command?

This is not meant for some long-running job which you have to manage.

Allowing commands being asynchronous makes sense for things which (a)
typically don't take long, and (b) don't need any management.

So, if the connection goes down the job is simply canceled, and after
reconnecting the management can simply send the same command again.

> > Whenever we can solve things on qemu side, I would rather not
> > deprecate current API.
> 
> Making a synchronous command asynchronous definitely changes API.

Inside qemu yes, sure.  But for the QMP client nothing changes.

cheers,
  Gerd



^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-27  9:07           ` Gerd Hoffmann
@ 2019-05-27 13:23             ` Markus Armbruster
  2019-05-27 15:17               ` Marc-André Lureau
                                 ` (2 more replies)
  0 siblings, 3 replies; 64+ messages in thread
From: Markus Armbruster @ 2019-05-27 13:23 UTC (permalink / raw)
  To: Gerd Hoffmann
  Cc: Kevin Wolf, QEMU, Michael Roth, Dr. David Alan Gilbert,
	Marc-André Lureau, John Snow

Gerd Hoffmann <kraxel@redhat.com> writes:

> On Mon, May 27, 2019 at 10:18:42AM +0200, Markus Armbruster wrote:
>> Marc-André Lureau <marcandre.lureau@gmail.com> writes:
>> 
>> > Hi
>> >
>> > On Thu, May 23, 2019 at 9:52 AM Markus Armbruster <armbru@redhat.com> wrote:
>> >> I'm not sure how asynchronous commands could support reconnect and
>> >> resume.
>> >
>> > The same way as current commands, including job commands.
>> 
>> Consider the following scenario: a management application such as
>> libvirt starts a long-running task with the intent to monitor it until
>> it finishes.  Half-way through, the management application needs to
>> disconnect and reconnect for some reason (systemctl restart, or crash &
>> recover, or whatever).
>> 
>> If the long-running task is a job, the management application can resume
>> after reconnect: the job's ID is as valid as it was before, and the
>> commands to query and control the job work as before.
>> 
>> What if it's and asynchronous command?
>
> This is not meant for some long-running job which you have to manage.
>
> Allowing commands being asynchronous makes sense for things which (a)
> typically don't take long, and (b) don't need any management.
>
> So, if the connection goes down the job is simply canceled, and after
> reconnecting the management can simply send the same command again.

Is this worth its own infrastructure?

Would you hazard a guess on how many commands can take long enough to
demand a conversion to asynchronous, yet not need any management?

>> > Whenever we can solve things on qemu side, I would rather not
>> > deprecate current API.
>> 
>> Making a synchronous command asynchronous definitely changes API.
>
> Inside qemu yes, sure.  But for the QMP client nothing changes.

Command replies can arrive out of order, can't they?


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-27 13:23             ` Markus Armbruster
@ 2019-05-27 15:17               ` Marc-André Lureau
  2019-05-28  6:09               ` Gerd Hoffmann
  2019-05-31  9:15               ` Kevin Wolf
  2 siblings, 0 replies; 64+ messages in thread
From: Marc-André Lureau @ 2019-05-27 15:17 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Michael Roth, QEMU, Dr. David Alan Gilbert,
	Gerd Hoffmann, John Snow

Hi

On Mon, May 27, 2019 at 3:23 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Gerd Hoffmann <kraxel@redhat.com> writes:
>
> > On Mon, May 27, 2019 at 10:18:42AM +0200, Markus Armbruster wrote:
> >> Marc-André Lureau <marcandre.lureau@gmail.com> writes:
> >>
> >> > Hi
> >> >
> >> > On Thu, May 23, 2019 at 9:52 AM Markus Armbruster <armbru@redhat.com> wrote:
> >> >> I'm not sure how asynchronous commands could support reconnect and
> >> >> resume.
> >> >
> >> > The same way as current commands, including job commands.
> >>
> >> Consider the following scenario: a management application such as
> >> libvirt starts a long-running task with the intent to monitor it until
> >> it finishes.  Half-way through, the management application needs to
> >> disconnect and reconnect for some reason (systemctl restart, or crash &
> >> recover, or whatever).
> >>
> >> If the long-running task is a job, the management application can resume
> >> after reconnect: the job's ID is as valid as it was before, and the
> >> commands to query and control the job work as before.
> >>
> >> What if it's and asynchronous command?
> >
> > This is not meant for some long-running job which you have to manage.
> >
> > Allowing commands being asynchronous makes sense for things which (a)
> > typically don't take long, and (b) don't need any management.
> >
> > So, if the connection goes down the job is simply canceled, and after
> > reconnecting the management can simply send the same command again.
>
> Is this worth its own infrastructure?

Yes, not having to change/break the client side API is worth some effort.

> Would you hazard a guess on how many commands can take long enough to
> demand a conversion to asynchronous, yet not need any management?

Some of the currently synchronous commands that are doing some
substantial task (many of them are not simply reading values from
memory) could be gradually converted, as needed.

> >> > Whenever we can solve things on qemu side, I would rather not
> >> > deprecate current API.
> >>
> >> Making a synchronous command asynchronous definitely changes API.
> >
> > Inside qemu yes, sure.  But for the QMP client nothing changes.
>
> Command replies can arrive out of order, can't they?

They are returned in order, see "QmpSession: return orderly".


-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-27 13:23             ` Markus Armbruster
  2019-05-27 15:17               ` Marc-André Lureau
@ 2019-05-28  6:09               ` Gerd Hoffmann
  2019-05-31  9:15               ` Kevin Wolf
  2 siblings, 0 replies; 64+ messages in thread
From: Gerd Hoffmann @ 2019-05-28  6:09 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, QEMU, Michael Roth, Dr. David Alan Gilbert,
	Marc-André Lureau, John Snow

  Hi,

> > This is not meant for some long-running job which you have to manage.
> >
> > Allowing commands being asynchronous makes sense for things which (a)
> > typically don't take long, and (b) don't need any management.
> >
> > So, if the connection goes down the job is simply canceled, and after
> > reconnecting the management can simply send the same command again.
> 
> Is this worth its own infrastructure?
> 
> Would you hazard a guess on how many commands can take long enough to
> demand a conversion to asynchronous, yet not need any management?

Required:
  screendump with qxl (needs round-drop to spice-server display thread
  for fully up-to-date screen content, due to lazy rendering).

Nice to have:
  Move anything which needs more than a milisecond to a thread or
  coroutine, so we avoid monitor commands causing guest-visible latency
  spikes due to holding the big qemu lock for too long.

  From a quick scan through monitor help hot candidates are screendump
  and pmemsave because they might write rather large data files.

  Dunno about savevm/loadvm.  I think they stop the guest anyway.  So
  moving them to async probably doesn't buy us much, at least from a
  latency point of view.

cheers,
  Gerd



^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type
  2019-05-27 13:23             ` Markus Armbruster
  2019-05-27 15:17               ` Marc-André Lureau
  2019-05-28  6:09               ` Gerd Hoffmann
@ 2019-05-31  9:15               ` Kevin Wolf
  2 siblings, 0 replies; 64+ messages in thread
From: Kevin Wolf @ 2019-05-31  9:15 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Michael Roth, QEMU, Dr. David Alan Gilbert,
	Marc-André Lureau, Gerd Hoffmann, John Snow

Am 27.05.2019 um 15:23 hat Markus Armbruster geschrieben:
> Gerd Hoffmann <kraxel@redhat.com> writes:
> 
> > On Mon, May 27, 2019 at 10:18:42AM +0200, Markus Armbruster wrote:
> >> Marc-André Lureau <marcandre.lureau@gmail.com> writes:
> >> 
> >> > Hi
> >> >
> >> > On Thu, May 23, 2019 at 9:52 AM Markus Armbruster <armbru@redhat.com> wrote:
> >> >> I'm not sure how asynchronous commands could support reconnect and
> >> >> resume.
> >> >
> >> > The same way as current commands, including job commands.
> >> 
> >> Consider the following scenario: a management application such as
> >> libvirt starts a long-running task with the intent to monitor it until
> >> it finishes.  Half-way through, the management application needs to
> >> disconnect and reconnect for some reason (systemctl restart, or crash &
> >> recover, or whatever).
> >> 
> >> If the long-running task is a job, the management application can resume
> >> after reconnect: the job's ID is as valid as it was before, and the
> >> commands to query and control the job work as before.
> >> 
> >> What if it's and asynchronous command?
> >
> > This is not meant for some long-running job which you have to manage.
> >
> > Allowing commands being asynchronous makes sense for things which (a)
> > typically don't take long, and (b) don't need any management.
> >
> > So, if the connection goes down the job is simply canceled, and after
> > reconnecting the management can simply send the same command again.
> 
> Is this worth its own infrastructure?
> 
> Would you hazard a guess on how many commands can take long enough to
> demand a conversion to asynchronous, yet not need any management?

Candidates are any commands that perform I/O. You don't want to hold the
BQL while doing I/O. Probably most block layer commands fall into this
category.

In fact, even the commands to start a block job could probably make use
of this infrastructure because they typically do some I/O before
returning success for starting the job.

> >> > Whenever we can solve things on qemu side, I would rather not
> >> > deprecate current API.
> >> 
> >> Making a synchronous command asynchronous definitely changes API.
> >
> > Inside qemu yes, sure.  But for the QMP client nothing changes.
> 
> Command replies can arrive out of order, can't they?

My understanding is that this is just an internal change and commands
still aren't processed in parallel.

Kevin


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous
  2019-04-10  8:48     ` Gerd Hoffmann
  (?)
@ 2019-07-15 19:09     ` Marc-André Lureau
  2019-07-30 11:23       ` Gerd Hoffmann
  -1 siblings, 1 reply; 64+ messages in thread
From: Marc-André Lureau @ 2019-07-15 19:09 UTC (permalink / raw)
  To: Gerd Hoffmann
  Cc: Markus Armbruster, Michael Roth, Dr. David Alan Gilbert, QEMU

On Wed, Apr 10, 2019 at 12:49 PM Gerd Hoffmann <kraxel@redhat.com> wrote:
>
> > +static void qmp_screendump_finish(QemuConsole *con, struct qmp_screendump *dump)
> > +{
> > +    Error *err = NULL;
> > +    DisplaySurface *surface;
> > +    Monitor *prev_mon = cur_mon;
>
> Why this is needed?
>

ppm_save() calls qemu_open() which may lookup fd associated to the
monitor: monitor_fdset_get_fd().

Interestingly, it seems fdset are not coupled with the current
monitor, so it's probably unnecessary to update the monitor to the one
associated with the command invocation.

> > +        /*
> > +         * FIXME: async save with coroutine? it would have to copy or
> > +         * lock the surface.
> > +         */
> > +        ppm_save(dump->filename, surface, &err);
>
> DisplaySurface is just a thin layer above pixman images these days.
> Pixman images are reference counted, so you can
> pixman_image_ref(surface->image) to make sure it doesn't disappear
> underneath you, then pass the pixman image to ppm_save.

ppm_save() is still synchronous. I suppose you suggested that for a
future async version. (note that in this case, ref the surface is
probably not sufficient, as it could be mutated while it is being
saved)

-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous
  2019-07-15 19:09     ` Marc-André Lureau
@ 2019-07-30 11:23       ` Gerd Hoffmann
  0 siblings, 0 replies; 64+ messages in thread
From: Gerd Hoffmann @ 2019-07-30 11:23 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Markus Armbruster, Michael Roth, Dr. David Alan Gilbert, QEMU

> > > +        /*
> > > +         * FIXME: async save with coroutine? it would have to copy or
> > > +         * lock the surface.
> > > +         */
> > > +        ppm_save(dump->filename, surface, &err);
> >
> > DisplaySurface is just a thin layer above pixman images these days.
> > Pixman images are reference counted, so you can
> > pixman_image_ref(surface->image) to make sure it doesn't disappear
> > underneath you, then pass the pixman image to ppm_save.
> 
> ppm_save() is still synchronous. I suppose you suggested that for a
> future async version.

Yes.

> (note that in this case, ref the surface is
> probably not sufficient, as it could be mutated while it is being
> saved)

That can happen anyway.  The display surface / pixman image can be backed
by guest-writable memory (stdvga vram for example) and even when holding
the qemu lock the guest vcpu can write there ...

cheers,
  GerdY



^ permalink raw reply	[flat|nested] 64+ messages in thread

end of thread, other threads:[~2019-07-30 11:23 UTC | newest]

Thread overview: 64+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-09 16:09 [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type Marc-André Lureau
2019-04-09 16:09 ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 01/20] qmp: constify QmpCommand and list Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 02/20] json-lexer: make it safe to call destroy multiple times Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 03/20] qmp: add QmpSession Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 04/20] QmpSession: add a return callback Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 05/20] QmpSession: add json parser and use it in qga Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 06/20] monitor: use qmp session to parse json feed Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 07/20] qga: simplify dispatch_return_cb Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 08/20] QmpSession: introduce QmpReturn Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 09/20] qmp: simplify qmp_return_error() Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:09 ` [Qemu-devel] [PATCH v4 10/20] QmpSession: keep a queue of pending commands Marc-André Lureau
2019-04-09 16:09   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 11/20] QmpSession: return orderly Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 12/20] qmp: introduce asynchronous command type Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 13/20] scripts: learn 'async' qapi commands Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 14/20] qmp: add qmp_return_is_cancelled() Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 15/20] monitor: add qmp_return_get_monitor() Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 16/20] console: add graphic_hw_update_done() Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-10  8:43   ` Gerd Hoffmann
2019-04-10  8:43     ` Gerd Hoffmann
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 17/20] console: make screendump asynchronous Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-10  8:48   ` Gerd Hoffmann
2019-04-10  8:48     ` Gerd Hoffmann
2019-07-15 19:09     ` Marc-André Lureau
2019-07-30 11:23       ` Gerd Hoffmann
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 18/20] monitor: start making qmp_human_monitor_command() asynchronous Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 19/20] monitor: teach HMP about asynchronous commands Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 16:10 ` [Qemu-devel] [PATCH v4 20/20] hmp: call the asynchronous QMP screendump to fix outdated/glitches Marc-André Lureau
2019-04-09 16:10   ` Marc-André Lureau
2019-04-09 19:05 ` [Qemu-devel] [PATCH v4 00/20] monitor: add asynchronous command type no-reply
2019-04-09 19:05   ` no-reply
2019-04-09 19:07 ` no-reply
2019-04-09 19:07   ` no-reply
2019-05-17 22:21 ` Marc-André Lureau
2019-05-21 14:17 ` Markus Armbruster
2019-05-21 14:50   ` Marc-André Lureau
2019-05-23  7:52     ` Markus Armbruster
2019-05-25 12:12       ` Marc-André Lureau
2019-05-27  8:18         ` Markus Armbruster
2019-05-27  9:07           ` Gerd Hoffmann
2019-05-27 13:23             ` Markus Armbruster
2019-05-27 15:17               ` Marc-André Lureau
2019-05-28  6:09               ` Gerd Hoffmann
2019-05-31  9:15               ` Kevin Wolf
2019-05-21 20:50   ` John Snow

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.