<br><br><div class="gmail_quote"><div dir="ltr">On Thu, Oct 5, 2017, 11:20 AM Frediano Ziglio <<a href="mailto:fziglio@redhat.com">fziglio@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">><br>
> On Wed, Oct 4, 2017 at 12:43 PM, Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>> wrote:<br>
> >><br>
> >> Replace existing main while-loop with GMainLoop.<br>
> >><br>
> >> Use GIOChannel with g_io_add_watch() to manage IO<br>
> >> from x11 connections in the main loop.<br>
> >><br>
> >> udscs_connect() now internally creates GSources<br>
> >> by calling g_io_add_watch().<br>
> >> This enables GMainLoop integration,<br>
> >> clients no longer need to call<br>
> >> udscs_client_fill_fds() and<br>
> >> udscs_client_handle_fds() in a loop.<br>
> >> Read callback and udscs_write() functions as before.<br>
> >><br>
> >> Signals are also handled in the main loop,<br>
> >> using g_unix_signal_add().<br>
> >> SIGQUIT is currently not supported by GLib.<br>
> >><br>
> >> This enables further GTK+ integration in the future.<br>
> >><br>
> >> Signed-off-by: Victor Toso <<a href="mailto:victortoso@redhat.com" target="_blank">victortoso@redhat.com</a>><br>
> >> ---<br>
> >>  src/udscs.c           |  60 ++++++++++++++++++++++<br>
> >>  src/vdagent/vdagent.c | 138<br>
> >>  ++++++++++++++++++++++++--------------------------<br>
> >>  src/vdagent/vdagent.h |   4 ++<br>
> >>  3 files changed, 131 insertions(+), 71 deletions(-)<br>
> >><br>
> >> diff --git a/src/udscs.c b/src/udscs.c<br>
> >> index 8b16f89..44842f4 100644<br>
> >> --- a/src/udscs.c<br>
> >> +++ b/src/udscs.c<br>
> >> @@ -31,6 +31,8 @@<br>
> >>  #include <errno.h><br>
> >>  #include <sys/socket.h><br>
> >>  #include <sys/un.h><br>
> >> +#include <glib.h><br>
> >> +#include <glib-unix.h><br>
> >>  #include "udscs.h"<br>
> >><br>
> >>  struct udscs_buf {<br>
> >> @@ -66,8 +68,16 @@ struct udscs_connection {<br>
> >><br>
> >>      struct udscs_connection *next;<br>
> >>      struct udscs_connection *prev;<br>
> >> +<br>
> >> +    GIOChannel                     *io_channel;<br>
> >> +    guint                           write_watch_id;<br>
> >> +    guint                           read_watch_id;<br>
> >>  };<br>
> >><br>
> >> +static gboolean udscs_io_channel_cb(GIOChannel *source,<br>
> >> +                                    GIOCondition condition,<br>
> >> +                                    gpointer data);<br>
> >> +<br>
> >>  struct udscs_connection *udscs_connect(const char *socketname,<br>
> >>      udscs_read_callback read_callback,<br>
> >>      udscs_disconnect_callback disconnect_callback,<br>
> >> @@ -103,6 +113,17 @@ struct udscs_connection *udscs_connect(const char<br>
> >> *socketname,<br>
> >>          return NULL;<br>
> >>      }<br>
> >><br>
> >> +    conn->io_channel = g_io_channel_unix_new(conn->fd);<br>
> >> +    if (!conn->io_channel) {<br>
> >> +        udscs_destroy_connection(&conn);<br>
> >> +        return NULL;<br>
> >> +    }<br>
> >> +    conn->read_watch_id =<br>
> >> +        g_io_add_watch(conn->io_channel,<br>
> >> +                       G_IO_IN | G_IO_ERR | G_IO_NVAL,<br>
> >> +                       udscs_io_channel_cb,<br>
> >> +                       conn);<br>
> >> +<br>
> >>      conn->read_callback = read_callback;<br>
> >>      conn->disconnect_callback = disconnect_callback;<br>
> >><br>
> >> @@ -141,6 +162,12 @@ void udscs_destroy_connection(struct udscs_connection<br>
> >> **connp)<br>
> >><br>
> >>      close(conn->fd);<br>
> >><br>
> >> +    g_clear_pointer(&conn->io_channel, g_io_channel_unref);<br>
> >> +    if (conn->write_watch_id != 0)<br>
> >> +        g_source_remove(conn->write_watch_id);<br>
> >> +    if (conn->read_watch_id != 0)<br>
> >> +        g_source_remove(conn->read_watch_id);<br>
> >> +<br>
> ><br>
> > I would put freeing io_channel as last, but as reference counting<br>
> > are used is just style.<br>
><br>
> I agree.<br>
> ><br>
> >>      if (conn->debug)<br>
> >>          syslog(LOG_DEBUG, "%p disconnected", conn);<br>
> >><br>
> >> @@ -198,6 +225,13 @@ int udscs_write(struct udscs_connection *conn,<br>
> >> uint32_t<br>
> >> type, uint32_t arg1,<br>
> >>                     conn, type, arg1, arg2, size);<br>
> >>      }<br>
> >><br>
> >> +    if (conn->io_channel && conn->write_watch_id == 0)<br>
> >> +        conn->write_watch_id =<br>
> >> +            g_io_add_watch(conn->io_channel,<br>
> >> +                           G_IO_OUT | G_IO_ERR | G_IO_NVAL,<br>
> >> +                           udscs_io_channel_cb,<br>
> >> +                           conn);<br>
> >> +<br>
> >>      if (!conn->write_buf) {<br>
> >>          conn->write_buf = new_wbuf;<br>
> >>          return 0;<br>
> >> @@ -353,6 +387,32 @@ int udscs_client_fill_fds(struct udscs_connection<br>
> >> *conn,<br>
> >> fd_set *readfds,<br>
> >>      return conn->fd + 1;<br>
> >>  }<br>
> >><br>
> >> +static gboolean udscs_io_channel_cb(GIOChannel *source,<br>
> >> +                                    GIOCondition condition,<br>
> >> +                                    gpointer data)<br>
> >> +{<br>
> >> +    struct udscs_connection *conn = data;<br>
> >> +<br>
> >> +    if (condition & G_IO_IN) {<br>
> >> +        udscs_do_read(&conn);<br>
> >> +        if (conn == NULL)<br>
> >> +            return G_SOURCE_REMOVE;<br>
> >> +        return G_SOURCE_CONTINUE;<br>
> >> +    }<br>
> >> +    if (condition & G_IO_OUT) {<br>
> >> +        udscs_do_write(&conn);<br>
> >> +        if (conn == NULL)<br>
> >> +            return G_SOURCE_REMOVE;<br>
> >> +        if (conn->write_buf)<br>
> >> +            return G_SOURCE_CONTINUE;<br>
> >> +        conn->write_watch_id = 0;<br>
> >> +        return G_SOURCE_REMOVE;<br>
> >> +    }<br>
> >> +<br>
> >> +    udscs_destroy_connection(&conn);<br>
> >> +    return G_SOURCE_REMOVE;<br>
> >> +}<br>
> >> +<br>
> >><br>
> >>  #ifndef UDSCS_NO_SERVER<br>
> >><br>
> >> diff --git a/src/vdagent/vdagent.c b/src/vdagent/vdagent.c<br>
> >> index fd2ba51..fe443df 100644<br>
> >> --- a/src/vdagent/vdagent.c<br>
> >> +++ b/src/vdagent/vdagent.c<br>
> >> @@ -34,8 +34,8 @@<br>
> >>  #include <sys/select.h><br>
> >>  #include <sys/stat.h><br>
> >>  #include <spice/vd_agent.h><br>
> >> -#include <glib.h><br>
> >>  #include <poll.h><br>
> >> +#include <glib-unix.h><br>
> >><br>
> >>  #include "vdagentd-proto.h"<br>
> >>  #include "vdagentd-proto-strings.h"<br>
> >> @@ -44,7 +44,6 @@<br>
> >><br>
> >>  G_DEFINE_TYPE (VDAgent, vdagent, G_TYPE_OBJECT);<br>
> >><br>
> >> -static int quit = 0;<br>
> >>  static int version_mismatch = 0;<br>
> >><br>
> >>  /* Command line options */<br>
> >> @@ -170,7 +169,7 @@ static void daemon_read_complete(struct<br>
> >> udscs_connection<br>
> >> **connp,<br>
> >>          if (strcmp((char *)data, VERSION) != 0) {<br>
> >>              syslog(LOG_INFO, "vdagentd version mismatch: got %s expected<br>
> >>              %s",<br>
> >>                     data, VERSION);<br>
> >> -            udscs_destroy_connection(connp);<br>
> >> +            g_main_loop_quit(agent->loop);<br>
> >>              version_mismatch = 1;<br>
> >>          }<br>
> >>          break;<br>
> >> @@ -228,25 +227,12 @@ static void daemon_read_complete(struct<br>
> >> udscs_connection **connp,<br>
> >>      }<br>
> >>  }<br>
> >><br>
> >> -static struct udscs_connection *client_setup_sync(VDAgent *agent)<br>
> >> +static void daemon_disconnect_cb(struct udscs_connection *conn)<br>
> >>  {<br>
> >> -    struct udscs_connection *conn = NULL;<br>
> >> -<br>
> >> -    while (!quit) {<br>
> >> -        conn = udscs_connect(vdagentd_socket, daemon_read_complete, NULL,<br>
> >> -                             vdagentd_messages, VDAGENTD_NO_MESSAGES,<br>
> >> -                             debug);<br>
> >> -        if (conn || !do_daemonize || quit) {<br>
> >> -            break;<br>
> >> -        }<br>
> >> -        sleep(1);<br>
> >> -    }<br>
> >> -    return conn;<br>
> >> -}<br>
> >> -<br>
> >> -static void quit_handler(int sig)<br>
> >> -{<br>
> >> -    quit = 1;<br>
> >> +    VDAgent *agent = udscs_get_user_data(conn);<br>
> >> +    agent->conn = NULL;<br>
> >> +    if (agent->loop)<br>
> >> +        g_main_loop_quit(agent->loop);<br>
> >>  }<br>
> >><br>
> >>  /* When we daemonize, it is useful to have the main process<br>
> >> @@ -304,13 +290,40 @@ static int file_test(const char *path)<br>
> >>      return stat(path, &buffer);<br>
> >>  }<br>
> >><br>
> >> +static gboolean x11_io_channel_cb(GIOChannel *source,<br>
> >> +                                  GIOCondition condition,<br>
> >> +                                  gpointer data)<br>
> >> +{<br>
> >> +    VDAgent *agent = data;<br>
> >> +    vdagent_x11_do_read(agent->x11);<br>
> >> +<br>
> >> +    return G_SOURCE_CONTINUE;<br>
> >> +}<br>
> >> +<br>
> >> +gboolean vdagent_signal_handler(gpointer user_data)<br>
> >> +{<br>
> >> +    VDAgent *agent = user_data;<br>
> >> +    do_daemonize = FALSE;<br>
> ><br>
> > why you removed quit and used do_daemonize?<br>
> > Is not that readable setting do_daemonize if you want to<br>
> > quit.<br>
> ><br>
> I understand that it's less readable, but do we really need to<br>
> keep that extra variable around when the effect of both is the same?<br>
> What about creating a simple function like vdagent_exit()<br>
> which would quit the mainloop and set do_daemonize to FALSE?<br>
> Or renaming do_daemonize to something like keep_running,<br>
> or try_reconnect to make it more clear?<br>
><br>
<br>
#define keep_running do_daemonize<br>
<br>
??<br>
<br>
or you could release agent->loop and set to NULL so<br>
you can check it but this sounds more complicate.<br>
<br>
> >> +    g_main_loop_quit(agent->loop);<br>
> >> +    return G_SOURCE_REMOVE;<br>
> >> +}<br>
> >> +<br>
> >>  static void vdagent_init(VDAgent *self)<br>
> >>  {<br>
> >> +    self->loop = g_main_loop_new(NULL, FALSE);<br>
> >> +<br>
> >> +    g_unix_signal_add(SIGINT, vdagent_signal_handler, self);<br>
> >> +    g_unix_signal_add(SIGHUP, vdagent_signal_handler, self);<br>
> >> +    g_unix_signal_add(SIGTERM, vdagent_signal_handler, self);<br>
> >>  }<br>
> >><br>
> >>  static void vdagent_finalize(GObject *gobject)<br>
> >>  {<br>
> >>      VDAgent *self = VDAGENT(gobject);<br>
> >> +<br>
> >> +    g_clear_pointer(&self->x11_channel, g_io_channel_unref);<br>
> >> +    g_clear_pointer(&self->loop, g_main_loop_unref);<br>
> >> +<br>
> >>      vdagent_finalize_file_xfer(self);<br>
> >>      vdagent_x11_destroy(self->x11, self->conn == NULL);<br>
> >>      udscs_destroy_connection(&self->conn);<br>
> >> @@ -324,29 +337,50 @@ static void vdagent_class_init(VDAgentClass  *klass)<br>
> >>      gobject_class->finalize = vdagent_finalize;<br>
> >>  }<br>
> >><br>
> >> -static gboolean vdagent_init_sync(VDAgent *agent)<br>
> >> +static gboolean vdagent_init_async_cb(gpointer user_data)<br>
> >>  {<br>
> >> -    agent->conn = client_setup_sync(agent);<br>
> >> +    VDAgent *agent = user_data;<br>
> >> +<br>
> >> +    agent->conn = udscs_connect(vdagentd_socket,<br>
> >> +                                daemon_read_complete,<br>
> >> daemon_disconnect_cb,<br>
> >> +                                vdagentd_messages, VDAGENTD_NO_MESSAGES,<br>
> >> debug);<br>
> >>      if (agent->conn == NULL)<br>
> >> -        return FALSE;<br>
> >> +        return G_SOURCE_CONTINUE;<br>
> >>      udscs_set_user_data(agent->conn, agent);<br>
> >><br>
> >>      agent->x11 = vdagent_x11_create(agent->conn, debug, x11_sync);<br>
> >>      if (agent->x11 == NULL)<br>
> >> -        return FALSE;<br>
> >> +        goto err_init;<br>
> >> +    agent->x11_channel =<br>
> >> g_io_channel_unix_new(vdagent_x11_get_fd(agent->x11));<br>
> >> +    if (agent->x11_channel == NULL)<br>
> >> +        goto err_init;<br>
> >> +<br>
> >> +    g_io_add_watch(agent->x11_channel,<br>
> >> +                   G_IO_IN,<br>
> >> +                   x11_io_channel_cb,<br>
> >> +                   agent);<br>
> >><br>
<br>
Just realized a nasty problem with this.<br>
This function register a GSource in the default main context so<br>
you'll have (references):<br>
<br>
  main_context -> source with x11_io_channel_cb and agent<br>
<br>
however if you have a reconnection (goto reconnect in main)<br>
agent if freed and created again. But the source will still<br>
exits with old sources. When loop will be executed potentially<br>
these sources will be executed using the old agent pointer<br>
which now is invalid. Note that we register the watch for X11<br>
and the timeout for initialization.<br>
In case udscs disconnect you'll have the loop recreating<br>
the agent.<br>
Maybe (not sure) you could use g_main_context_find_source_by_user_data<br>
with agent and g_main_context_default to find the sources you should<br>
remove (not sure if the user_data in the source is the agent).<br></blockquote></div><div><br></div><div>I think there's an easy fix:</div><div>- both g_io_add_watch() and g_timeout_add_seconds() return a source tag (id)</div><div>- when freeing the agent, the sources can be removed with g_source_remove(id)</div><div><br></div><div>We already do this in the udscs.c, have a look. I somehow forgot to add this to the vdagent...</div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
> >>      if (!vdagent_init_file_xfer(agent))<br>
> >>          syslog(LOG_WARNING, "File transfer is disabled");<br>
> >><br>
> >> -    return TRUE;<br>
> >> +    if (agent->parent_socket) {<br>
> >> +        if (write(agent->parent_socket, "OK", 2) != 2)<br>
> >> +            syslog(LOG_WARNING, "Parent already gone.");<br>
> >> +        close(agent->parent_socket);<br>
> >> +        agent->parent_socket = 0;<br>
> >> +    }<br>
> >> +<br>
> >> +    return G_SOURCE_REMOVE;<br>
> >> +<br>
> >> +err_init:<br>
> >> +    g_main_loop_quit(agent->loop);<br>
> >> +    do_daemonize = FALSE;<br>
> ><br>
> > here too.<br>
> ><br>
> >> +    return G_SOURCE_REMOVE;<br>
> >>  }<br>
> >><br>
> >>  int main(int argc, char *argv[])<br>
> >>  {<br>
> >> -    fd_set readfds, writefds;<br>
> >> -    int n, nfds, x11_fd;<br>
> >>      int parent_socket = 0;<br>
> >> -    struct sigaction act;<br>
> >>      GOptionContext *context;<br>
> >>      GError *error = NULL;<br>
> >>      VDAgent *agent;<br>
> >> @@ -372,14 +406,6 @@ int main(int argc, char *argv[])<br>
> >>      if (vdagentd_socket == NULL)<br>
> >>          vdagentd_socket = g_strdup(VDAGENTD_SOCKET);<br>
> >><br>
> >> -    memset(&act, 0, sizeof(act));<br>
> >> -    act.sa_flags = SA_RESTART;<br>
> >> -    act.sa_handler = quit_handler;<br>
> >> -    sigaction(SIGINT, &act, NULL);<br>
> >> -    sigaction(SIGHUP, &act, NULL);<br>
> >> -    sigaction(SIGTERM, &act, NULL);<br>
> >> -    sigaction(SIGQUIT, &act, NULL);<br>
> >> -<br>
> >>      openlog("spice-vdagent", do_daemonize ? LOG_PID : (LOG_PID |<br>
> >>      LOG_PERROR),<br>
> >>              LOG_USER);<br>
> >><br>
> >> @@ -400,44 +426,14 @@ reconnect:<br>
> >><br>
> >>      agent = g_object_new(TYPE_VDAGENT, NULL);<br>
> >>      agent->parent_socket = parent_socket;<br>
> >> -    if (!vdagent_init_sync(agent)) {<br>
> >> -        quit = 1;<br>
> >> -        goto end_main;<br>
> >> -    }<br>
> >> -<br>
> >> -    if (parent_socket) {<br>
> >> -        if (write(parent_socket, "OK", 2) != 2)<br>
> >> -            syslog(LOG_WARNING, "Parent already gone.");<br>
> >> -        close(parent_socket);<br>
> >> -        parent_socket = 0;<br>
> >> -    }<br>
> >><br>
> >> -    while (agent->conn && !quit) {<br>
> >> -        FD_ZERO(&readfds);<br>
> >> -        FD_ZERO(&writefds);<br>
> >> -<br>
> >> -        nfds = udscs_client_fill_fds(agent->conn, &readfds, &writefds);<br>
> >> -        x11_fd = vdagent_x11_get_fd(agent->x11);<br>
> >> -        FD_SET(x11_fd, &readfds);<br>
> >> -        if (x11_fd >= nfds)<br>
> >> -            nfds = x11_fd + 1;<br>
> >> -<br>
> >> -        n = select(nfds, &readfds, &writefds, NULL, NULL);<br>
> >> -        if (n == -1) {<br>
> >> -            if (errno == EINTR)<br>
> >> -                continue;<br>
> >> -            syslog(LOG_ERR, "Fatal error select: %s", strerror(errno));<br>
> >> -            break;<br>
> >> -        }<br>
> >> +    g_timeout_add_seconds(1, vdagent_init_async_cb, agent);<br>
> >><br>
> ><br>
> > this adds an extra 1 second delay to the start of the agent.<br>
> > I understand you did this to handle signals during initialization too.<br>
> > An easy fix is to call vdagent_init_async_cb manually, something like<br>
> ><br>
> >    if (vdagent_init_async_cb(agent) != G_SOURCE_CONTINUE)<br>
> >        g_timeout_add_seconds(1, vdagent_init_async_cb, agent);<br>
> ><br>
> >    if (!quit)<br>
> >       g_main_loop_run(agent->loop);<br>
><br>
> Yes, that would fix it. Maybe we can initialize the agent outside<br>
> the mainloop and use sleep() instead (as before).<br>
> We probably won't handle signals when the<br>
> connection isn't initialized anyway.<br>
> ><br>
> >> -        if (FD_ISSET(x11_fd, &readfds))<br>
> >> -            vdagent_x11_do_read(agent->x11);<br>
> >> -        udscs_client_handle_fds(&agent->conn, &readfds, &writefds);<br>
> >> -    }<br>
> >> +    g_main_loop_run(agent->loop);<br>
> >><br>
> >> -end_main:<br>
> >>      g_clear_object(&agent);<br>
> >> -    if (!quit && do_daemonize)<br>
> >> +<br>
> >> +    if (do_daemonize)<br>
> >>          goto reconnect;<br>
> >><br>
> >>      g_free(fx_dir);<br>
> >> diff --git a/src/vdagent/vdagent.h b/src/vdagent/vdagent.h<br>
> >> index 8376315..2a62f6f 100644<br>
> >> --- a/src/vdagent/vdagent.h<br>
> >> +++ b/src/vdagent/vdagent.h<br>
> >> @@ -20,6 +20,7 @@<br>
> >>  #define __VDAGENT_H_<br>
> >><br>
> >>  #include <glib-object.h><br>
> >> +#include <glib.h><br>
> >>  #include "udscs.h"<br>
> >>  #include "x11.h"<br>
> >>  #include "file-xfers.h"<br>
> >> @@ -39,6 +40,9 @@ typedef struct VDAgent {<br>
> >>      struct vdagent_x11             *x11;<br>
> >>      struct vdagent_file_xfers      *xfers;<br>
> >>      struct udscs_connection        *conn;<br>
> >> +    GIOChannel                     *x11_channel;<br>
> >> +<br>
> >> +    GMainLoop                      *loop;<br>
> >><br>
> >>      int                             parent_socket;<br>
> >>  } VDAgent;<br>
> ><br>
> > Frediano<br>
><br>
> Cheers,<br>
>   Jakub<br>
><br>
<br>
Frediano<br></blockquote></div><div><br></div><div>Thanks for your review,</div><div>  Jakub</div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
</blockquote></div>