[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v2 11/25] scripts: learn 'async' qapi commands
From: |
Marc-André Lureau |
Subject: |
[Qemu-devel] [PATCH v2 11/25] scripts: learn 'async' qapi commands |
Date: |
Wed, 18 Jan 2017 20:03:18 +0400 |
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 (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 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 should call
qmp_return_error().
Signed-off-by: Marc-André Lureau <address@hidden>
---
qapi/introspect.json | 2 +-
scripts/qapi.py | 14 +++--
scripts/qapi-commands.py | 139 +++++++++++++++++++++++++++++++++--------
scripts/qapi-introspect.py | 7 ++-
tests/Makefile.include | 1 +
tests/qapi-schema/async.err | 0
tests/qapi-schema/async.exit | 1 +
tests/qapi-schema/async.json | 6 ++
tests/qapi-schema/async.out | 10 +++
tests/qapi-schema/test-qapi.py | 7 ++-
10 files changed, 151 insertions(+), 36 deletions(-)
create mode 100644 tests/qapi-schema/async.err
create mode 100644 tests/qapi-schema/async.exit
create mode 100644 tests/qapi-schema/async.json
create mode 100644 tests/qapi-schema/async.out
diff --git a/qapi/introspect.json b/qapi/introspect.json
index f6adc439bb..fd6a9e98a4 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -262,7 +262,7 @@
# Since: 2.5
##
{ 'struct': 'SchemaInfoCommand',
- 'data': { 'arg-type': 'str', 'ret-type': 'str' } }
+ 'data': { 'arg-type': 'str', 'ret-type': 'str', 'async': 'bool' } }
##
# @SchemaInfoEvent:
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 53a44779d0..066b97a7fb 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -881,7 +881,8 @@ def check_exprs(exprs):
add_struct(expr, info)
elif 'command' in expr:
check_keys(expr_elem, 'command', [],
- ['data', 'returns', 'gen', 'success-response', 'boxed'])
+ ['data', 'returns', 'gen', 'success-response', 'boxed',
+ 'async'])
add_name(expr['command'], info, 'command')
elif 'event' in expr:
check_keys(expr_elem, 'event', [], ['data', 'boxed'])
@@ -1064,7 +1065,7 @@ class QAPISchemaVisitor(object):
pass
def visit_command(self, name, info, arg_type, ret_type,
- gen, success_response, boxed):
+ gen, success_response, boxed, async):
pass
def visit_event(self, name, info, arg_type, boxed):
@@ -1406,7 +1407,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
class QAPISchemaCommand(QAPISchemaEntity):
def __init__(self, name, info, arg_type, ret_type, gen, success_response,
- boxed):
+ boxed, async):
QAPISchemaEntity.__init__(self, name, info)
assert not arg_type or isinstance(arg_type, str)
assert not ret_type or isinstance(ret_type, str)
@@ -1417,6 +1418,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
self.gen = gen
self.success_response = success_response
self.boxed = boxed
+ self.async = async
def check(self, schema):
if self._arg_type_name:
@@ -1440,7 +1442,8 @@ class QAPISchemaCommand(QAPISchemaEntity):
def visit(self, visitor):
visitor.visit_command(self.name, self.info,
self.arg_type, self.ret_type,
- self.gen, self.success_response, self.boxed)
+ self.gen, self.success_response, self.boxed,
+ self.async)
class QAPISchemaEvent(QAPISchemaEntity):
@@ -1645,6 +1648,7 @@ class QAPISchema(object):
data = expr.get('data')
rets = expr.get('returns')
gen = expr.get('gen', True)
+ async = expr.get('async', False)
success_response = expr.get('success-response', True)
boxed = expr.get('boxed', False)
if isinstance(data, OrderedDict):
@@ -1654,7 +1658,7 @@ class QAPISchema(object):
assert len(rets) == 1
rets = self._make_array_type(rets[0], info)
self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
- success_response, boxed))
+ success_response, boxed, async))
def _def_event(self, expr, info):
name = expr['event']
diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 09e8467d90..8c6a281b7f 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -16,18 +16,30 @@ from qapi import *
import re
-def gen_command_decl(name, arg_type, boxed, ret_type):
- return mcgen('''
+def gen_command_decl(name, arg_type, boxed, ret_type, async):
+ if async:
+ extra = "QmpReturn *qret"
+ else:
+ extra = 'Error **errp'
+
+ if async:
+ return mcgen('''
+void qmp_%(name)s(%(params)s);
+void qmp_%(name)s_return(QmpReturn *qret%(c_type)s);
+''',
+ c_type=(", " + ret_type.c_type() if ret_type else ""),
+ name=c_name(name),
+ params=gen_params(arg_type, boxed, extra))
+ 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=gen_params(arg_type, boxed, 'Error **errp'))
+ c_type=(ret_type and ret_type.c_type()) or 'void',
+ c_name=c_name(name),
+ params=gen_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 +51,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 +79,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,18 +146,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 gen_marshal_proto(name):
- return 'void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)' %
c_name(name)
+def gen_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=gen_marshal_proto(name))
+ proto=gen_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('''
@@ -103,9 +170,9 @@ def gen_marshal(name, arg_type, boxed, ret_type):
{
Error *err = NULL;
''',
- proto=gen_marshal_proto(name))
+ proto=gen_marshal_proto(name, async))
- if ret_type:
+ if ret_type and not async:
ret += mcgen('''
%(c_type)s retval;
''',
@@ -152,12 +219,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);
''')
@@ -192,15 +275,17 @@ out:
return ret
-def gen_register_command(name, success_response):
+def gen_register_command(name, success_response, async):
options = 'QCO_NO_OPTIONS'
if not success_response:
options = 'QCO_NO_SUCCESS_RESP'
-
+ func = 'qmp_register_command'
+ if async:
+ func = 'qmp_register_async_command'
ret = mcgen('''
- qmp_register_command("%(name)s", qmp_marshal_%(c_name)s, %(opts)s);
+ %(func)s("%(name)s", qmp_marshal_%(c_name)s, %(opts)s);
''',
- name=name, c_name=c_name(name),
+ func=func, name=name, c_name=c_name(name),
opts=options)
return ret
@@ -239,16 +324,19 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
self._visited_ret_types = None
def visit_command(self, name, info, arg_type, ret_type,
- gen, success_response, boxed):
+ gen, success_response, boxed, async):
if not gen:
return
- self.decl += gen_command_decl(name, arg_type, boxed, ret_type)
+ self.decl += gen_command_decl(name, arg_type, boxed,
+ ret_type, async)
if ret_type and ret_type not in self._visited_ret_types:
self._visited_ret_types.add(ret_type)
self.defn += gen_marshal_output(ret_type)
- self.decl += gen_marshal_decl(name)
- self.defn += gen_marshal(name, arg_type, boxed, ret_type)
- self._regy += gen_register_command(name, success_response)
+ if async:
+ self.defn += gen_async_return(name, ret_type)
+ self.decl += gen_marshal_decl(name, async)
+ self.defn += gen_marshal(name, arg_type, boxed, ret_type, async)
+ self._regy += gen_register_command(name, success_response, async)
(input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
@@ -306,6 +394,7 @@ fdef.write(mcgen('''
fdecl.write(mcgen('''
#include "%(prefix)sqapi-types.h"
#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/dispatch.h"
#include "qapi/error.h"
''',
diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index 541644e350..f8a854dd0f 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -28,6 +28,8 @@ def to_json(obj, level=0):
to_json(obj[key], level + 1))
for key in sorted(obj.keys())]
ret = '{' + ', '.join(elts) + '}'
+ elif isinstance(obj, bool):
+ ret = 'true' if obj else 'false'
else:
assert False # not implemented
if level == 1:
@@ -154,12 +156,13 @@ const char %(c_name)s[] = %(c_string)s;
for m in variants.variants]})
def visit_command(self, name, info, arg_type, ret_type,
- gen, success_response, boxed):
+ gen, success_response, boxed, async):
arg_type = arg_type or self._schema.the_empty_object_type
ret_type = ret_type or self._schema.the_empty_object_type
self._gen_json(name, 'command',
{'arg-type': self._use_type(arg_type),
- 'ret-type': self._use_type(ret_type)})
+ 'ret-type': self._use_type(ret_type),
+ 'async': async})
def visit_event(self, name, info, arg_type, boxed):
arg_type = arg_type or self._schema.the_empty_object_type
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 152655d086..d9b575e657 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -343,6 +343,7 @@ qapi-schema += args-member-unknown.json
qapi-schema += args-name-clash.json
qapi-schema += args-union.json
qapi-schema += args-unknown.json
+qapi-schema += async.json
qapi-schema += bad-base.json
qapi-schema += bad-data.json
qapi-schema += bad-ident.json
diff --git a/tests/qapi-schema/async.err b/tests/qapi-schema/async.err
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/async.exit b/tests/qapi-schema/async.exit
new file mode 100644
index 0000000000..573541ac97
--- /dev/null
+++ b/tests/qapi-schema/async.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/async.json b/tests/qapi-schema/async.json
new file mode 100644
index 0000000000..2073349d39
--- /dev/null
+++ b/tests/qapi-schema/async.json
@@ -0,0 +1,6 @@
+##
+# @screendump-async:
+#
+# @filename: foo
+##
+{ 'command': 'screendump-async', 'data': {'filename': 'str'}, 'async': true }
diff --git a/tests/qapi-schema/async.out b/tests/qapi-schema/async.out
new file mode 100644
index 0000000000..bcb5a400d7
--- /dev/null
+++ b/tests/qapi-schema/async.out
@@ -0,0 +1,10 @@
+enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat',
'qbool']
+ prefix QTYPE
+object q_empty
+object q_obj_screendump-async-arg
+ member filename: str optional=False
+command screendump-async q_obj_screendump-async-arg -> None
+ gen=True success_response=True boxed=False async=True
+doc symbol=screendump-async expr=('command', 'screendump-async')
+ arg=filename
+foo
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index b4cde4ff4f..608c304995 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -36,11 +36,12 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
self._print_variants(variants)
def visit_command(self, name, info, arg_type, ret_type,
- gen, success_response, boxed):
+ gen, success_response, boxed, 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' % \
- (gen, success_response, boxed)
+ print ' gen=%s success_response=%s boxed=%s%s' % \
+ (gen, success_response, boxed,
+ ' async=True' if async else '')
def visit_event(self, name, info, arg_type, boxed):
print 'event %s %s' % (name, arg_type and arg_type.name)
--
2.11.0.295.gd7dffce1c
- Re: [Qemu-devel] [PATCH v2 01/25] tests: start generic qemu-qmp tests, (continued)
- [Qemu-devel] [PATCH v2 02/25] tests: change /0.15/* tests to /qmp/*, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 03/25] qmp: teach qmp_dispatch() to take a pre-filled QDict, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 04/25] qmp: use a return callback for the command reply, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 05/25] qmp: add QmpClient, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 06/25] qmp: add qmp_return_is_cancelled(), Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 08/25] qapi: ignore top-level 'id' field, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 07/25] qmp: introduce async command type, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 09/25] qmp: take 'id' from request, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 10/25] qmp: check that async command have an 'id', Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 11/25] scripts: learn 'async' qapi commands,
Marc-André Lureau <=
- [Qemu-devel] [PATCH v2 12/25] tests: add dispatch async tests, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 13/25] monitor: add 'async' capability, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 14/25] monitor: add !qmp pre-conditions, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 15/25] monitor: suspend when running async and client has no async, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 16/25] qmp: update qmp-spec about async capability, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 17/25] qtest: add qtest-timeout, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 19/25] tests: add tests for async and non-async clients, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 18/25] qtest: add qtest_init_qmp_caps(), Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 20/25] qapi: improve 'screendump' documentation, Marc-André Lureau, 2017/01/18
- [Qemu-devel] [PATCH v2 21/25] console: graphic_hw_update return true if async, Marc-André Lureau, 2017/01/18