<html><body><div style="font-family: times new roman, new york, times, serif; font-size: 12pt; color: #000000"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div><br></div><div class="gmail_quote"><div dir="ltr">On Wed, Oct 18, 2017, 9:17 AM Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">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 Tue, Oct 17, 2017 at 10:09 AM, Frediano Ziglio <<a href="mailto:fziglio@redhat.com" target="_blank">fziglio@redhat.com</a>> wrote:<br>
> > From: Jakub Janků <<a href="mailto:janku.jakub.jj@gmail.com" target="_blank">janku.jakub.jj@gmail.com</a>><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 | 159<br>
> > +++++++++++++++++++++++++++-----------------------<br>
> > 2 files changed, 145 insertions(+), 74 deletions(-)<br>
> ><br>
> > Changes since v4:<br>
> > - remove source ids and use g_source_remove_by_user_data to remove<br>
> > all sources, even signal ones;<br>
> > - register first connection event with g_timeout_add instead<br>
> > of calling the callback directly.<br>
> ><br>
> > diff --git a/src/udscs.c b/src/udscs.c<br>
> > index 8b16f89..3a1ea44 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>
> > + 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>
> > + g_clear_pointer(&conn->io_channel, g_io_channel_unref);<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 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, 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 69d8786..ac90634 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 "udscs.h"<br>
> > #include "vdagentd-proto.h"<br>
> > @@ -48,6 +48,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>
> > } VDAgent;<br>
> ><br>
> > static int quit = 0;<br>
> > @@ -177,7 +180,7 @@ static void daemon_read_complete(struct<br>
> > udscs_connection **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>
> > @@ -235,25 +238,12 @@ static void daemon_read_complete(struct<br>
> > udscs_connection **connp,<br>
> > }<br>
> > }<br>
> ><br>
> > -static struct udscs_connection *client_setup_sync(void)<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>
> > +static void daemon_disconnect_cb(struct udscs_connection *conn)<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>
> > @@ -311,10 +301,34 @@ 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>
> > + quit = TRUE;<br>
> > + g_main_loop_quit(agent->loop);<br>
> > + return G_SOURCE_REMOVE;<br>
> > +}<br>
> > +<br>
> > static VDAgent *vdagent_new(void)<br>
> > {<br>
> > VDAgent *agent = g_new0(VDAgent, 1);<br>
> ><br>
> > + agent->loop = g_main_loop_new(NULL, FALSE);<br>
> > +<br>
> > + g_unix_signal_add(SIGINT, vdagent_signal_handler, agent);<br>
> > + g_unix_signal_add(SIGHUP, vdagent_signal_handler, agent);<br>
> > + g_unix_signal_add(SIGTERM, vdagent_signal_handler, agent);<br>
> > +<br>
> > return agent;<br>
> > }<br>
> ><br>
> > @@ -324,14 +338,59 @@ static void vdagent_destroy(VDAgent *agent)<br>
> > vdagent_x11_destroy(agent->x11, agent->conn == NULL);<br>
> > udscs_destroy_connection(&agent->conn);<br>
> ><br>
> > + while (g_source_remove_by_user_data(agent))<br>
> > + continue;<br>
> > +<br>
> > + g_clear_pointer(&agent->x11_channel, g_io_channel_unref);<br>
> > + g_clear_pointer(&agent->loop, g_main_loop_unref);<br>
> > g_free(agent);<br>
> > }<br>
> ><br>
> > +static gboolean vdagent_init_async_cb(gpointer user_data)<br>
> > +{<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>
> > + g_timeout_add_seconds(1, vdagent_init_async_cb, agent);<br>
> > + return G_SOURCE_REMOVE;<br>
> > + }<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>
> > + 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>
> > + if (!vdagent_init_file_xfer(agent))<br>
> > + syslog(LOG_WARNING, "File transfer is disabled");<br>
> > +<br>
> > + if (parent_socket != -1) {<br>
> > + if (write(parent_socket, "OK", 2) != 2)<br>
> > + syslog(LOG_WARNING, "Parent already gone.");<br>
> > + close(parent_socket);<br>
> > + parent_socket = -1;<br>
> > + }<br>
> > +<br>
> > + return G_SOURCE_REMOVE;<br>
> > +<br>
> > +err_init:<br>
> > + g_main_loop_quit(agent->loop);<br>
> > + quit = TRUE;<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>
> > - struct sigaction act;<br>
> > GOptionContext *context;<br>
> > GError *error = NULL;<br>
> > VDAgent *agent;<br>
> > @@ -357,14 +416,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>
> > @@ -385,53 +436,13 @@ reconnect:<br>
> ><br>
> > agent = vdagent_new();<br>
> ><br>
> > - agent->conn = client_setup_sync();<br>
> > - if (agent->conn == NULL) {<br>
> > - return 1;<br>
> > - }<br>
> > - udscs_set_user_data(agent->conn, agent);<br>
> > + g_timeout_add(0, vdagent_init_async_cb, agent);<br>
> ><br>
> > - agent->x11 = vdagent_x11_create(agent->conn, debug, x11_sync);<br>
> > - if (!agent->x11) {<br>
> > - udscs_destroy_connection(&agent->conn);<br>
> > - return 1;<br>
> > - }<br>
> > -<br>
> > - if (!vdagent_init_file_xfer(agent))<br>
> > - syslog(LOG_WARNING, "File transfer is disabled");<br>
> > -<br>
> > - if (parent_socket != -1) {<br>
> > - if (write(parent_socket, "OK", 2) != 2)<br>
> > - syslog(LOG_WARNING, "Parent already gone.");<br>
> > - close(parent_socket);<br>
> > - parent_socket = -1;<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>
> > -<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>
> > vdagent_destroy(agent);<br>
> > agent = NULL;<br>
> > +<br>
> > if (!quit && do_daemonize)<br>
> > goto reconnect;<br>
> ><br>
> > --<br>
> > 2.13.6<br>
> ><br>
><br>
> Just one more note on this:<br>
> According to GTK docs for g_timeout_add_seconds():<br>
> "Note that the first call of the timer may not be precise for timeouts<br>
> of one second.<br>
> If you need finer precision and have such a timeout,<br>
> you may want to use g_timeout_add() instead."<br>
> (this is basically the same what Christophe pointed out)<br>
><br><br>
Yes, first iteration uses (last patch version) g_timeout_add.<br><br>
> Since we remove the source in vdagent_init_async_cb() each time,<br>
> the interval between each call might not be regular.<br>
><br><br>
>From g_timeout_add_seconds_full documentation:<br>
"Note that timeout functions may be delayed, due to the processing of other<br>
event sources. Thus they should not be relied on for precise timing. After<br>
each call to the timeout function, the time of the next timeout is recalculated<br>
based on the current time and the given interval"<br>
so creating the source every time does not change much on the timing.<br><br>
> One solution would be to use g_timeout_add().<br>
><br>
> Another solution:<br>
> - in main():<br>
> replace g_timeout_add(0, vdagent_init_async_cb, agent);<br>
> with g_idle_add(vdagent_init_async_cb, agent);<br>
><br>
> - in vdagent_init_async_cb():<br>
> if (agent->conn == NULL) {<br>
> if (g_idle_remove_by_data(agent)) {<br>
> g_timeout_add_seconds(1, vdagent_init_async_cb, agent);<br>
> return G_SOURCE_REMOVE;<br>
> }<br>
> return G_SOURCE_CONTINUE;<br>
> }<br>
> This way, the source doesn't have to be recreated every single time.<br>
><br>
> Jakub<br>
><br><br>
1 seconds is quite "human" timing thing, I don't think we need big<br>
precision. If we really need precision I would use g_file_monitor_directory<br>
where available to avoid entirely all the polling (which would be limited<br>
to Unix systems without inotify or other GLib compatible implementations).<br>
This way you can detect more or less precisely when the unix socket is<br>
created/modified in the directory without having to check every second.<br><br>
The g_idle solution should work and avoid creating the source every time,<br>
only drawback is that adding another g_idle in a possible future is<br>
tricky and prone to bugs as potentially the g_idle_remove_by_data would<br>
remove the wrong source.<br><br>
Frediano<br></blockquote></div><div><br></div><div>That's true, however, I don't think we will add another idle function any time soon.</div></blockquote><div>That's the problem. Not soon but in a year or more people could add a g_idle call and<br></div><div>strangely in some corner cases the callback won't be called and will have to spend time<br></div><div>to debug the reason till will find the g_idle_remove_by_data deleting its g_idle source.<br></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div>It was just a note, I think we could leave it as it is,</div><div>if nobody else complains.</div><div><br></div><div>Thanks,</div><div>Jakub</div></blockquote><div>Frediano<br></div><div><br></div></div></body></html>