From 75bbe26909d270749656da60949bbb2a8524fefd Mon Sep 17 00:00:00 2001 From: Debarshi Ray Date: Sat, 14 May 2011 15:41:02 +0300 Subject: [PATCH 2/2] IdleServerConnection: Replace low level network code with GIO Fixes: https://bugs.freedesktop.org/37145 --- src/idle-server-connection.c | 479 ++++++++++-------------------------------- 1 files changed, 110 insertions(+), 369 deletions(-) diff --git a/src/idle-server-connection.c b/src/idle-server-connection.c index 0d48188..76155e2 100644 --- a/src/idle-server-connection.c +++ b/src/idle-server-connection.c @@ -3,6 +3,7 @@ * * Copyright (C) 2006-2007 Collabora Limited * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2011 Debarshi Ray * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License @@ -20,28 +21,19 @@ #include "idle-server-connection.h" -#include - -#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #define IDLE_DEBUG_FLAG IDLE_DEBUG_NETWORK #include "idle-connection.h" -#include "idle-dns-resolver.h" #include "idle-debug.h" #include "idle-server-connection-iface.h" static void _server_connection_iface_init(gpointer, gpointer); typedef struct _IdleServerConnectionPrivate IdleServerConnectionPrivate; +typedef struct _SocketSourceData SocketSourceData; #define IDLE_SERVER_CONNECTION_GET_PRIVATE(conn) (G_TYPE_INSTANCE_GET_PRIVATE((conn), IDLE_TYPE_SERVER_CONNECTION, IdleServerConnectionPrivate)) @@ -53,69 +45,48 @@ enum { PROP_PORT }; -struct _AsyncConnectData { - guint watch_id; - - GIOChannel *io_chan; - - IdleDNSResult *res; - IdleDNSResult *cur; -}; - -#define async_connect_data_new() \ - (g_slice_new(struct _AsyncConnectData)) -#define async_connect_data_new0() \ - (g_slice_new0(struct _AsyncConnectData)) - -static void async_connect_data_destroy(struct _AsyncConnectData *data) { - if (data->watch_id) { - g_source_remove(data->watch_id); - data->watch_id = 0; - } - - if (data->io_chan) { - g_io_channel_shutdown(data->io_chan, FALSE, NULL); - g_io_channel_unref(data->io_chan); - data->io_chan = NULL; - } - - if (data->res) { - idle_dns_result_destroy(data->res); - data->res = NULL; - } - - g_slice_free(struct _AsyncConnectData, data); -} - struct _IdleServerConnectionPrivate { gchar *host; guint16 port; - GIOChannel *io_chan; - IdleServerConnectionState state; - - guint read_watch_id; + GSocketClient *socket_client; + GSocketConnection *socket_connection; + GCancellable *cancellable; + IdleServerConnectionState state; gboolean dispose_has_run; +}; - IdleDNSResolver *resolver; - struct _AsyncConnectData *connect_data; +struct _SocketSourceData { + IdleServerConnection *conn; + GCancellable *cancellable; }; static GObject *idle_server_connection_constructor(GType type, guint n_props, GObjectConstructParam *props); +static gboolean _object_destroy_idle(gpointer data) { + if (data == NULL) + return FALSE; + + g_object_unref(G_OBJECT(data)); + return FALSE; +} + +static void _socket_source_data_destroy(gpointer data) { + g_slice_free(SocketSourceData, data); +} + static void idle_server_connection_init(IdleServerConnection *conn) { IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); priv->host = NULL; priv->port = 0; - priv->io_chan = NULL; - priv->state = SERVER_CONNECTION_STATE_NOT_CONNECTED; - priv->read_watch_id = 0; - priv->connect_data = NULL; - priv->resolver = idle_dns_resolver_new(); + priv->socket_client = g_socket_client_new(); + priv->socket_connection = NULL; + priv->cancellable = g_cancellable_new(); + priv->state = SERVER_CONNECTION_STATE_NOT_CONNECTED; priv->dispose_has_run = FALSE; } @@ -147,14 +118,15 @@ static void idle_server_connection_dispose(GObject *obj) { } } - if (priv->connect_data != NULL) { - async_connect_data_destroy(priv->connect_data); - priv->connect_data = NULL; + if (priv->cancellable != NULL) { + g_cancellable_cancel(priv->cancellable); + g_idle_add(_object_destroy_idle, priv->cancellable); + priv->cancellable = NULL; } - if (priv->resolver != NULL) { - idle_dns_resolver_destroy(priv->resolver); - priv->resolver = NULL; + if (priv->socket_client != NULL) { + g_object_unref(priv->socket_client); + priv->socket_client = NULL; } } @@ -266,39 +238,73 @@ static gboolean io_err_cleanup_func(gpointer data) { return FALSE; } -static gboolean io_func(GIOChannel *src, GIOCondition cond, gpointer data) { - IdleServerConnection *conn = IDLE_SERVER_CONNECTION(data); +static gboolean _socket_received(GSocket *socket, GIOCondition condition, gpointer user_data) { + SocketSourceData *socket_source_data = (SocketSourceData *) user_data; + IdleServerConnection *conn; + IdleServerConnectionPrivate *priv; + GInputStream *input_stream; gchar buf[IRC_MSG_MAXLEN + 3]; - GIOStatus status; - gsize len; GError *error = NULL; - if (cond & (G_IO_ERR | G_IO_HUP)) { + if (g_cancellable_is_cancelled(socket_source_data->cancellable)) + return FALSE; + + conn = socket_source_data->conn; + priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (condition & (G_IO_ERR | G_IO_HUP)) { IDLE_DEBUG("got G_IO_ERR | G_IO_HUP"); - g_idle_add(io_err_cleanup_func, data); + g_idle_add(io_err_cleanup_func, conn); return FALSE; } - memset(buf, 0, IRC_MSG_MAXLEN + 3); - status = g_io_channel_read_chars(src, buf, IRC_MSG_MAXLEN + 2, &len, &error); + input_stream = g_io_stream_get_input_stream(G_IO_STREAM(priv->socket_connection)); - if ((status != G_IO_STATUS_NORMAL) && (status != G_IO_STATUS_AGAIN)) { - IDLE_DEBUG("status: %u, error: %s", status, (error != NULL) ? (error->message != NULL) ? error->message : "(null)" : "(null)"); - - if (error) { + if (condition & (G_IO_IN | G_IO_PRI)) { + memset(buf, 0, IRC_MSG_MAXLEN + 3); + if (g_input_stream_read(input_stream, buf, sizeof(buf) - 1, NULL, &error) == -1) { + IDLE_DEBUG("error: %s", error->message); g_error_free(error); + g_idle_add(io_err_cleanup_func, conn); + return FALSE; } - g_idle_add(io_err_cleanup_func, data); - return FALSE; + g_signal_emit_by_name(conn, "received", buf); } - g_signal_emit_by_name(conn, "received", buf); - return TRUE; } -static gboolean do_connect(IdleServerConnection *conn); +static void _connect_to_host_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) { + GSocketClient *socket_client = G_SOCKET_CLIENT(source_object); + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(user_data); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + GSocket *socket; + GSource *source; + SocketSourceData *socket_source_data; + GError *error = NULL; + + priv->socket_connection = g_socket_client_connect_to_host_finish(socket_client, res, &error); + if (priv->socket_connection == NULL) { + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return; + } + + g_tcp_connection_set_graceful_disconnect(G_TCP_CONNECTION(priv->socket_connection), TRUE); + + socket = g_socket_connection_get_socket(priv->socket_connection); + source = g_socket_create_source(socket, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, priv->cancellable); + + socket_source_data = g_slice_new0(SocketSourceData); + socket_source_data->conn = conn; + socket_source_data->cancellable = priv->cancellable; + + g_source_set_callback(source, (GSourceFunc) _socket_received, socket_source_data, _socket_source_data_destroy); + g_source_attach(source, g_main_context_default()); + g_source_unref(source); + + change_state(conn, SERVER_CONNECTION_STATE_CONNECTED, SERVER_CONNECTION_STATE_REASON_REQUESTED); +} static gboolean iface_connect_impl(IdleServerConnectionIface *iface, GError **error) { IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); @@ -322,237 +328,14 @@ static gboolean iface_connect_impl(IdleServerConnectionIface *iface, GError **er return FALSE; } - if (!do_connect(conn)) { - IDLE_DEBUG("do_connect failed"); - g_set_error(error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "failed to connect"); - return FALSE; - } + g_cancellable_reset(priv->cancellable); + g_socket_client_connect_to_host_async(priv->socket_client, priv->host, priv->port, priv->cancellable, _connect_to_host_ready, conn); change_state(conn, SERVER_CONNECTION_STATE_CONNECTING, SERVER_CONNECTION_STATE_REASON_REQUESTED); return TRUE; } -static gboolean connect_io_func(GIOChannel *src, GIOCondition cond, gpointer data) { - IdleServerConnection *conn = IDLE_SERVER_CONNECTION(data); - IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); - int rc; - int fd = -1; - int optval; - socklen_t optlen = sizeof(optval); - GIOChannel *io_chan; - struct _AsyncConnectData *connect_data = priv->connect_data; - - IdleDNSResult *cur = connect_data->cur; - IdleDNSResult *next = cur->ai_next; - - fd = g_io_channel_unix_get_fd(connect_data->io_chan); - g_assert(getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == 0); - - if (optval == 0) { - int opt; - - IDLE_DEBUG("connected!"); - - change_state(conn, SERVER_CONNECTION_STATE_CONNECTED, SERVER_CONNECTION_STATE_REASON_REQUESTED); - - g_assert(priv->io_chan == NULL); - g_assert(priv->read_watch_id == 0); - - priv->read_watch_id = g_io_add_watch(connect_data->io_chan, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, io_func, data); - priv->io_chan = connect_data->io_chan; - - connect_data->io_chan = NULL; - - async_connect_data_destroy(priv->connect_data); - priv->connect_data = NULL; - - opt = 1; - - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) { - g_warning("%s: failed to set TCP_NODELAY: %s", G_STRFUNC, g_strerror(errno)); - } - - opt = 1; - - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)) < 0) { - g_warning("%s: failed to set SO_KEEPALIVE: %s", G_STRFUNC, g_strerror(errno)); - } - - return FALSE; - } - - IDLE_DEBUG("connection failed"); - - if (next == NULL) { - IDLE_DEBUG("and this was the last address we can try, tough luck."); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - - return FALSE; - } - - if ((next->ai_family == cur->ai_family) && - (next->ai_socktype == cur->ai_socktype) && - (next->ai_protocol == cur->ai_protocol)) { - IDLE_DEBUG("re-using existing socket for trying again"); - - errno = 0; - rc = connect(fd, next->ai_addr, next->ai_addrlen); - - for (int i = 0; i < 5 && errno == ECONNABORTED; i++) { - IDLE_DEBUG("got ECONNABORTED for %ith time", i + 1); - errno = 0; - connect(fd, next->ai_addr, next->ai_addrlen); - } - - if ((errno != EINPROGRESS) && (rc == -1)) { - IDLE_DEBUG("connect() failed: %s", g_strerror(errno)); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return FALSE; - } - - connect_data->cur = next; - - return TRUE; - } - - IDLE_DEBUG("we'll have to create a new socket since the address family/socket type/protocol is different"); - - for (cur = next; cur != NULL; cur = cur->ai_next) { - fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); - - if (fd == -1) { - if ((errno == EINVAL) || (errno == EAFNOSUPPORT)) { - continue; - } - - IDLE_DEBUG("socket() failed: %s", g_strerror(errno)); - return FALSE; - } - else { - break; - } - } - connect_data->cur = cur; - - if (fd == -1) { - IDLE_DEBUG("could not socket(): %s", g_strerror(errno)); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return FALSE; - } - - io_chan = g_io_channel_unix_new(fd); - g_io_channel_set_encoding(io_chan, NULL, NULL); - g_io_channel_set_buffered(io_chan, FALSE); - - rc = fcntl(fd, F_SETFL, O_NONBLOCK); - - if (rc != 0) { - IDLE_DEBUG("failed to set socket to non-blocking mode: %s", g_strerror(errno)); - g_io_channel_shutdown(io_chan, FALSE, NULL); - g_io_channel_unref(io_chan); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return FALSE; - } - - errno = 0; - rc = connect(fd, cur->ai_addr, cur->ai_addrlen); - - if ((errno != EINPROGRESS) && (rc == -1)) { - IDLE_DEBUG("initial connect() failed: %s", g_strerror(errno)); - g_io_channel_shutdown(io_chan, FALSE, NULL); - g_io_channel_unref(io_chan); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return FALSE; - } - - g_io_channel_shutdown(connect_data->io_chan, FALSE, NULL); - g_io_channel_unref(connect_data->io_chan); - connect_data->io_chan = io_chan; - - connect_data->watch_id = g_io_add_watch(io_chan, G_IO_OUT | G_IO_ERR, connect_io_func, conn); - - return FALSE; -} - -static void dns_result_callback(guint unused, IdleDNSResult *results, gpointer user_data) { - IdleServerConnection *conn = IDLE_SERVER_CONNECTION(user_data); - IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); - IdleDNSResult *cur; - int fd = -1; - int rc = -1; - GIOChannel *io_chan; - - if (!results) { - IDLE_DEBUG("no DNS results received"); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return; - } - - for (cur = results; cur != NULL; cur = cur->ai_next) { - fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); - - if (fd == -1) { - if ((errno == EINVAL) || (errno = EAFNOSUPPORT)) - continue; - - IDLE_DEBUG("socket() failed: %s", g_strerror(errno)); - return; - } else { - break; - } - } - - if (fd == -1) { - IDLE_DEBUG("failed: %s", g_strerror(errno)); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return; - } - - rc = fcntl(fd, F_SETFL, O_NONBLOCK); - - if (rc != 0) { - IDLE_DEBUG("failed to set socket to non-blocking mode: %s", g_strerror(errno)); - close(fd); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return; - } - - rc = connect(fd, cur->ai_addr, cur->ai_addrlen); - - g_assert(rc == -1); - - if (errno != EINPROGRESS) { - IDLE_DEBUG("initial connect() failed: %s", g_strerror(errno)); - close(fd); - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); - return; - } - - if (priv->connect_data != NULL) { - async_connect_data_destroy(priv->connect_data); - } - - priv->connect_data = async_connect_data_new(); - - io_chan = g_io_channel_unix_new(fd); - g_io_channel_set_encoding(io_chan, NULL, NULL); - g_io_channel_set_buffered(io_chan, FALSE); - - priv->connect_data->io_chan = io_chan; - priv->connect_data->res = results; - priv->connect_data->cur = cur; - priv->connect_data->watch_id = g_io_add_watch(io_chan, G_IO_OUT | G_IO_ERR, connect_io_func, conn); -} - -static gboolean do_connect(IdleServerConnection *conn) { - IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); - - idle_dns_resolver_query(priv->resolver, priv->host, priv->port, dns_result_callback, conn); - - return TRUE; -} - static gboolean iface_disconnect_impl(IdleServerConnectionIface *iface, GError **error) { return iface_disconnect_impl_full(iface, error, SERVER_CONNECTION_STATE_REASON_REQUESTED); } @@ -561,7 +344,6 @@ static gboolean iface_disconnect_impl_full(IdleServerConnectionIface *iface, GEr IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); GError *io_error = NULL; - GIOStatus status; g_assert(priv != NULL); @@ -571,90 +353,49 @@ static gboolean iface_disconnect_impl_full(IdleServerConnectionIface *iface, GEr return FALSE; } - if (priv->read_watch_id != 0) { - g_source_remove(priv->read_watch_id); - priv->read_watch_id = 0; - } + g_cancellable_cancel(priv->cancellable); - if (priv->io_chan != NULL) { - status = g_io_channel_shutdown(priv->io_chan, TRUE, &io_error); + g_io_stream_close(G_IO_STREAM(priv->socket_connection), NULL, &io_error); + g_object_unref(priv->socket_connection); + priv->socket_connection = NULL; - if (status != G_IO_STATUS_NORMAL && io_error) { - IDLE_DEBUG("g_io_channel_shutdown failed: %s", io_error->message); - g_error_free(io_error); - } + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, reason); - g_io_channel_unref(priv->io_chan); - priv->io_chan = NULL; + if (io_error != NULL) { + IDLE_DEBUG("error: %s", io_error->message); + g_error_free(io_error); + g_set_error(error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "possible loss of data"); + return FALSE; } - change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, reason); - return TRUE; } static gboolean iface_send_impl(IdleServerConnectionIface *iface, const gchar *cmd, GError **error) { IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); - GIOStatus status; - gsize written; - GError *local_error = NULL; + GOutputStream *output_stream; + gsize nwrite; + GError *io_error = NULL; if (priv->state != SERVER_CONNECTION_STATE_CONNECTED) { IDLE_DEBUG("connection was not open!"); - g_set_error(error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "connection was not open!"); - return FALSE; } - status = g_io_channel_write_chars(priv->io_chan, - cmd, - -1, - &written, - &local_error); + output_stream = g_io_stream_get_output_stream(G_IO_STREAM(priv->socket_connection)); - if (local_error) { - IDLE_DEBUG("error: %s", local_error->message); - g_error_free(local_error); + if (!g_output_stream_write_all(output_stream, cmd, strlen(cmd), &nwrite, NULL, &io_error)) { + IDLE_DEBUG("error: %s", io_error->message); + g_error_free(io_error); + iface_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(conn), NULL, SERVER_CONNECTION_STATE_REASON_ERROR); + g_set_error(error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "failed to send"); + return FALSE; } - switch (status) { - case G_IO_STATUS_ERROR: - IDLE_DEBUG("got G_IO_STATUS_ERROR"); - - iface_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(conn), NULL, SERVER_CONNECTION_STATE_REASON_ERROR); - - g_set_error(error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "got G_IO_STATUS_ERROR"); - - return FALSE; - - case G_IO_STATUS_NORMAL: - IDLE_DEBUG("sent \"%s\" to IOChannel %p", cmd, priv->io_chan); - - return TRUE; - - case G_IO_STATUS_EOF: - IDLE_DEBUG("got G_IO_STATUS_EOF"); - - if (iface_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(conn), &local_error, SERVER_CONNECTION_STATE_REASON_ERROR)) - g_error_free(local_error); - - g_set_error(error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "got G_IO_STATUS_EOF"); - - return FALSE; - - case G_IO_STATUS_AGAIN: - IDLE_DEBUG("got G_IO_STATUS_AGAIN"); - - g_set_error(error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "got G_IO_STATUS_AGAIN"); - - return FALSE; - - default: - g_assert_not_reached(); - return FALSE; - } + IDLE_DEBUG("sent \"%s\" to OutputStream %p", cmd, output_stream); + return TRUE; } static IdleServerConnectionState iface_get_state_impl(IdleServerConnectionIface *iface) { -- 1.7.4.4