diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2010-12-02 18:59:24 +0100 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2010-12-05 18:15:22 +0100 |
commit | e0e77b800e5ac18f7e970b4ff70dd910972eba71 (patch) | |
tree | a327865ccbdea1da4072320b19dcf4e9a253f82a | |
parent | a08d433772c1df26fa159be56cb880c5402ed792 (diff) |
gtk: WIP use coroutines and GSocket to connect
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | gtk/Makefile.am | 4 | ||||
-rw-r--r-- | gtk/gio-coroutine.c | 144 | ||||
-rw-r--r-- | gtk/gio-coroutine.h | 61 | ||||
-rw-r--r-- | gtk/spice-channel-priv.h | 4 | ||||
-rw-r--r-- | gtk/spice-channel.c | 35 | ||||
-rw-r--r-- | gtk/spice-session-priv.h | 4 | ||||
-rw-r--r-- | gtk/spice-session.c | 73 |
8 files changed, 330 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index 1a5ff6f..2cc32c9 100644 --- a/configure.ac +++ b/configure.ac @@ -127,7 +127,7 @@ PKG_CHECK_MODULES(GTK2, gtk+-2.0) AC_SUBST(GTK2_CFLAGS) AC_SUBST(GTK2_LIBS) -PKG_CHECK_MODULES(GLIB2, glib-2.0) +PKG_CHECK_MODULES(GLIB2, glib-2.0 >= 2.22) AC_SUBST(GLIB2_CFLAGS) AC_SUBST(GLIB2_LIBS) @@ -135,6 +135,10 @@ PKG_CHECK_MODULES(GOBJECT2, gobject-2.0) AC_SUBST(GOBJECT2_CFLAGS) AC_SUBST(GOBJECT2_LIBS) +PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.10.0) +AC_SUBST(GIO_CFLAGS) +AC_SUBST(GIO_LIBS) + PKG_CHECK_MODULES(PULSE, libpulse libpulse-mainloop-glib) AC_SUBST(PULSE_CFLAGS) AC_SUBST(PULSE_LIBS) diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 444465b..9741450 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -39,6 +39,7 @@ SPICE_COMMON_CPPFLAGS = \ $(PULSE_CFLAGS) \ $(GTK2_CFLAGS) \ $(GLIB2_CFLAGS) \ + $(GIO_CFLAGS) \ $(GOBJECT2_CFLAGS) \ $(SSL_CFLAGS) \ $(NULL) @@ -82,6 +83,7 @@ libspice_client_glib_la_LDFLAGS = \ libspice_client_glib_la_LIBADD = \ $(GLIB2_LIBS) \ + $(GIO_LIBS) \ $(GOBJECT2_LIBS) \ $(CELT051_LIBS) \ $(JPEG_LIBS) \ @@ -119,6 +121,8 @@ libspice_client_glib_la_SOURCES = \ generated_marshallers.c \ generated_marshallers1.c \ coroutine.h \ + gio-coroutine.c \ + gio-coroutine.h \ \ channel-base.c \ channel-main.h \ diff --git a/gtk/gio-coroutine.c b/gtk/gio-coroutine.c new file mode 100644 index 0000000..cc90cca --- /dev/null +++ b/gtk/gio-coroutine.c @@ -0,0 +1,144 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2010 Red Hat, Inc. + Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.0 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "gio-coroutine.h" + +/* Main loop helper functions */ +static gboolean g_io_wait_helper(GSocket *sock G_GNUC_UNUSED, + GIOCondition cond, + gpointer data) +{ + struct coroutine *to = data; + coroutine_yieldto(to, &cond); + return FALSE; +} + +GIOCondition g_io_wait(GSocket *sock, GIOCondition cond) +{ + GIOCondition *ret; + GSource *src = g_socket_create_source(sock, + cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + NULL); + g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, coroutine_self(), NULL); + g_source_attach(src, NULL); + ret = coroutine_yield(NULL); + return *ret; +} + + +GIOCondition g_io_wait_interruptable(struct wait_queue *wait, + GSocket *sock, + GIOCondition cond) +{ + GIOCondition *ret; + gint id; + + wait->context = coroutine_self(); + GSource *src = g_socket_create_source(sock, + cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + NULL); + g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, + wait->context, NULL); + id = g_source_attach(src, NULL); + wait->waiting = TRUE; + ret = coroutine_yield(NULL); + wait->waiting = FALSE; + + if (ret == NULL) { + g_source_remove(id); + return 0; + } else + return *ret; +} + +void g_io_wakeup(struct wait_queue *wait) +{ + if (wait->waiting) + coroutine_yieldto(wait->context, NULL); +} + + +/* + * Call immediately before the main loop does an iteration. Returns + * true if the condition we're checking is ready for dispatch + */ +static gboolean g_condition_wait_prepare(GSource *src, + int *timeout) { + struct g_condition_wait_source *vsrc = (struct g_condition_wait_source *)src; + *timeout = -1; + return vsrc->func(vsrc->data); +} + +/* + * Call immediately after the main loop does an iteration. Returns + * true if the condition we're checking is ready for dispatch + */ +static gboolean g_condition_wait_check(GSource *src) +{ + struct g_condition_wait_source *vsrc = (struct g_condition_wait_source *)src; + return vsrc->func(vsrc->data); +} + +static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED, + GSourceFunc cb, + gpointer data) { + return cb(data); +} + +GSourceFuncs waitFuncs = { + .prepare = g_condition_wait_prepare, + .check = g_condition_wait_check, + .dispatch = g_condition_wait_dispatch, +}; + +static gboolean g_condition_wait_helper(gpointer data) +{ + struct coroutine *co = (struct coroutine *)data; + coroutine_yieldto(co, NULL); + return FALSE; +} + +gboolean g_condition_wait(g_condition_wait_func func, gpointer data) +{ + GSource *src; + struct g_condition_wait_source *vsrc; + + /* Short-circuit check in case we've got it ahead of time */ + if (func(data)) { + return TRUE; + } + + /* + * Don't have it, so yield to the main loop, checking the condition + * on each iteration of the main loop + */ + src = g_source_new(&waitFuncs, sizeof(struct g_condition_wait_source)); + vsrc = (struct g_condition_wait_source *)src; + + vsrc->func = func; + vsrc->data = data; + vsrc->co = coroutine_self(); + + g_source_attach(src, NULL); + g_source_set_callback(src, g_condition_wait_helper, coroutine_self(), NULL); + coroutine_yield(NULL); + return TRUE; +} diff --git a/gtk/gio-coroutine.h b/gtk/gio-coroutine.h new file mode 100644 index 0000000..ec9faa9 --- /dev/null +++ b/gtk/gio-coroutine.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2010 Red Hat, Inc. + Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.0 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef __GIO_COROUTINE_H__ +#define __GIO_COROUTINE_H__ + +#include <gio/gio.h> +#include "coroutine.h" + +G_BEGIN_DECLS + +struct wait_queue +{ + gboolean waiting; + struct coroutine *context; +}; + +/* + * A special GSource impl which allows us to wait on a certain + * condition to be satisfied. This is effectively a boolean test + * run on each iteration of the main loop. So whenever a file has + * new I/O, or a timer occurs, etc we'll do the check. This is + * pretty efficient compared to a normal GLib Idle func which has + * to busy wait on a timeout, since our condition is only checked + * when some other source's state changes + */ +typedef gboolean (*g_condition_wait_func)(gpointer); + +struct g_condition_wait_source +{ + GSource src; + struct coroutine *co; + g_condition_wait_func func; + gpointer data; +}; + +GIOCondition g_io_wait (GSocket *sock, GIOCondition cond); +gboolean g_condition_wait (g_condition_wait_func func, gpointer data); +void g_io_wakeup (struct wait_queue *wait); +GIOCondition g_io_wait_interruptable(struct wait_queue *wait, GSocket *sock, GIOCondition cond); + +G_END_DECLS + +#endif /* __GIO_COROUTINE_H__ */ diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h index 09a6138..498b677 100644 --- a/gtk/spice-channel-priv.h +++ b/gtk/spice-channel-priv.h @@ -19,6 +19,7 @@ #define __SPICE_CLIENT_CHANNEL_PRIV_H__ #include <openssl/ssl.h> +#include "coroutine.h" /* common/ */ #include "marshallers.h" @@ -57,6 +58,9 @@ enum spice_channel_state { struct spice_channel { SpiceSession *session; + struct coroutine coroutine; + guint open_id; + char name[16]; enum spice_channel_state state; int socket; diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c index e69937e..5fa25a4 100644 --- a/gtk/spice-channel.c +++ b/gtk/spice-channel.c @@ -30,6 +30,8 @@ #include <sys/socket.h> +#include "gio-coroutine.h" + static void spice_channel_send_msg(SpiceChannel *channel, spice_msg_out *out); static void spice_channel_send_link(SpiceChannel *channel); @@ -937,10 +939,31 @@ static int tls_verify(int preverify_ok, X509_STORE_CTX *ctx) return preverify_ok; } +static void *spice_channel_coroutine(void *data) +{ + spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(data); + GSocket *socket; + /* int ret; */ + /* struct signal_data s; */ + + SPICE_DEBUG("Started background coroutine"); + socket = spice_session_channel_open_host(c->session, c->tls); + if (socket == NULL) /* FIXME: reconnect with tls? */ + goto cleanup; + + cleanup: + SPICE_DEBUG("Doing final channel cleanup"); + /* vnc_connection_close(conn); */ + /* vnc_connection_emit_main_context(conn, VNC_DISCONNECTED, &s); */ + /* g_idle_add(spice_connection_delayed_unref, conn); */ + return NULL; +} + static gboolean channel_connect(SpiceChannel *channel) { spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel); int rc, err; + struct coroutine *co; g_return_val_if_fail(c != NULL, FALSE); @@ -954,6 +977,18 @@ static gboolean channel_connect(SpiceChannel *channel) } reconnect: + SPICE_DEBUG("Open coroutine starting"); + c->open_id = 0; + + co = &c->coroutine; + + co->stack_size = 16 << 20; + co->entry = spice_channel_coroutine; + co->release = NULL; + + coroutine_init(co); + coroutine_yieldto(co, channel); + if (spice_session_get_client_provided_socket(c->session)) { if (c->socket == -1) { g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls); diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h index 86d0bf4..e43fb6d 100644 --- a/gtk/spice-session-priv.h +++ b/gtk/spice-session-priv.h @@ -18,12 +18,16 @@ #ifndef __SPICE_CLIENT_SESSION_PRIV_H__ #define __SPICE_CLIENT_SESSION_PRIV_H__ +#include <glib.h> +#include <gio/gio.h> + G_BEGIN_DECLS void spice_session_set_connection_id(SpiceSession *session, int id); int spice_session_get_connection_id(SpiceSession *session); gboolean spice_session_get_client_provided_socket(SpiceSession *session); +GSocket* spice_session_channel_open_host(SpiceSession *session, gboolean use_tls); int spice_session_channel_connect(SpiceSession *session, bool use_tls); void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel); void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel); diff --git a/gtk/spice-session.c b/gtk/spice-session.c index 71445a7..a60921a 100644 --- a/gtk/spice-session.c +++ b/gtk/spice-session.c @@ -15,6 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, see <http://www.gnu.org/licenses/>. */ +#include <gio/gio.h> #include "spice-client.h" #include "spice-common.h" @@ -24,6 +25,8 @@ /* spice/common */ #include "ring.h" +#include "gio-coroutine.h" + struct channel { SpiceChannel *channel; RingItem link; @@ -492,6 +495,76 @@ int spice_session_channel_connect(SpiceSession *session, bool use_tls) return tcp_connect(&s->ai, NULL, NULL, s->host, port); } +static GSocket *channel_connect_socket(GSocketAddress *sockaddr, + GError **error) +{ + GSocket *sock = g_socket_new(g_socket_address_get_family(sockaddr), + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + error); + + if (!sock) + return NULL; + + g_socket_set_blocking(sock, FALSE); + if (!g_socket_connect(sock, sockaddr, NULL, error)) { + if (*error && (*error)->code == G_IO_ERROR_PENDING) { + g_error_free(*error); + *error = NULL; + SPICE_DEBUG("Socket pending"); + g_io_wait(sock, G_IO_OUT|G_IO_ERR|G_IO_HUP); + + if (!g_socket_check_connect_result(sock, error)) { + SPICE_DEBUG("Failed to connect %s", (*error)->message); + g_object_unref(sock); + return NULL; + } + } else { + SPICE_DEBUG("Socket error: %s", *error ? (*error)->message : "unknown"); + g_object_unref(sock); + return NULL; + } + } + + SPICE_DEBUG("Finally connected"); + + return sock; +} + +GSocket* spice_session_channel_open_host(SpiceSession *session, gboolean use_tls) +{ + spice_session *s = SPICE_SESSION_GET_PRIVATE(session); + GSocketConnectable *addr; + GSocketAddressEnumerator *enumerator; + GSocketAddress *sockaddr; + GError *conn_error = NULL; + GSocket *sock = NULL; + int port = atoi(use_tls ? s->tls_port : s->port); + + SPICE_DEBUG("Resolving host %s %d", s->host, port); + + addr = g_network_address_new(s->host, port); + + enumerator = g_socket_connectable_enumerate (addr); + g_object_unref (addr); + + /* Try each sockaddr until we succeed. Record the first + * connection error, but not any further ones (since they'll probably + * be basically the same as the first). + */ + while (!sock && + (sockaddr = g_socket_address_enumerator_next(enumerator, NULL, &conn_error))) { + SPICE_DEBUG("Trying one socket"); + g_clear_error(&conn_error); + sock = channel_connect_socket(sockaddr, &conn_error); + g_object_unref(sockaddr); + } + g_object_unref(enumerator); + g_clear_error(&conn_error); + return sock; +} + + void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel) { spice_session *s = SPICE_SESSION_GET_PRIVATE(session); |