[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Xen-devel] [PATCH v6.2 05/11] libxl_qmp: Implementation of libxl__ev_qmp_*



On Fri, Nov 16, 2018 at 04:09:03PM +0000, Ian Jackson wrote:
> Anthony PERARD writes ("Re: [PATCH v6.2 05/11] libxl_qmp: Implementation of 
> libxl__ev_qmp_*"):
> > On Thu, Nov 15, 2018 at 06:40:13PM +0000, Ian Jackson wrote:
> > > It would be better if the id column stated something more useful than
> > > `set'.  `next' maybe, where applicable ?
> > 
> > When 'set', id is the id of the user command we intend to send or have
> > already sent and waiting for the associated reply. I'm not sure which
> > word or set of word would describe enough. I might need to add a comment
> > below the table.
> 
> You could use `sent', `next', alongside your existing `prev' ?

Will do.

> > > > + * - Allowed internal state transition:
> > > > + * disconnected                         -> connecting
> > > > + * connection                           -> capability_negotiation
> > > > + * capability_negotiation/waiting_reply -> connected
> > > > + * connected                            -> waiting_reply
> > > > + * any                                  -> disconnected
> > > 
> > > This does not mention the state `broken' and it should.
> > 
> > Maybe:
> >   any -> broken
> 
> Fine by me.  You want to mention broken -> disconnected too :-).

Will, that part of any->disconnected, but it doesn't hurt to add it.

> > > > +static int qmp_ev_prepare_cmd(libxl__gc *gc,
> > > > +                              libxl__ev_qmp *ev,
> > > > +                              const char *cmd,
> > > > +                              const libxl__json_object *args)
> ...
> > > > +    ev->id++;
> > > > +    buf = qmp_prepare_cmd(gc, cmd, args, ev->id, &len);
> > > > +    if (!buf) {
> > > > +        return ERROR_FAIL;
> > > > +    }
> > > 
> > > There is no doc comment for qmp_prepare_cmd saying whether it logs on
> > > error.  So it doesn't ?  In which case maybe you should do so here.
> > > 
> > > Alternatively if it can only really fail due to memory allocation
> > > failure, maybe it should return void (and call libxl__alloc_failed
> > > on allocation failure) instead ?
> > 
> > So we can have the allocation been done by libxl's functions, as
> > yajl_gen_alloc can use other functions that malloc/free/realloc, that
> > would remove the allocation failure.
> 
> Right.
> 
> > But then I think that qmp_prepare_cmd could fail and return null if
> > 'cmd' or 'args' contains bad json objects, or a wrong string.
> > 
> > So we still needs to detect failure.
> 
> OK.
> 
> > I log here as I can add the domid.
> 
> I'm not sure what you mean.  Where do you log ?  Not here.

Not yet :). There is maybe missing words in my sentence...

> > > > +/* Setup connection */
> > > > +
> > > > +static int qmp_ev_connect(libxl__gc *gc, libxl__ev_qmp *ev)
> > > > +    /* disconnected -> connecting but with `msg` free
> > > > +     * on error: disconnected
> > > > +     * If the initial state isn't disconnected, then nothing is done */
> > > 
> > > Maybe it be better for this function to return `broken' on error ?
> > 
> > That not the case, but I could change to return broken on error, and let
> > the caller clean the state.
> 
> That's what I mean.  That might simplify this function and the caller
> probably has the relevant cleanup path already.  (I haven't checked.)

It does, and yes, that will make the function simpler.

> > > > +    if (revents & POLLOUT) {
> > > > +        rc = qmp_ev_callback_writable(gc, ev, fd);
> > > ...
> > > > +    if (revents & POLLIN) {
> > > > +        rc = qmp_ev_callback_readable(egc, ev, fd);
> > > > +        if (rc)
> > > > +            goto out;
> > > > +
> > > > +        /* parse input */
> > > 
> > > I find it odd that this input parsing is not part of
> > > qmp_ev_callback_readable.  What do you think about moving it there ?
> > 
> > I wanted to reduce the number of function calls between when a user
> > callback is called and when ev_qmp's control passes outsite of ev_qmp's
> > implementation. So I moved qmp_ev_handle_message() call here.
> 
> Why did you want to reduce the number of function calls ?

Well, there is a need to track that `ev` might be discarded, and the
only way left is via a return value. It is probably easier to follow if
less function have the possibility that a user callback have been
called.

> > > > +    /*
> > > > +     * We will send a file descriptor associated with a command on the
> > > > +     * first byte of this command.
> > > > +     */
> > > > +    if (ev->qmp_state == qmp_state_waiting_reply &&
> > > > +        ev->fd >= 0 &&
> > > > +        ev->tx_buf_off == 0) {
> > > 
> > > According to the doc comments, state waiting_reply might have tx_buf
> > > free, in which case it would try to execute this code.  I don't think
> > > that can be right.
> > 
> > When tx_buf is free, that function returns earlier. So this code isn't
> > executed.
> 
> Oh yes, so it does.  But why are we entering this callback with a free
> tx_buf at all ?  If we just return, we are likely to get called again,
> ie an infinite loop.  This can only occur as a result of a bug ?
> In which case maybe it should be an assert ?

Yes, I can probably add the assert.

> > > I wrote:
> > > 
> > >   But I think you should treat only `\n' as the delimiter.  This will
> > >   considerably simplify the buffer handling.  (You should check and trim
> > >   the preceding `\r' before passing things to libxl__json_parse of
> > >   course.)
> > > 
> > > But you don't seem to have done that, or replied ?
> > 
> > This is still simpler than what we had before, and if I look for only
> > '\n', I would need a loop in cases where \n isn't precedded by \r. I
> > also check the whole buffer rather than new data, so looking for \r and
> > check if it is followed by a \n is similar.
> 
> I think a bare \n is not legal and should be treated as a protocol
> error.  Don't you agree ?  Given that, you can search for \n, and if
> it is not preceded by \r, call it an error.

I can't find anything that say bare \n are not legal. \n is part of
rfc7159, which the qmp-spec mentions when speaking about JSON data
structures. It is even possible to ask QEMU to add bare \n, this is done
with pretty=on.

> > > > +    case LIBXL__QMP_MESSAGE_TYPE_QMP:
> > > ...
> > > > +        assert(!ev->tx_buf);
> > > > +        ev->tx_buf = qmp_prepare_cmd(gc, "qmp_capabilities", NULL,
> > > > +                              QMP_CAPABILITY_NEGOTIATION_MSGID,
> > > > +                              &ev->tx_buf_len);
> > > > +        ev->tx_buf_off = 0;
> > > 
> > > Why do you not use qmp_ev_prepare_cmd for this ?
> > 
> > Because qmp_ev_prepare_cmd does more like increasing the id. This
> > qmp_capabilities is a special command that is part of the
> > initialisation. I need to know which id is used so that I can track it
> > later and found out when QEMU is ready to receive other commands.
> 
> But see below about the id.  I think you should be using the normal
> id.
> 
> In another thread you were talking about possibly trying to reuse an
> existing session and putting the pid in the id or something.  In this
> case a fixed id for the capabilities command is not good either.
> 
> Let me bring forward the part where we handle the resuls:
> 
> > > > +        id = libxl__json_object_get_integer(o);
> > > > +
> > > > +        if (id == QMP_CAPABILITY_NEGOTIATION_MSGID) {
> > > > +            /* We have a response to our qmp_capabilities cmd */
> > > > +            if (ev->qmp_state != qmp_state_capability_negotiation ||
> > > > +                type != LIBXL__QMP_MESSAGE_TYPE_RETURN)
> > > > +                goto out_unknown_id;
> > > > +            qmp_ev_set_state(gc, ev, qmp_state_connected);
> > > > +            return 0;
> > > > +        }
> > > 
> > > I'm a bit puzzled about the capability negotation, its id, etc.:
> > > 
> > > * Why does QMP_CAPABILITY_NEGOTIATION_MSGID exist at all ?  You
> > >   could just use a normal id, couldn't you ?  And you'd be able to
> > >   tell from your own state that it was the right value.
> > 
> > But I would then needs to track two different ids, one for the
> > qmp_capabilities command, and one for the user's command.
> 
> But you only construct the id when you prepare the command into
> tx_buf.  So you only have one id at a time: the id of the most
> recently prepared command.  Or am I wrong ?
> 
> ...
> 
> I have looked at this code again and I was wrong.  You format the
> user's message, with id, right at the beginning.  (The sequencing of
> events in the code structure is slightly odd, in that the capabilities
> message is formatted after the user's message, but sent beforehand.)
> 
> I still think for the reasons above that you probably can't have a
> fixed QMP_CAPABILITY_NEGOTIATION_MSGID because we may want to be able
> to reuse an old transport connection in the future.
> 
> So that means you do need two ids.  (I think you can't defer
> formatting the user's message, baking an id into it, because the
> lifetime of the callers' json object is too short for you to save it.)
> 
> I'm not sure I can see sensible a way of doing this that doesn't have
> *three* id variables:
>   - a counter for generating new ids
>   - the id put in the capabilities command
>   - the id put in the user's command

That sounds fine.

> > We could call qmp_ev_prepare_cmd, qmp_ev_prepare_user_cmd.
> > 
> > > > +    case LIBXL__QMP_MESSAGE_TYPE_RETURN:
> > > > +    case LIBXL__QMP_MESSAGE_TYPE_ERROR:
> > > ...
> > > > +        o = libxl__json_map_get("id", resp, JSON_INTEGER);
> > > > +        if (!o) {
> > > > +            /*
> > > > +             * If "id" isn't present, an error occur on the server 
> > > > before
> > > > +             * it has read the "id" provided by libxl.
> > > > +             */
> > > > +            qmp_ev_parse_error_messages(egc, ev, resp);
> > > > +            return ERROR_PROTOCOL_ERROR_QMP;
> > > 
> > > I think you wanted to return the error code from
> > > qmp_ev_parse_error_messages ?
> > 
> > Yeah, but then I've added to the public documentation that ERROR_QMP_*
> > means that ev_qmp is still Connected.
> 
> Oh.  Right.  I see.  Now that you have explained I think the code is
> correct.  Can you add a comment about this ?  Something like this:
>    /*
>     * Deliberately squash all errors into ERROR_PROTOCOL_ERROR_QMP.
>     * qmp_ev_parse_error_messages will only return that, or
>     * ERROR_QMP_*; but ERROR_QMP_* is reserved for errors resulting
>     * from the caller's command, and when we return that we promise
>     * that the ev_qmp is Connected.
>     */

Will do.

> > > * Why are we even doing capability negotation when we throw the answer
> > >   away ?
> > 
> > It is part of the protocol.
> > 
> > And qmp_capabilities doesn't return anything (just an empty object).
> 
> Oh, is the purpose to inform qemu what our capabilities are ?

It actually inform qemu of the capabilities we want. The QMP server will
not accept any other commands before the client execute qmp_capabilities.

The conversation between QEMU and libxl goes like this:

QEMU: Hi, I'm QEMU 3.0, I can do "oob".
libxl: Hi, I don't need any capabilities.
QEMU: Thanks, you can now run any other command you like.
libxl sends the user command.

Or in term of edited JSON:
QEMU:  { "QMP": {"version": XXX, "capabilities": ["oob"] } }
libxl: { "execute": "qmp_capabilities", "arguments": {} }
QEMU:  { "return": {}}

And here is a link to the example in the QMP spec document
(3. QMP Examples):
https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/qmp-spec.txt;h=8f7da0245d51447be7df2b3d4b105bad9fbec0b3;hb=HEAD#l244

Maybe I should add somewhere in a comment where to find the QMP spec,
even so we already have "This file implement a client for QMP (QEMU
Monitor Protocol). For the Specification, see in the QEMU repository."
at the top of libxl_qmp.c


Thanks,

-- 
Anthony PERARD

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.