diff mbox

[2/7] qemu-char: Allow a chardev to reconnect if disconnected

Message ID 1393979937-9082-3-git-send-email-minyard@acm.org
State New
Headers show

Commit Message

Corey Minyard March 5, 2014, 12:38 a.m. UTC
From: Corey Minyard <cminyard@mvista.com>

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 <cminyard@mvista.com>
---
 include/sysemu/char.h |  16 ++++-
 qemu-char.c           | 165 +++++++++++++++++++++++++++++++++++++++++++++-----
 qemu-options.hx       |  11 +++-
 3 files changed, 173 insertions(+), 19 deletions(-)

Comments

Weidong Huang March 6, 2014, 6:47 a.m. UTC | #1
On 2014/3/5 8:38, minyard@acm.org wrote:

> From: Corey Minyard <cminyard@mvista.com>
> 
> 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 <cminyard@mvista.com>
> ---
>  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.
>  
> -@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet]
> +@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
>  
>  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.
>  
> +@option{reconnect} 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

The client will reconnect for ever when the server is dead. Is it better that try to reconnect several times?
Or add a option which specifies times of reconnect?
Michael S. Tsirkin March 6, 2014, 7:43 a.m. UTC | #2
On Tue, Mar 04, 2014 at 06:38:52PM -0600, minyard@acm.org wrote:
> From: Corey Minyard <cminyard@mvista.com>
> 
> 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 <cminyard@mvista.com>
> ---
>  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.
>  
> -@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet]
> +@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
>  
>  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.
>  
> +@option{reconnect} 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
Corey Minyard March 6, 2014, 5:18 p.m. UTC | #3
On 03/06/2014 12:47 AM, Weidong Huang wrote:
>  escape sequences.
>  
> +@option{reconnect} 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
> The client will reconnect for ever when the server is dead. Is it better that try to reconnect several times?
> Or add a option which specifies times of reconnect?
>
I'm not really sure about this.  For a remote IPMI BMC, you would want
it to reconnect for forever, there's no point it having it stop trying
after a while, since you want it to come back even if the BMC is down
for a day.  I would think the same if you wanted a remote console or
something of that nature.

What's the use case where you would want it to stop trying?  I can't
think of any.

Thanks,

-corey
Michael S. Tsirkin March 6, 2014, 5:41 p.m. UTC | #4
On Thu, Mar 06, 2014 at 11:18:00AM -0600, Corey Minyard wrote:
> On 03/06/2014 12:47 AM, Weidong Huang wrote:
> >  escape sequences.
> >  
> > +@option{reconnect} 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
> > The client will reconnect for ever when the server is dead. Is it better that try to reconnect several times?
> > Or add a option which specifies times of reconnect?
> >
> I'm not really sure about this.  For a remote IPMI BMC, you would want
> it to reconnect for forever, there's no point it having it stop trying
> after a while, since you want it to come back even if the BMC is down
> for a day.  I would think the same if you wanted a remote console or
> something of that nature.
> 
> What's the use case where you would want it to stop trying?  I can't
> think of any.
> 
> Thanks,
> 
> -corey

Depends on what happens after it fails.
Imagine whoever was using the socket is gone.
It might be useful to stop VM or exit
after a while rather than just hang about.
Corey Minyard March 6, 2014, 6:04 p.m. UTC | #5
On 03/06/2014 01:43 AM, Michael S. Tsirkin wrote:
>  
> +@option{reconnect} 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.

Maybe, but that would basically disable qemu until the connection came
back, because it would just be spinning trying to connect.  That doesn't
seem like a good idea to me.

> - it seems to me that if we don't specify nowait, this
> should stop the VM until reconnect.

That's an interesting thought.  Currently the option basically implies
nowait.  It's probably easy to add this, but I'm not sure of the use
case.  Anyone else have any opinions on this?

> - 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.

IIRC, that's what it does already, even without this patch.  If in
server mode, I believe you can disconnect and then reconnect a socket
and it works fine.  So I see no need for this function in server mode.

>
> 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
There's also
    - stop the VM until the socket reconnects.

I'm not sure why you would want to single out monitor as special. 
Especially if it is mux-ed into another connection.

>
> 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.

I just don't see why you would want to do this.  It means that the
connection is gone until the VM starts back up again.

One thing I did think of was to add a monitor command to try to
reconnect.  Then I could understand giving up, since you can get into
the monitor and kick it back off.

Thanks,

-corey


> 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
Michael S. Tsirkin March 6, 2014, 6:43 p.m. UTC | #6
On Thu, Mar 06, 2014 at 12:04:24PM -0600, Corey Minyard wrote:
> On 03/06/2014 01:43 AM, Michael S. Tsirkin wrote:
> >  
> > +@option{reconnect} 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.
> 
> Maybe, but that would basically disable qemu until the connection came
> back, because it would just be spinning trying to connect.

Why spin? queue the request in bh.

>  That doesn't
> seem like a good idea to me.
> 
> > - it seems to me that if we don't specify nowait, this
> > should stop the VM until reconnect.
> 
> That's an interesting thought.  Currently the option basically implies
> nowait.  It's probably easy to add this, but I'm not sure of the use
> case.

To avoid losing information that would have been sent there.

>  Anyone else have any opinions on this?
> 
> > - 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.
> 
> IIRC, that's what it does already, even without this patch.  If in
> server mode, I believe you can disconnect and then reconnect a socket
> and it works fine.  So I see no need for this function in server mode.
> 
> >
> > 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
> There's also
>     - stop the VM until the socket reconnects.
> 
> I'm not sure why you would want to single out monitor as special. 
> Especially if it is mux-ed into another connection.

it seems useful to make qemu exit when monitor
closes. many programs behave this way, this
makes sure if you lose connection,
the server does not hang around doing nothing.
combine with keepalive socket option,
you get a way to make qemu exit if it loses
networking, which just might help restore networking.

> >
> > 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.
> 
> I just don't see why you would want to do this.  It means that the
> connection is gone until the VM starts back up again.

e.g. if socket is used for serial, you might want to stop
VM to avoid losing output.

> One thing I did think of was to add a monitor command to try to
> reconnect.  Then I could understand giving up, since you can get into
> the monitor and kick it back off.
> 
> Thanks,
> 
> -corey
> 

if qemu exits on timeout, then that's also useful
as a way of automatically cleaning up resources
if you lose connection to qemu.
which might help host recover from e.g. oom
situation, and become responsive again.


> > 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
diff mbox

Patch

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.
 
-@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet]
+@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
 
 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.
 
+@option{reconnect} 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