qemu-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]