qemu-devel
[Top][All Lists]
Advanced

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

Re: [Qemu-devel] [PATCH 2/7] qemu-char: Allow a chardev to reconnect if


From: Michael S. Tsirkin
Subject: Re: [Qemu-devel] [PATCH 2/7] qemu-char: Allow a chardev to reconnect if disconnected
Date: Thu, 6 Mar 2014 09:43:35 +0200

On Tue, Mar 04, 2014 at 06:38:52PM -0600, address@hidden wrote:
> From: Corey Minyard <address@hidden>
> 
> Allow a socket that connects to reconnect on a periodic basis if it
> fails to connect at startup or if the connection drops while in use.
> 
> Signed-off-by: Corey Minyard <address@hidden>
> ---
>  include/sysemu/char.h |  16 ++++-
>  qemu-char.c           | 165 
> +++++++++++++++++++++++++++++++++++++++++++++-----
>  qemu-options.hx       |  11 +++-
>  3 files changed, 173 insertions(+), 19 deletions(-)
> 
> diff --git a/include/sysemu/char.h b/include/sysemu/char.h
> index f6844dd..1800c54 100644
> --- a/include/sysemu/char.h
> +++ b/include/sysemu/char.h
> @@ -53,6 +53,17 @@ typedef struct {
>  
>  typedef void IOEventHandler(void *opaque, int event);
>  
> +struct ReconData {
> +    void *opaque;
> +    uint64_t recon_time;
> +    struct ReconHandlers *handlers;
> +    void (*state_change)(void *open_opaque, int is_open);
> +    void *state_change_opaque;
> +    void (*reconnected)(struct ReconData *r);
> +    void (*connection_lost)(struct ReconData *r);
> +    void (*shutdown)(struct ReconData *r);
> +};
> +
>  struct CharDriverState {
>      void (*init)(struct CharDriverState *s);
>      int (*chr_write)(struct CharDriverState *s, const uint8_t *buf, int len);
> @@ -82,6 +93,8 @@ struct CharDriverState {
>      guint fd_in_tag;
>      QemuOpts *opts;
>      QTAILQ_ENTRY(CharDriverState) next;
> +    GSList *backend;
> +    struct ReconData *recon;
>  };
>  
>  /**
> @@ -293,7 +306,8 @@ CharDriverState *qemu_chr_find(const char *name);
>  QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename);
>  
>  void register_char_driver(const char *name,
> -        CharDriverState *(*open)(CharDriverState *, QemuOpts *));
> +        CharDriverState *(*open)(CharDriverState *, QemuOpts *),
> +        int (*recon_setup)(CharDriverState *));
>  void register_char_driver_qapi(const char *name, ChardevBackendKind kind,
>          void (*parse)(QemuOpts *opts, ChardevBackend *backend, Error 
> **errp));
>  
> diff --git a/qemu-char.c b/qemu-char.c
> index fc688cf..d9838aa 100644
> --- a/qemu-char.c
> +++ b/qemu-char.c
> @@ -88,6 +88,44 @@
>  /***********************************************************/
>  /* character device */
>  
> +struct GenericReconData {
> +    QEMUTimer *recon_timer;
> +};
> +
> +static void generic_recon_timeout(void *opaque)
> +{
> +    struct ReconData *r = opaque;
> +    struct GenericReconData *d = r->opaque;
> +
> +    timer_mod(d->recon_timer,
> +              (get_clock() + (r->recon_time * get_ticks_per_sec())));
> +    r->state_change(r->state_change_opaque, 0);
> +}
> +
> +static void generic_reconnected(struct ReconData *r)
> +{
> +    struct GenericReconData *d = r->opaque;
> +
> +    timer_del(d->recon_timer);
> +}
> +
> +static void generic_connection_lost(struct ReconData *r)
> +{
> +    struct GenericReconData *d = r->opaque;
> +
> +    r->state_change(r->state_change_opaque, 1);
> +    timer_mod(d->recon_timer,
> +              (get_clock() + (r->recon_time * get_ticks_per_sec())));
> +}
> +
> +static void generic_recon_shutdown(struct ReconData *r)
> +{
> +    struct GenericReconData *d = r->opaque;
> +    timer_free(d->recon_timer);
> +    free(d);
> +    r->opaque = NULL;
> +}
> +
>  static QTAILQ_HEAD(CharDriverStateHead, CharDriverState) chardevs =
>      QTAILQ_HEAD_INITIALIZER(chardevs);
>  
> @@ -96,9 +134,15 @@ void qemu_chr_be_event(CharDriverState *s, int event)
>      /* Keep track if the char device is open */
>      switch (event) {
>          case CHR_EVENT_OPENED:
> +            if (s->recon) {
> +                s->recon->reconnected(s->recon);
> +            }
>              s->be_open = 1;
>              break;
>          case CHR_EVENT_CLOSED:
> +            if (s->recon) {
> +                s->recon->connection_lost(s->recon);
> +            }
>              s->be_open = 0;
>              break;
>      }
> @@ -2582,6 +2626,7 @@ static void tcp_chr_close(CharDriverState *chr)
>          closesocket(s->listen_fd);
>      }
>      g_free(s);
> +    chr->opaque = NULL;
>      qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
>  }
>  
> @@ -2641,8 +2686,6 @@ static CharDriverState 
> *qemu_chr_open_socket_fd(CharDriverState *chr,
>      chr->get_msgfd = tcp_get_msgfd;
>      chr->chr_add_client = tcp_chr_add_client;
>      chr->chr_add_watch = tcp_chr_add_watch;
> -    /* be isn't opened until we get a connection */
> -    chr->explicit_be_open = true;
>  
>      if (is_listen) {
>          s->listen_fd = fd;
> @@ -2680,6 +2723,9 @@ static CharDriverState 
> *qemu_chr_open_socket(CharDriverState *chr,
>      bool do_nodelay     = !qemu_opt_get_bool(opts, "delay", true);
>      bool is_unix        = qemu_opt_get(opts, "path") != NULL;
>  
> +    /* be isn't opened until we get a connection */
> +    chr->explicit_be_open = true;
> +
>      if (is_unix) {
>          if (is_listen) {
>              fd = unix_listen_opts(opts, &local_err);
> @@ -2716,8 +2762,9 @@ static CharDriverState 
> *qemu_chr_open_socket(CharDriverState *chr,
>      if (fd >= 0) {
>          closesocket(fd);
>      }
> -    if (chr) {
> +    if (chr && chr->opaque) {
>          g_free(chr->opaque);
> +        chr->opaque = NULL;
>      }
>      return NULL;
>  }
> @@ -3128,6 +3175,7 @@ static void qemu_chr_parse_mux(QemuOpts *opts, 
> ChardevBackend *backend,
>  
>  typedef struct CharDriver {
>      const char *name;
> +    int (*recon_setup)(CharDriverState *);
>      /* old, pre qapi */
>      CharDriverState *(*open)(CharDriverState *chr, QemuOpts *opts);
>      /* new, qapi-based */
> @@ -3138,13 +3186,15 @@ typedef struct CharDriver {
>  static GSList *backends;
>  
>  void register_char_driver(const char *name,
> -                          CharDriverState *(*open)(CharDriverState*,QemuOpts 
> *))
> +                          CharDriverState *(*open)(CharDriverState*,QemuOpts 
> *),
> +                          int (*recon_setup)(CharDriverState *))
>  {
>      CharDriver *s;
>  
>      s = g_malloc0(sizeof(*s));
>      s->name = g_strdup(name);
>      s->open = open;
> +    s->recon_setup = recon_setup;
>  
>      backends = g_slist_append(backends, s);
>  }
> @@ -3162,6 +3212,50 @@ void register_char_driver_qapi(const char *name, 
> ChardevBackendKind kind,
>      backends = g_slist_append(backends, s);
>  }
>  
> +static void generic_recon_state_change(void *opaque, int is_open)
> +{
> +    struct CharDriverState *chr = opaque;
> +
> +    if (!is_open) {
> +        struct CharDriver *cd = chr->backend->data;
> +
> +        if (chr->be_open) {
> +            return;
> +        }
> +
> +        cd->open(chr, chr->opts);
> +    } else {
> +        void (*chr_close)(struct CharDriverState *chr) = chr->chr_close;
> +
> +        if (chr_close) {
> +            chr->chr_close = NULL;
> +            chr_close(chr);
> +        }
> +    }
> +}
> +
> +static int generic_recon_setup(struct CharDriverState *chr)
> +{
> +    struct GenericReconData *d;
> +
> +    d = g_malloc(sizeof(*d));
> +    chr->recon->opaque = d;
> +
> +    chr->recon->reconnected = generic_reconnected;
> +    chr->recon->connection_lost = generic_connection_lost;
> +    chr->recon->shutdown = generic_recon_shutdown;
> +    chr->recon->state_change = generic_recon_state_change;
> +    chr->recon->state_change_opaque = chr;
> +
> +    d->recon_timer = timer_new(QEMU_CLOCK_REALTIME, SCALE_NS,
> +                               generic_recon_timeout, chr->recon);
> +
> +    /* Make sure it connects in time. */
> +    timer_mod(d->recon_timer,
> +              (get_clock() + (chr->recon->recon_time * 
> get_ticks_per_sec())));
> +    return 1;
> +}
> +
>  CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
>                                      void (*init)(struct CharDriverState *s),
>                                      Error **errp)
> @@ -3169,6 +3263,7 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
>      CharDriver *cd;
>      CharDriverState *chr;
>      GSList *i;
> +    uint64_t recon_time;
>  
>      if (qemu_opts_id(opts) == NULL) {
>          error_setg(errp, "chardev: no id specified");
> @@ -3244,11 +3339,41 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts 
> *opts,
>      }
>  
>      chr = g_malloc0(sizeof(CharDriverState));
> -    chr = cd->open(chr, opts);
> -    if (!chr) {
> -        error_setg(errp, "chardev: opening backend \"%s\" failed",
> -                   qemu_opt_get(opts, "backend"));
> -        goto err;
> +
> +    chr->backend = i;
> +    recon_time = qemu_opt_get_number(opts, "reconnect", 0);
> +    if (recon_time) {
> +        CharDriver *d = chr->backend->data;
> +        if (!d->recon_setup) {
> +            g_free(chr);
> +            fprintf(stderr, "chardev: reconnect not supported on %s\n",
> +                    qemu_opt_get(opts, "backend"));
> +            return NULL;
> +        }
> +        if (qemu_opt_get_bool(opts, "server", 0)) {
> +            g_free(chr);
> +            fprintf(stderr, "chardev: server device cannot reconnect\n");
> +            return NULL;
> +        }
> +        chr->opts = opts;
> +        chr->recon = g_malloc(sizeof(*chr->recon));
> +        chr->recon->recon_time = recon_time;
> +        if (!d->recon_setup(chr)) {
> +            g_free(chr->recon);
> +            g_free(chr);
> +            fprintf(stderr, "chardev: Unable to set up reconnect\n");
> +            return NULL;
> +        }
> +    }
> +
> +    if (!cd->open(chr, opts)) {
> +        if (!chr->recon) {
> +            /* Reconnect is not enabled, give up */
> +            fprintf(stderr, "chardev: opening backend \"%s\" failed\n",
> +                    qemu_opt_get(opts, "backend"));
> +            g_free(chr);
> +            return NULL;
> +        }
>      }
>  
>      if (!chr->filename)
> @@ -3267,7 +3392,8 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
>          int len = strlen(qemu_opts_id(opts)) + 6;
>          base->label = g_malloc(len);
>          snprintf(base->label, len, "%s-base", qemu_opts_id(opts));
> -        chr = qemu_chr_open_mux(chr, base);
> +        chr = g_malloc0(sizeof(CharDriverState));
> +        qemu_chr_open_mux(chr, base);
>          chr->filename = base->filename;
>          chr->avail_connections = MAX_MUX;
>          QTAILQ_INSERT_TAIL(&chardevs, chr, next);
> @@ -3275,7 +3401,6 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
>          chr->avail_connections = 1;
>      }
>      chr->label = g_strdup(qemu_opts_id(opts));
> -    chr->opts = opts;
>      return chr;
>  
>  err:
> @@ -3378,9 +3503,16 @@ void qemu_chr_fe_release(CharDriverState *s)
>  
>  void qemu_chr_delete(CharDriverState *chr)
>  {
> +    void (*chr_close)(struct CharDriverState *chr) = chr->chr_close;
> +
>      QTAILQ_REMOVE(&chardevs, chr, next);
> -    if (chr->chr_close) {
> -        chr->chr_close(chr);
> +    if (chr_close) {
> +        chr->chr_close = NULL;
> +        chr_close(chr);
> +    }
> +    if (chr->recon) {
> +        chr->recon->shutdown(chr->recon);
> +        g_free(chr->recon);
>      }
>      g_free(chr->filename);
>      g_free(chr->label);
> @@ -3515,6 +3647,9 @@ QemuOptsList qemu_chardev_opts = {
>              .name = "mux",
>              .type = QEMU_OPT_BOOL,
>          },{
> +            .name = "reconnect",
> +            .type = QEMU_OPT_NUMBER,
> +        },{
>              .name = "signal",
>              .type = QEMU_OPT_BOOL,
>          },{
> @@ -3811,8 +3946,8 @@ void qmp_chardev_remove(const char *id, Error **errp)
>  static void register_types(void)
>  {
>      register_char_driver_qapi("null", CHARDEV_BACKEND_KIND_NULL, NULL);
> -    register_char_driver("socket", qemu_chr_open_socket);
> -    register_char_driver("udp", qemu_chr_open_udp);
> +    register_char_driver("socket", qemu_chr_open_socket, 
> generic_recon_setup);
> +    register_char_driver("udp", qemu_chr_open_udp, NULL);
>      register_char_driver_qapi("ringbuf", CHARDEV_BACKEND_KIND_RINGBUF,
>                                qemu_chr_parse_ringbuf);
>      register_char_driver_qapi("file", CHARDEV_BACKEND_KIND_FILE,
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 56e5fdf..1002a3d 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1787,8 +1787,9 @@ ETEXI
>  DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
>      "-chardev null,id=id[,mux=on|off]\n"
>      "-chardev 
> socket,id=id[,host=host],port=host[,to=to][,ipv4][,ipv6][,nodelay]\n"
> -    "         [,server][,nowait][,telnet][,mux=on|off] (tcp)\n"
> -    "-chardev socket,id=id,path=path[,server][,nowait][,telnet],[mux=on|off] 
> (unix)\n"
> +    "         [,server][,nowait][,telnet][,mux=on|off][,reconnect=seconds] 
> (tcp)\n"
> +    "-chardev 
> socket,id=id,path=path[,server][,nowait][,telnet][,mux=on|off]\n"
> +    "         [,reconnect=seconds] (unix)\n"
>      "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
>      "         [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
>      "-chardev msmouse,id=id[,mux=on|off]\n"
> @@ -1860,7 +1861,7 @@ Options to each backend are described below.
>  A void device. This device will not emit any data, and will drop any data it
>  receives. The null backend does not take any options.
>  
> address@hidden -chardev socket ,address@hidden address@hidden options} or 
> @var{unix options}] [,server] [,nowait] [,telnet]
> address@hidden -chardev socket ,address@hidden address@hidden options} or 
> @var{unix options}] [,server] [,nowait] [,telnet] [,address@hidden
>  
>  Create a two-way stream socket, which can be either a TCP or a unix socket. A
>  unix socket will be created if @option{path} is specified. Behaviour is
> @@ -1874,6 +1875,10 @@ connect to a listening socket.
>  @option{telnet} specifies that traffic on the socket should interpret telnet
>  escape sequences.
>  
> address@hidden specifies that if the client socket does not connect at
> +startup, or if the client socket is closed for some reason (like the other
> +end exited), wait the given number of seconds and attempt to reconnect.
> +
>  TCP and unix socket options are given below:
>  
>  @table @option

This looks useful but it bothers me that this
new option can't be used together with others:
server and nowait, which limits its usefulness
and would make it harder to extend in the future.

- I think that specifying a time-out should be separate from the option to
retry.
E.g. it might be quite reasonable to retry connecting immediately.
- it seems to me that if we don't specify nowait, this
should stop the VM until reconnect.
- I think this can co-exist with server option.
E.g. if we detect that the other side closed the socket,
start listening again. Or even listen all the time
and accept if disconnected.

I'd like to suggest we generalize this a bit:

When we detect a disconnect, three thinkable things to
do would be:
    - keep going as if nothing happened
        (default for all sockets except monitor)
    - exit qemu
        (default for monitor)
    - retry

When waiting for retry it seems reasonable to
either stop the VM or keep going (nowait option).

When retrying, it seems reasonable to specify a
timeout to make it fail.

All of the above seem to apply to server
and client sockets, with or without nowait option.

For clients only, it might be somewhat useful to
limit the network traffic caused by reconnects.

Thanks,
MST

> -- 
> 1.8.3.1



reply via email to

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