|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v7 06/14] libxl_qmp: Implementation of libxl__ev_qmp_*
On Fri, Dec 21, 2018 at 03:36:18PM +0000, Ian Jackson wrote:
> Anthony PERARD writes ("[PATCH v7 06/14] libxl_qmp: Implementation of
> libxl__ev_qmp_*"):
> > This patch implement the API libxl__ev_qmp documented in the previous
> > patch, "libxl: Design of an async API to issue QMP commands to QEMU".
> >
> > Since this API is to interact with QEMU via the QMP protocol, it also
> > implement a QMP client. The specification for the QEMU Machine Protocol
> > (QMP) can be found in the QEMU repository at:
> > https://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/interop/qmp-spec.txt
>
> Thanks.
>
> I have only fairly minor comments now. The biggest one remaining is
> about the use of EGC_GC which I think probably wants to become
> STATE_AO_GC throughout. See below...
>
>
> > diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
> > index 1c7a3b22f4..056de9de2f 100644
> > --- a/tools/libxl/libxl_internal.h
> > +++ b/tools/libxl/libxl_internal.h
> > @@ -412,6 +412,19 @@ _hidden int libxl__ev_qmp_send(libxl__gc *gc,
> > libxl__ev_qmp *ev,
>
> > + /* receive buffer */
> > + char *rx_buf;
>
> rx_buf needs a comment saying it comes from NOGC since otherwise one
> would assume it came from the ao gc like the other buffers.
>
> (Could it come from the ao gc instead?)
I guess it's fine to have the rx_buf comes from ao gc. The buffer isn't
that big (maybe 100kB in worse cases).
>
> > +static void qmp_ev_ensure_reading_writing(libxl__gc *gc, libxl__ev_qmp *ev)
> > + /* Update the state of `efd` to match the permited state */
> > +{
>
> This function is legal only in states other than disconnected.
> Needs to be documented.
Will do.
> > +static void qmp_ev_set_state(libxl__gc *gc, libxl__ev_qmp *ev,
> > + libxl__qmp_state new_state)
> > + /* on entry: !broken and !disconnected */
> > +{
> > + switch (new_state) {
> > + case qmp_state_disconnected:
> > + break;
> > + case qmp_state_connecting:
> > + assert(ev->state == qmp_state_disconnected);
> > + break;
> > + case qmp_state_capability_negotiation:
> > + assert(ev->state == qmp_state_connecting);
> > + break;
> > + case qmp_state_waiting_reply:
> > + assert(ev->state == qmp_state_capability_negotiation ||
> > + ev->state == qmp_state_connected);
> > + break;
> > + case qmp_state_connected:
> > + assert(ev->state == qmp_state_waiting_reply);
> > + break;
> > + }
> > +
> > + ev->state = new_state;
>
> I think this function needs to update efd ? What am I missing ?
Yes, I think it's fine to call qmp_ev_ensure_reading_writing here ( or
even inline it) and qmp_ev_ensure_reading_writing won't needs to be
call from other places.
> The comment doesn't say what the output state is but naturally I
> assume that it is precisely new_state, and not some transitional
> mixture. If it is intended to produce a transitional mixture that
> ought to be documented.
>
> For a concrete example: if on entry, with new_state==disconnected, we
> are `connecting' then: efd will be looking for POLLIN. But it needs
> to become Idle.
Once I update efd with this function, I think qmp_ev_set_state should
always output precisely new_state. But new_state alone might not be
enough in few cases (waiting_reply) to describe a full state, but it
will be one of the possible internal state as describe in the state
documentation of the implementation.
>
> > +/* Setup connection */
> > +
> > +static int qmp_ev_connect(libxl__gc *gc, libxl__ev_qmp *ev)
> > + /* disconnected -> connecting but with `msg` free
> > + * on error: broken */
> > +{
>
> This function looks fine to me. However, earlier I wrote this:
>
> Contrary to the state description, this function does not
> transition rx_buf from free to used. However, I think this
> would probably be more easily remedied by changing the
> definition of `used' to permit NULL/0/0. You might want to use
> a different word to `used', `inuse' perhaps ?
>
> This is still true. That is, your state description for `connecting'
> says that rx_buf is `used'. And your description lower about what
> rx_buf being `used' means says that rx_buf must be `allocated'.
>
> I think this would probably be best resolved by writing:
>
> * free used
> - * rx_buf NULL allocated
> + * rx_buf NULL NULL or allocated
> * rx_buf_size 0 allocation size of `rx_buf`
> * rx_buf_used 0 <= rx_buf_size, actual data in the
> * buffer
I'll make this change.
> Ie just to change the internal spec. I am going to assume for the
> rest of the review that the code is right and the internal spec will
> be updated. (I don't think it is necessary to change the descriptions
> of rx_buf_size and rx_buf_used; it will be clear that the `allocation
> size' of a NULL must be 0.)
>
>
> > +/* QMP FD callbacks */
> > +
> > +static void qmp_ev_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
> ...
>
> > +static int qmp_ev_callback_writable(libxl__gc *gc,
> > + libxl__ev_qmp *ev, int fd)
> > + /* on entry: !disconnected
> > + * on return, one of these state transition:
> > + * waiting_reply (with msg set) -> waiting_reply (with msg free)
> > + * tx_buf set -> same state or tx_buf free
> > + * tx_buf free -> no state change
> > + * on error: broken */
> > +{
> ...
> > + assert(ev->tx_buf);
> > + if (!ev->tx_buf)
> > + return 0;
>
> I think the if is vestigial.
I'll remove it.
> > + while (ev->tx_buf_off < ev->tx_buf_len) {
> > + r = write(fd, ev->tx_buf + ev->tx_buf_off,
> > + ev->tx_buf_len - ev->tx_buf_off);
> > + if (r < 0) {
> > + if (errno == EINTR)
> > + continue;
> > + if (errno == EWOULDBLOCK)
> > + break;
> > + LOGED(ERROR, ev->domid, "failed to write to QMP socket");
> > + return ERROR_FAIL;
> > + }
> > + ev->tx_buf_off += r;
>
> Can you assert that the value of r was within range ? (Perhaps this
> is paranoia on my part, but, still.)
I do assert the value of tx_buf_off which does include r.
But I can add
`assert(r > 0 && r <= (ev->rx_buf_size - ev->rx_buf_used));'.
> > +static int qmp_ev_get_next_msg(libxl__egc *egc, libxl__ev_qmp *ev,
> > + libxl__json_object **o_r)
> > + /* Find a JSON object and store it in o_r.
> > + * return ERROR_NOTFOUND if no object is found.
> > + * `o_r` is allocated within `egc`.
>
> Why not allocate o_r from within AO_GC ?
>
> ISTM that taking it from egc is a beartrap. If you do want to
> allocate it from egc, this should definitely be documented in the
> internal public api for libxl__ev_qmp_callback. Right now a caller
> might well reasonably assume that the libxl__json_object *response
> they get is useable for the whole ao. Indeed future callers might
> even need that semantic.
Indeed, that can be an issue. I'll make the changes to use ao gc instead
of egc.
> To do this, use STATE_AO_GC instead of EGC_GC.
> TBH I think you should probably do that throughout.
>
> > +static int qmp_ev_parse_error_messages(libxl__egc *egc,
> > + libxl__ev_qmp *ev,
> > + const libxl__json_object *resp)
> > + /* no state change */
> > +{
> > + EGC_GC;
> > + int rc;
> > + const char *s;
> > + const libxl__json_object *o;
> > + const libxl__json_object *err;
> > +
> > + /*
> > + * { "error": { "class": string, "desc": string } }
> > + */
> > +
> > + err = libxl__json_map_get("error", resp, JSON_MAP);
> > +
> > + o = libxl__json_map_get("class", err, JSON_STRING);
>
> I wondered: surely err can be NULL ? I didn't find any docs saying
> that it couldn't; nor that it tolerated NULL for o on input.
>
> However, reading the implementation I see that libxl__json_map_get
> calls libxl__json_object_is_map which does indeed handle o==0.
> Could you perhaps add a comment (in libxl_internal.h near
> libxl__json_map_get) et al about this ?
I'll add the comments.
> > +static int qmp_ev_handle_message(libxl__egc *egc,
> > + libxl__ev_qmp *ev,
> > + const libxl__json_object *resp)
> ...
> > + /* Prepare next message to send */
> > + assert(!ev->tx_buf);
> > + ev->id = ev->next_id++;
> > + buf = qmp_prepare_cmd(libxl__ao_inprogress_gc(ev->ao),
> > + "qmp_capabilities", NULL, ev->id);
>
> Erk, I don't like the open coded call to libxl__ao_inprogress_gc. I'm
> becoming more convinced you just wanted AO_GC or STATE_AO_GC
> everywhere.
>
> > +void libxl__ev_qmp_dispose(libxl__gc *gc, libxl__ev_qmp *ev)
> > + /* * -> disconnected */
> > +{
> > + LOGD(DEBUG, ev->domid, " ev %p", ev);
> > +
> > + free(ev->rx_buf);
> > +
> > + libxl__ev_fd_deregister(gc, &ev->efd);
> > + libxl__carefd_close(ev->cfd);
> > +
> > + libxl__ev_qmp_init(ev);
> > +}
>
> It's a small point, but it would be nicer to move the free of rx_buf
> nearer the call to libxl__ev_qmp_init which zeroes it.
As rx_buf is probably going to be allocated from ao gc, the free won't
be needed anymore. Or, I could realloc with a new size of 0:
libxl__realloc(maybe_gc, ev->rx_buf, 0);
and that would free the memory. I've check realloc, libxl__realloc and
libxl__free_all, and they all seems to do the right things when the new
size is 0.
Thanks,
--
Anthony PERARD
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |