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 <marcandre.lur...@redhat.com> --- 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