From: Marc-André Lureau <marcandre.lur...@redhat.com> Commands with the 'async' key will be registered as async type (see previous commit), and will allow a synchronous (in command cb) or asynchronous return (when ready, in idle etc).
Ex: { 'command': 'foo-async, 'data': {'arg': 'str'}, async: true } Signed-off-by: Marc-André Lureau <marcandre.lur...@redhat.com> --- qapi/introspect.json | 2 +- scripts/qapi-commands.py | 133 +++++++++++++++++++++++++++++++++-------- scripts/qapi-introspect.py | 7 ++- scripts/qapi.py | 14 +++-- tests/Makefile | 1 + tests/qapi-schema/async.err | 0 tests/qapi-schema/async.exit | 1 + tests/qapi-schema/async.json | 1 + tests/qapi-schema/async.out | 5 ++ tests/qapi-schema/test-qapi.py | 6 +- 10 files changed, 133 insertions(+), 37 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 5a3bd3e..5b878ff 100644 --- a/qapi/introspect.json +++ b/qapi/introspect.json @@ -259,7 +259,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-commands.py b/scripts/qapi-commands.py index 4db1ae3..b0b922d 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, box, ret_type): - return mcgen(''' +def gen_command_decl(name, arg_type, box, 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, box, 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, box, 'Error **errp')) + c_type=(ret_type and ret_type.c_type()) or 'void', + c_name=c_name(name), + params=gen_params(arg_type, box, extra)) -def gen_call(name, arg_type, box, ret_type): - ret = '' - +def gen_argstr(arg_type, box): argstr = '' if box: argstr = 'arg, ' @@ -38,6 +50,13 @@ def gen_call(name, arg_type, box, ret_type): argstr += 'has_%s, ' % c_name(memb.name) argstr += '%s, ' % c_name(memb.name) + return argstr + + +def gen_call(name, arg_type, box, ret_type): + ret = '' + + argstr = gen_argstr(arg_type, box) lhs = '' if ret_type: lhs = 'retval = ' @@ -59,14 +78,57 @@ qmp_marshal_output_%(c_name)s(retval, ret, &err); return ret -def gen_marshal_vars(arg_type, box, ret_type): +def gen_async_call(name, arg_type, box): + argstr = gen_argstr(arg_type, box) + + 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, arg_type, 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_vars(arg_type, box, ret_type, async): ret = mcgen(''' Error *err = NULL; ''') push_indent() - if ret_type: + if ret_type and not async: ret += mcgen(''' %(c_type)s retval; ''', @@ -178,29 +240,42 @@ out: c_type=ret_type.c_type(), c_name=ret_type.c_name()) -def gen_marshal_proto(name): - return 'static void qmp_marshal_%s' % c_name(name) + \ - '(QDict *args, QObject **ret, Error **errp)' +def gen_marshal_proto(name, async): + if async: + tmpl = 'static void qmp_marshal_%s(QDict *args, QmpReturn *qret)' + else: + tmpl = 'static void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)' + return tmpl % c_name(name) -def gen_marshal(name, arg_type, box, ret_type): +def gen_marshal(name, arg_type, box, ret_type, async): ret = mcgen(''' %(proto)s { ''', - proto=gen_marshal_proto(name)) + proto=gen_marshal_proto(name, async)) - ret += gen_marshal_vars(arg_type, box, ret_type) + ret += gen_marshal_vars(arg_type, box, ret_type, async) ret += gen_marshal_input_visit(arg_type, box) - ret += gen_call(name, arg_type, box, ret_type) + if async: + ret += gen_async_call(name, arg_type, box) + else: + ret += gen_call(name, arg_type, box, ret_type) if re.search('^ *goto out;', ret, re.MULTILINE): ret += mcgen(''' out: ''') - ret += mcgen(''' + if async: + ret += mcgen(''' + if (err) { + qmp_return_error(qret, err); + } +''') + else: + ret += mcgen(''' error_propagate(errp, err); ''') ret += gen_marshal_input_visit(arg_type, box, dealloc=True) @@ -210,16 +285,18 @@ out: return ret -def gen_register_command(name, success_response): +def gen_register_command(name, success_response, async): push_indent() 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) pop_indent() return ret @@ -259,15 +336,18 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor): self._visited_ret_types = None def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, box): + gen, success_response, box, async): if not gen: return - self.decl += gen_command_decl(name, arg_type, box, ret_type) + self.decl += gen_command_decl(name, arg_type, box, + 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.defn += gen_marshal(name, arg_type, box, ret_type) - self._regy += gen_register_command(name, success_response) + if async: + self.defn += gen_async_return(name, arg_type, ret_type) + self.defn += gen_marshal(name, arg_type, box, ret_type, async) + self._regy += gen_register_command(name, success_response, async) (input_file, output_dir, do_c, do_h, prefix, opts) = \ @@ -325,6 +405,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 8023f1b..6add6a0 100644 --- a/scripts/qapi-introspect.py +++ b/scripts/qapi-introspect.py @@ -29,6 +29,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, box): + gen, success_response, box, 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, box): arg_type = arg_type or self._schema.the_empty_object_type diff --git a/scripts/qapi.py b/scripts/qapi.py index 2a9b6e5..96901ab 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -822,7 +822,8 @@ def check_exprs(exprs): add_struct(expr, info) elif 'command' in expr: check_keys(expr_elem, 'command', [], - ['data', 'returns', 'gen', 'success-response', 'box']) + ['data', 'returns', 'gen', 'success-response', 'box', + 'async']) add_name(expr['command'], info, 'command') elif 'event' in expr: check_keys(expr_elem, 'event', [], ['data', 'box']) @@ -911,7 +912,7 @@ class QAPISchemaVisitor(object): pass def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, box): + gen, success_response, box, async): pass def visit_event(self, name, info, arg_type, box): @@ -1194,7 +1195,7 @@ class QAPISchemaAlternateType(QAPISchemaType): class QAPISchemaCommand(QAPISchemaEntity): def __init__(self, name, info, arg_type, ret_type, gen, success_response, - box): + box, async): QAPISchemaEntity.__init__(self, name, info) assert not arg_type or isinstance(arg_type, str) assert not ret_type or isinstance(ret_type, str) @@ -1205,6 +1206,7 @@ class QAPISchemaCommand(QAPISchemaEntity): self.gen = gen self.success_response = success_response self.box = box + self.async = async def check(self, schema): if self._arg_type_name: @@ -1219,7 +1221,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.box) + self.gen, self.success_response, self.box, + self.async) class QAPISchemaEvent(QAPISchemaEntity): @@ -1399,6 +1402,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) box = expr.get('box', False) if isinstance(data, dict): @@ -1410,7 +1414,7 @@ class QAPISchema(object): assert len(rets) == 1 rets = self._make_array_type(rets[0]) self._def_entity(QAPISchemaCommand(name, info, data, rets, gen, - success_response, box)) + success_response, box, async)) def _def_event(self, expr, info): name = expr['event'] diff --git a/tests/Makefile b/tests/Makefile index 7276b54..e5cdbbe 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -245,6 +245,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 0000000..e69de29 diff --git a/tests/qapi-schema/async.exit b/tests/qapi-schema/async.exit new file mode 100644 index 0000000..573541a --- /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 0000000..3b719e3 --- /dev/null +++ b/tests/qapi-schema/async.json @@ -0,0 +1 @@ +{ '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 0000000..c83bea5 --- /dev/null +++ b/tests/qapi-schema/async.out @@ -0,0 +1,5 @@ +object :empty +object :obj-screendump-async-arg + member filename: str optional=False +command screendump-async :obj-screendump-async-arg -> None + gen=True success_response=True box=False async diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 559f0e8..f99a980 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -36,11 +36,11 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): self._print_variants(variants) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, box): + gen, success_response, box, 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 box=%s' % (gen, success_response, - box) + print ' gen=%s success_response=%s box=%s%s' % \ + (gen, success_response, box, ' async' if async else '') def visit_event(self, name, info, arg_type, box): print 'event %s %s' % (name, arg_type and arg_type.name) -- 2.4.3