From: "Marc-André Lureau" <marcandre.lureau@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>,
armbru@redhat.com, kraxel@redhat.com
Subject: [PATCH v6 13/25] scripts: learn 'async' qapi commands
Date: Fri, 8 Nov 2019 19:01:11 +0400 [thread overview]
Message-ID: <20191108150123.12213-14-marcandre.lureau@redhat.com> (raw)
In-Reply-To: <20191108150123.12213-1-marcandre.lureau@redhat.com>
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 | 149 ++++++++++++++++++++----
scripts/qapi/doc.py | 2 +-
scripts/qapi/expr.py | 4 +-
scripts/qapi/introspect.py | 2 +-
scripts/qapi/schema.py | 11 +-
tests/qapi-schema/qapi-schema-test.json | 5 +
tests/qapi-schema/qapi-schema-test.out | 8 ++
tests/qapi-schema/test-qapi.py | 7 +-
tests/test-qmp-cmds.c | 60 ++++++++++
9 files changed, 212 insertions(+), 36 deletions(-)
diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index ab98e504f3..301b1a24d8 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -17,18 +17,35 @@ from qapi.common import *
from qapi.gen import QAPIGenCCode, QAPISchemaModularCVisitor, ifcontext
-def gen_command_decl(name, arg_type, boxed, ret_type):
- return mcgen('''
+def gen_command_decl(name, arg_type, boxed, ret_type, success_response, asyn):
+ if asyn:
+ extra = "QmpReturn *qret"
+ else:
+ extra = 'Error **errp'
+
+ if asyn:
+ ret = mcgen('''
+void qmp_%(name)s(%(params)s);
+ ''',
+ 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, 'Error **errp'))
+ 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
@@ -39,7 +56,13 @@ def gen_call(name, arg_type, boxed, ret_type):
if memb.optional:
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 = '
@@ -61,6 +84,51 @@ 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('''
@@ -84,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, asyn):
+ if asyn:
+ 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, asyn):
return mcgen('''
%(proto)s;
''',
- proto=build_marshal_proto(name))
+ proto=build_marshal_proto(name, asyn))
-def gen_marshal(name, arg_type, boxed, ret_type):
+def gen_marshal(name, arg_type, boxed, ret_type, asyn):
have_args = boxed or (arg_type and not arg_type.is_empty())
ret = mcgen('''
@@ -105,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, asyn))
- if ret_type:
+ if ret_type and not asyn:
ret += mcgen('''
%(c_type)s retval;
''',
@@ -154,12 +225,28 @@ def gen_marshal(name, arg_type, boxed, ret_type):
}
''')
- ret += gen_call(name, arg_type, boxed, ret_type)
+ if asyn:
+ ret += gen_async_call(name, arg_type, boxed)
+ else:
+ ret += gen_call(name, arg_type, boxed, ret_type)
ret += mcgen('''
out:
+''')
+
+ if asyn:
+ ret += mcgen('''
+ if (err) {
+ qmp_return_error(qret, err);
+ }
+''')
+ else:
+ ret += mcgen('''
error_propagate(errp, err);
+''')
+
+ ret += mcgen('''
visit_free(v);
''')
@@ -194,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,
+ asyn):
options = []
if not success_response:
@@ -203,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 asyn:
+ options += ['QCO_ASYNC']
if not options:
options = ['QCO_NO_OPTIONS']
options = " | ".join(options)
+ if asyn:
+ 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 +373,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
- features):
+ features, asyn):
if not gen:
return
# FIXME: If T is a user-defined type, the user is responsible
@@ -292,11 +387,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, asyn))
+ self._genh.add(gen_marshal_decl(name, asyn))
+ self._genc.add(gen_marshal(name, arg_type, boxed, ret_type, asyn))
+ if asyn 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,
+ asyn))
def gen_commands(schema, output_dir, prefix):
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 6f1c17f71f..375978f04f 100644
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -265,7 +265,7 @@ class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
- features):
+ features, asyn):
doc = self.cur_doc
self._gen.add(texi_msg('Command', doc, ifcond,
texi_arguments(doc,
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index d7a289eded..db4956133d 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -89,7 +89,7 @@ def check_flags(expr, info):
if key in expr and expr[key] is not False:
raise QAPISemError(
info, "flag '%s' may only use false value" % key)
- for key in ['boxed', 'allow-oob', 'allow-preconfig']:
+ for key in ['boxed', 'allow-oob', 'allow-preconfig', 'async']:
if key in expr and expr[key] is not True:
raise QAPISemError(
info, "flag '%s' may only use true value" % key)
@@ -344,7 +344,7 @@ def check_exprs(exprs):
['command'],
['data', 'returns', 'boxed', 'if', 'features',
'gen', 'success-response', 'allow-oob',
- 'allow-preconfig'])
+ 'allow-preconfig', 'async'])
normalize_members(expr.get('data'))
check_command(expr, info)
elif meta == 'event':
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index b3a463dd8b..f28c96afa3 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -212,7 +212,7 @@ const QLitObject %(c_name)s = %(c_string)s;
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
- features):
+ features, asyn):
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/scripts/qapi/schema.py b/scripts/qapi/schema.py
index cf0045f34e..2e1d5278bf 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -128,7 +128,7 @@ class QAPISchemaVisitor(object):
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
- features):
+ features, asyn):
pass
def visit_event(self, name, info, ifcond, arg_type, boxed):
@@ -678,7 +678,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
gen, success_response, boxed, allow_oob, allow_preconfig,
- features):
+ features, asyn):
QAPISchemaEntity.__init__(self, name, info, doc, ifcond, features)
assert not arg_type or isinstance(arg_type, str)
assert not ret_type or isinstance(ret_type, str)
@@ -691,6 +691,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
self.boxed = boxed
self.allow_oob = allow_oob
self.allow_preconfig = allow_preconfig
+ self.asyn = asyn
def check(self, schema):
QAPISchemaEntity.check(self, schema)
@@ -733,7 +734,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
self.gen, self.success_response,
self.boxed, self.allow_oob,
self.allow_preconfig,
- self.features)
+ self.features, self.asyn)
class QAPISchemaEvent(QAPISchemaEntity):
@@ -1016,6 +1017,7 @@ class QAPISchema(object):
allow_preconfig = expr.get('allow-preconfig', False)
ifcond = expr.get('if')
features = expr.get('features', [])
+ asyn = expr.get('async', False)
if isinstance(data, OrderedDict):
data = self._make_implicit_object_type(
name, info, ifcond, 'arg', self._make_members(data, info))
@@ -1025,7 +1027,8 @@ class QAPISchema(object):
self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
gen, success_response,
boxed, allow_oob, allow_preconfig,
- self._make_features(features, info)))
+ self._make_features(features, info),
+ asyn))
def _def_event(self, expr, info, doc):
name = expr['event']
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 9abf175fe0..58d2164c7e 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -148,6 +148,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 3660e75a48..19d2d202b9 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -217,6 +217,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 bad14edb47..1914b7d5ff 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -70,12 +70,13 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
- features):
+ features, asyn):
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 asyn else ''))
self._print_if(ifcond)
self._print_features(features)
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 213df7e53a..75ef30f6ea 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -34,6 +34,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);
@@ -394,6 +416,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);
@@ -407,6 +466,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();
--
2.24.0
next prev parent reply other threads:[~2019-11-08 15:11 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-11-08 15:00 [PATCH v6 00/25] monitor: add asynchronous command type Marc-André Lureau
2019-11-08 15:00 ` [PATCH v6 01/25] qmp: constify QmpCommand and list Marc-André Lureau
2019-11-08 16:50 ` Damien Hedde
2020-02-17 11:47 ` Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 02/25] json-lexer: make it safe to call destroy multiple times Marc-André Lureau
2019-11-08 16:04 ` Damien Hedde
2019-11-08 15:01 ` [PATCH v6 03/25] qmp: add QmpSession Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 04/25] QmpSession: add a return callback Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 05/25] QmpSession: add json parser and use it in qga Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 06/25] monitor: use qmp session to parse json feed Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 07/25] qga: simplify dispatch_return_cb Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 08/25] QmpSession: introduce QmpReturn Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 09/25] qmp: simplify qmp_return_error() Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 10/25] QmpSession: keep a queue of pending commands Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 11/25] QmpSession: return orderly Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 12/25] qmp: introduce asynchronous command type Marc-André Lureau
2019-11-08 15:01 ` Marc-André Lureau [this message]
2019-11-08 15:01 ` [PATCH v6 14/25] qmp: add qmp_return_is_cancelled() Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 15/25] console: add graphic_hw_update_done() Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 16/25] ppm-save: pass opened fd Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 17/25] ui: add pixman image g_autoptr support Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 18/25] object: add " Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 19/25] screendump: replace FILE with QIOChannel and fix close()/qemu_close() Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 20/25] osdep: add qemu_unlink() Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 21/25] screendump: use qemu_unlink() Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 22/25] console: make screendump asynchronous Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 23/25] monitor: start making qmp_human_monitor_command() asynchronous Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 24/25] monitor: teach HMP about asynchronous commands Marc-André Lureau
2019-11-08 15:01 ` [PATCH v6 25/25] hmp: call the asynchronous QMP screendump to fix outdated/glitches Marc-André Lureau
2019-12-13 16:03 ` [PATCH v6 00/25] monitor: add asynchronous command type Kevin Wolf
2019-12-13 16:28 ` Marc-André Lureau
2019-12-16 12:07 ` Kevin Wolf
2020-01-06 18:21 ` Marc-André Lureau
2020-01-07 5:17 ` Kevin Wolf
2020-01-07 12:11 ` Marc-André Lureau
2020-01-13 15:58 ` Markus Armbruster
2020-01-13 16:54 ` Kevin Wolf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20191108150123.12213-14-marcandre.lureau@redhat.com \
--to=marcandre.lureau@redhat.com \
--cc=armbru@redhat.com \
--cc=kraxel@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).