mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-27 22:46:15 +01:00
Merge branch 'wip/tingping/socket-client-data-races-fix' into 'master'
gsocketclient: Refactor g_socket_client_connect_async() Closes #1995, #1872, #1902, #1989, and #1871 See merge request GNOME/glib!1339
This commit is contained in:
commit
dff212effc
@ -1337,13 +1337,15 @@ typedef struct
|
|||||||
|
|
||||||
GSocketConnectable *connectable;
|
GSocketConnectable *connectable;
|
||||||
GSocketAddressEnumerator *enumerator;
|
GSocketAddressEnumerator *enumerator;
|
||||||
GProxyAddress *proxy_addr;
|
GCancellable *enumeration_cancellable;
|
||||||
GSocket *socket;
|
|
||||||
GIOStream *connection;
|
|
||||||
|
|
||||||
GSList *connection_attempts;
|
GSList *connection_attempts;
|
||||||
|
GSList *successful_connections;
|
||||||
GError *last_error;
|
GError *last_error;
|
||||||
|
|
||||||
|
gboolean enumerated_at_least_once;
|
||||||
|
gboolean enumeration_completed;
|
||||||
|
gboolean connection_in_progress;
|
||||||
gboolean completed;
|
gboolean completed;
|
||||||
} GSocketClientAsyncConnectData;
|
} GSocketClientAsyncConnectData;
|
||||||
|
|
||||||
@ -1355,10 +1357,9 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data)
|
|||||||
data->task = NULL;
|
data->task = NULL;
|
||||||
g_clear_object (&data->connectable);
|
g_clear_object (&data->connectable);
|
||||||
g_clear_object (&data->enumerator);
|
g_clear_object (&data->enumerator);
|
||||||
g_clear_object (&data->proxy_addr);
|
g_clear_object (&data->enumeration_cancellable);
|
||||||
g_clear_object (&data->socket);
|
|
||||||
g_clear_object (&data->connection);
|
|
||||||
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
|
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
|
||||||
|
g_slist_free_full (data->successful_connections, connection_attempt_unref);
|
||||||
|
|
||||||
g_clear_error (&data->last_error);
|
g_clear_error (&data->last_error);
|
||||||
|
|
||||||
@ -1370,6 +1371,7 @@ typedef struct
|
|||||||
GSocketAddress *address;
|
GSocketAddress *address;
|
||||||
GSocket *socket;
|
GSocket *socket;
|
||||||
GIOStream *connection;
|
GIOStream *connection;
|
||||||
|
GProxyAddress *proxy_addr;
|
||||||
GSocketClientAsyncConnectData *data; /* unowned */
|
GSocketClientAsyncConnectData *data; /* unowned */
|
||||||
GSource *timeout_source;
|
GSource *timeout_source;
|
||||||
GCancellable *cancellable;
|
GCancellable *cancellable;
|
||||||
@ -1401,6 +1403,7 @@ connection_attempt_unref (gpointer pointer)
|
|||||||
g_clear_object (&attempt->socket);
|
g_clear_object (&attempt->socket);
|
||||||
g_clear_object (&attempt->connection);
|
g_clear_object (&attempt->connection);
|
||||||
g_clear_object (&attempt->cancellable);
|
g_clear_object (&attempt->cancellable);
|
||||||
|
g_clear_object (&attempt->proxy_addr);
|
||||||
if (attempt->timeout_source)
|
if (attempt->timeout_source)
|
||||||
{
|
{
|
||||||
g_source_destroy (attempt->timeout_source);
|
g_source_destroy (attempt->timeout_source);
|
||||||
@ -1418,37 +1421,59 @@ connection_attempt_remove (ConnectionAttempt *attempt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data)
|
cancel_all_attempts (GSocketClientAsyncConnectData *data)
|
||||||
{
|
{
|
||||||
g_assert (data->connection);
|
GSList *l;
|
||||||
|
|
||||||
if (!G_IS_SOCKET_CONNECTION (data->connection))
|
for (l = data->connection_attempts; l; l = g_slist_next (l))
|
||||||
|
{
|
||||||
|
ConnectionAttempt *attempt_entry = l->data;
|
||||||
|
g_cancellable_cancel (attempt_entry->cancellable);
|
||||||
|
connection_attempt_unref (attempt_entry);
|
||||||
|
}
|
||||||
|
g_slist_free (data->connection_attempts);
|
||||||
|
data->connection_attempts = NULL;
|
||||||
|
|
||||||
|
g_slist_free_full (data->successful_connections, connection_attempt_unref);
|
||||||
|
data->successful_connections = NULL;
|
||||||
|
|
||||||
|
g_cancellable_cancel (data->enumeration_cancellable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
g_socket_client_async_connect_complete (ConnectionAttempt *attempt)
|
||||||
|
{
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
|
GError *error = NULL;
|
||||||
|
g_assert (attempt->connection);
|
||||||
|
g_assert (!data->completed);
|
||||||
|
|
||||||
|
if (!G_IS_SOCKET_CONNECTION (attempt->connection))
|
||||||
{
|
{
|
||||||
GSocketConnection *wrapper_connection;
|
GSocketConnection *wrapper_connection;
|
||||||
|
|
||||||
wrapper_connection = g_tcp_wrapper_connection_new (data->connection, data->socket);
|
wrapper_connection = g_tcp_wrapper_connection_new (attempt->connection, attempt->socket);
|
||||||
g_object_unref (data->connection);
|
g_object_unref (attempt->connection);
|
||||||
data->connection = (GIOStream *)wrapper_connection;
|
attempt->connection = (GIOStream *)wrapper_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data->completed)
|
data->completed = TRUE;
|
||||||
{
|
cancel_all_attempts (data);
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (data->task), &error))
|
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (data->task), &error))
|
||||||
{
|
{
|
||||||
|
g_debug ("GSocketClient: Connection cancelled!");
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
||||||
g_task_return_error (data->task, g_steal_pointer (&error));
|
g_task_return_error (data->task, g_steal_pointer (&error));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, data->connection);
|
g_debug ("GSocketClient: Connection successful!");
|
||||||
g_task_return_pointer (data->task, g_steal_pointer (&data->connection), g_object_unref);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, attempt->connection);
|
||||||
}
|
g_task_return_pointer (data->task, g_steal_pointer (&attempt->connection), g_object_unref);
|
||||||
|
|
||||||
data->completed = TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connection_attempt_unref (attempt);
|
||||||
g_object_unref (data->task);
|
g_object_unref (data->task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1470,59 +1495,63 @@ static void
|
|||||||
enumerator_next_async (GSocketClientAsyncConnectData *data,
|
enumerator_next_async (GSocketClientAsyncConnectData *data,
|
||||||
gboolean add_task_ref)
|
gboolean add_task_ref)
|
||||||
{
|
{
|
||||||
/* We need to cleanup the state */
|
|
||||||
g_clear_object (&data->socket);
|
|
||||||
g_clear_object (&data->proxy_addr);
|
|
||||||
g_clear_object (&data->connection);
|
|
||||||
|
|
||||||
/* Each enumeration takes a ref. This arg just avoids repeated unrefs when
|
/* Each enumeration takes a ref. This arg just avoids repeated unrefs when
|
||||||
an enumeration starts another enumeration */
|
an enumeration starts another enumeration */
|
||||||
if (add_task_ref)
|
if (add_task_ref)
|
||||||
g_object_ref (data->task);
|
g_object_ref (data->task);
|
||||||
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL);
|
||||||
|
g_debug ("GSocketClient: Starting new address enumeration");
|
||||||
g_socket_address_enumerator_next_async (data->enumerator,
|
g_socket_address_enumerator_next_async (data->enumerator,
|
||||||
g_task_get_cancellable (data->task),
|
data->enumeration_cancellable,
|
||||||
g_socket_client_enumerator_callback,
|
g_socket_client_enumerator_callback,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void try_next_connection_or_finish (GSocketClientAsyncConnectData *, gboolean);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_tls_handshake_callback (GObject *object,
|
g_socket_client_tls_handshake_callback (GObject *object,
|
||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
GSocketClientAsyncConnectData *data = user_data;
|
ConnectionAttempt *attempt = user_data;
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
|
|
||||||
if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (object),
|
if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (object),
|
||||||
result,
|
result,
|
||||||
&data->last_error))
|
&data->last_error))
|
||||||
{
|
{
|
||||||
g_object_unref (data->connection);
|
g_object_unref (attempt->connection);
|
||||||
data->connection = G_IO_STREAM (object);
|
attempt->connection = G_IO_STREAM (object);
|
||||||
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKED, data->connectable, data->connection);
|
g_debug ("GSocketClient: TLS handshake succeeded");
|
||||||
g_socket_client_async_connect_complete (data);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKED, data->connectable, attempt->connection);
|
||||||
|
g_socket_client_async_connect_complete (attempt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g_object_unref (object);
|
g_object_unref (object);
|
||||||
enumerator_next_async (data, FALSE);
|
connection_attempt_unref (attempt);
|
||||||
|
g_debug ("GSocketClient: TLS handshake failed: %s", data->last_error->message);
|
||||||
|
try_next_connection_or_finish (data, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data)
|
g_socket_client_tls_handshake (ConnectionAttempt *attempt)
|
||||||
{
|
{
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
GIOStream *tlsconn;
|
GIOStream *tlsconn;
|
||||||
|
|
||||||
if (!data->client->priv->tls)
|
if (!data->client->priv->tls)
|
||||||
{
|
{
|
||||||
g_socket_client_async_connect_complete (data);
|
g_socket_client_async_connect_complete (attempt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsconn = g_tls_client_connection_new (data->connection,
|
g_debug ("GSocketClient: Starting TLS handshake");
|
||||||
|
tlsconn = g_tls_client_connection_new (attempt->connection,
|
||||||
data->connectable,
|
data->connectable,
|
||||||
&data->last_error);
|
&data->last_error);
|
||||||
if (tlsconn)
|
if (tlsconn)
|
||||||
@ -1534,11 +1563,12 @@ g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data)
|
|||||||
G_PRIORITY_DEFAULT,
|
G_PRIORITY_DEFAULT,
|
||||||
g_task_get_cancellable (data->task),
|
g_task_get_cancellable (data->task),
|
||||||
g_socket_client_tls_handshake_callback,
|
g_socket_client_tls_handshake_callback,
|
||||||
data);
|
attempt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
enumerator_next_async (data, FALSE);
|
connection_attempt_unref (attempt);
|
||||||
|
try_next_connection_or_finish (data, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1547,23 +1577,38 @@ g_socket_client_proxy_connect_callback (GObject *object,
|
|||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
GSocketClientAsyncConnectData *data = user_data;
|
ConnectionAttempt *attempt = user_data;
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
|
|
||||||
g_object_unref (data->connection);
|
g_object_unref (attempt->connection);
|
||||||
data->connection = g_proxy_connect_finish (G_PROXY (object),
|
attempt->connection = g_proxy_connect_finish (G_PROXY (object),
|
||||||
result,
|
result,
|
||||||
&data->last_error);
|
&data->last_error);
|
||||||
if (data->connection)
|
if (attempt->connection)
|
||||||
{
|
{
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, data->connection);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, attempt->connection);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
enumerator_next_async (data, FALSE);
|
connection_attempt_unref (attempt);
|
||||||
|
try_next_connection_or_finish (data, TRUE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_socket_client_tls_handshake (data);
|
g_socket_client_tls_handshake (attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
complete_connection_with_error (GSocketClientAsyncConnectData *data,
|
||||||
|
GError *error)
|
||||||
|
{
|
||||||
|
g_debug ("GSocketClient: Connection failed: %s", error->message);
|
||||||
|
g_assert (!data->completed);
|
||||||
|
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
||||||
|
data->completed = TRUE;
|
||||||
|
cancel_all_attempts (data);
|
||||||
|
g_task_return_error (data->task, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -1577,15 +1622,114 @@ task_completed_or_cancelled (GSocketClientAsyncConnectData *data)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
else if (g_cancellable_set_error_if_cancelled (cancellable, &error))
|
else if (g_cancellable_set_error_if_cancelled (cancellable, &error))
|
||||||
{
|
{
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
complete_connection_with_error (data, g_steal_pointer (&error));
|
||||||
g_task_return_error (task, g_steal_pointer (&error));
|
|
||||||
data->completed = TRUE;
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
try_next_successful_connection (GSocketClientAsyncConnectData *data)
|
||||||
|
{
|
||||||
|
ConnectionAttempt *attempt;
|
||||||
|
const gchar *protocol;
|
||||||
|
GProxy *proxy;
|
||||||
|
|
||||||
|
if (data->connection_in_progress)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_assert (data->successful_connections != NULL);
|
||||||
|
attempt = data->successful_connections->data;
|
||||||
|
g_assert (attempt != NULL);
|
||||||
|
data->successful_connections = g_slist_remove (data->successful_connections, attempt);
|
||||||
|
data->connection_in_progress = TRUE;
|
||||||
|
|
||||||
|
g_debug ("GSocketClient: Starting application layer connection");
|
||||||
|
|
||||||
|
if (!attempt->proxy_addr)
|
||||||
|
{
|
||||||
|
g_socket_client_tls_handshake (g_steal_pointer (&attempt));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol = g_proxy_address_get_protocol (attempt->proxy_addr);
|
||||||
|
|
||||||
|
/* The connection should not be anything other than TCP,
|
||||||
|
* but let's put a safety guard in case
|
||||||
|
*/
|
||||||
|
if (!G_IS_TCP_CONNECTION (attempt->connection))
|
||||||
|
{
|
||||||
|
g_critical ("Trying to proxy over non-TCP connection, this is "
|
||||||
|
"most likely a bug in GLib IO library.");
|
||||||
|
|
||||||
|
g_set_error_literal (&data->last_error,
|
||||||
|
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||||
|
_("Proxying over a non-TCP connection is not supported."));
|
||||||
|
}
|
||||||
|
else if (g_hash_table_contains (data->client->priv->app_proxies, protocol))
|
||||||
|
{
|
||||||
|
/* Simply complete the connection, we don't want to do TLS handshake
|
||||||
|
* as the application proxy handling may need proxy handshake first */
|
||||||
|
g_socket_client_async_connect_complete (g_steal_pointer (&attempt));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
else if ((proxy = g_proxy_get_default_for_protocol (protocol)))
|
||||||
|
{
|
||||||
|
GIOStream *connection = attempt->connection;
|
||||||
|
GProxyAddress *proxy_addr = attempt->proxy_addr;
|
||||||
|
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATING, data->connectable, attempt->connection);
|
||||||
|
g_debug ("GSocketClient: Starting proxy connection");
|
||||||
|
g_proxy_connect_async (proxy,
|
||||||
|
connection,
|
||||||
|
proxy_addr,
|
||||||
|
g_task_get_cancellable (data->task),
|
||||||
|
g_socket_client_proxy_connect_callback,
|
||||||
|
g_steal_pointer (&attempt));
|
||||||
|
g_object_unref (proxy);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_clear_error (&data->last_error);
|
||||||
|
|
||||||
|
g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||||
|
_("Proxy protocol “%s” is not supported."),
|
||||||
|
protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->connection_in_progress = FALSE;
|
||||||
|
g_clear_pointer (&attempt, connection_attempt_unref);
|
||||||
|
return FALSE; /* All non-return paths are failures */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
try_next_connection_or_finish (GSocketClientAsyncConnectData *data,
|
||||||
|
gboolean end_current_connection)
|
||||||
|
{
|
||||||
|
if (end_current_connection)
|
||||||
|
data->connection_in_progress = FALSE;
|
||||||
|
|
||||||
|
if (data->connection_in_progress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Keep trying successful connections until one works, each iteration pops one */
|
||||||
|
while (data->successful_connections)
|
||||||
|
{
|
||||||
|
if (try_next_successful_connection (data))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data->enumeration_completed)
|
||||||
|
{
|
||||||
|
enumerator_next_async (data, FALSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_connection_with_error (data, data->last_error);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_connected_callback (GObject *source,
|
g_socket_client_connected_callback (GObject *source,
|
||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
@ -1593,10 +1737,7 @@ g_socket_client_connected_callback (GObject *source,
|
|||||||
{
|
{
|
||||||
ConnectionAttempt *attempt = user_data;
|
ConnectionAttempt *attempt = user_data;
|
||||||
GSocketClientAsyncConnectData *data = attempt->data;
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
GSList *l;
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
GProxy *proxy;
|
|
||||||
const gchar *protocol;
|
|
||||||
|
|
||||||
if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable))
|
if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable))
|
||||||
{
|
{
|
||||||
@ -1618,11 +1759,12 @@ g_socket_client_connected_callback (GObject *source,
|
|||||||
{
|
{
|
||||||
clarify_connect_error (error, data->connectable, attempt->address);
|
clarify_connect_error (error, data->connectable, attempt->address);
|
||||||
set_last_error (data, error);
|
set_last_error (data, error);
|
||||||
|
g_debug ("GSocketClient: Connection attempt failed: %s", error->message);
|
||||||
connection_attempt_remove (attempt);
|
connection_attempt_remove (attempt);
|
||||||
enumerator_next_async (data, FALSE);
|
|
||||||
connection_attempt_unref (attempt);
|
connection_attempt_unref (attempt);
|
||||||
|
try_next_connection_or_finish (data, FALSE);
|
||||||
}
|
}
|
||||||
else
|
else /* Silently ignore cancelled attempts */
|
||||||
{
|
{
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
g_object_unref (data->task);
|
g_object_unref (data->task);
|
||||||
@ -1632,74 +1774,21 @@ g_socket_client_connected_callback (GObject *source,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data->socket = g_steal_pointer (&attempt->socket);
|
g_socket_connection_set_cached_remote_address ((GSocketConnection*)attempt->connection, NULL);
|
||||||
data->connection = g_steal_pointer (&attempt->connection);
|
g_debug ("GSocketClient: TCP connection successful");
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, attempt->connection);
|
||||||
for (l = data->connection_attempts; l; l = g_slist_next (l))
|
|
||||||
{
|
|
||||||
ConnectionAttempt *attempt_entry = l->data;
|
|
||||||
g_cancellable_cancel (attempt_entry->cancellable);
|
|
||||||
connection_attempt_unref (attempt_entry);
|
|
||||||
}
|
|
||||||
g_slist_free (data->connection_attempts);
|
|
||||||
data->connection_attempts = NULL;
|
|
||||||
connection_attempt_unref (attempt);
|
|
||||||
|
|
||||||
g_socket_connection_set_cached_remote_address ((GSocketConnection*)data->connection, NULL);
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, data->connection);
|
|
||||||
|
|
||||||
/* wrong, but backward compatible */
|
/* wrong, but backward compatible */
|
||||||
g_socket_set_blocking (data->socket, TRUE);
|
g_socket_set_blocking (attempt->socket, TRUE);
|
||||||
|
|
||||||
if (!data->proxy_addr)
|
/* This ends the parallel "happy eyeballs" portion of connecting.
|
||||||
{
|
Now that we have a successful tcp connection we will attempt to connect
|
||||||
g_socket_client_tls_handshake (data);
|
at the TLS/Proxy layer. If those layers fail we will move on to the next
|
||||||
return;
|
connection.
|
||||||
}
|
|
||||||
|
|
||||||
protocol = g_proxy_address_get_protocol (data->proxy_addr);
|
|
||||||
|
|
||||||
/* The connection should not be anything other than TCP,
|
|
||||||
* but let's put a safety guard in case
|
|
||||||
*/
|
*/
|
||||||
if (!G_IS_TCP_CONNECTION (data->connection))
|
connection_attempt_remove (attempt);
|
||||||
{
|
data->successful_connections = g_slist_append (data->successful_connections, g_steal_pointer (&attempt));
|
||||||
g_critical ("Trying to proxy over non-TCP connection, this is "
|
try_next_connection_or_finish (data, FALSE);
|
||||||
"most likely a bug in GLib IO library.");
|
|
||||||
|
|
||||||
g_set_error_literal (&data->last_error,
|
|
||||||
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
||||||
_("Proxying over a non-TCP connection is not supported."));
|
|
||||||
|
|
||||||
enumerator_next_async (data, FALSE);
|
|
||||||
}
|
|
||||||
else if (g_hash_table_contains (data->client->priv->app_proxies, protocol))
|
|
||||||
{
|
|
||||||
/* Simply complete the connection, we don't want to do TLS handshake
|
|
||||||
* as the application proxy handling may need proxy handshake first */
|
|
||||||
g_socket_client_async_connect_complete (data);
|
|
||||||
}
|
|
||||||
else if ((proxy = g_proxy_get_default_for_protocol (protocol)))
|
|
||||||
{
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATING, data->connectable, data->connection);
|
|
||||||
g_proxy_connect_async (proxy,
|
|
||||||
data->connection,
|
|
||||||
data->proxy_addr,
|
|
||||||
g_task_get_cancellable (data->task),
|
|
||||||
g_socket_client_proxy_connect_callback,
|
|
||||||
data);
|
|
||||||
g_object_unref (proxy);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_clear_error (&data->last_error);
|
|
||||||
|
|
||||||
g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
||||||
_("Proxy protocol “%s” is not supported."),
|
|
||||||
protocol);
|
|
||||||
|
|
||||||
enumerator_next_async (data, FALSE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -1707,7 +1796,11 @@ on_connection_attempt_timeout (gpointer data)
|
|||||||
{
|
{
|
||||||
ConnectionAttempt *attempt = data;
|
ConnectionAttempt *attempt = data;
|
||||||
|
|
||||||
|
if (!attempt->data->enumeration_completed)
|
||||||
|
{
|
||||||
|
g_debug ("GSocketClient: Timeout reached, trying another enumeration");
|
||||||
enumerator_next_async (attempt->data, TRUE);
|
enumerator_next_async (attempt->data, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
g_clear_pointer (&attempt->timeout_source, g_source_unref);
|
g_clear_pointer (&attempt->timeout_source, g_source_unref);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
@ -1717,9 +1810,9 @@ static void
|
|||||||
on_connection_cancelled (GCancellable *cancellable,
|
on_connection_cancelled (GCancellable *cancellable,
|
||||||
gpointer data)
|
gpointer data)
|
||||||
{
|
{
|
||||||
GCancellable *attempt_cancellable = data;
|
GCancellable *linked_cancellable = G_CANCELLABLE (data);
|
||||||
|
|
||||||
g_cancellable_cancel (attempt_cancellable);
|
g_cancellable_cancel (linked_cancellable);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1743,39 +1836,49 @@ g_socket_client_enumerator_callback (GObject *object,
|
|||||||
result, &error);
|
result, &error);
|
||||||
if (address == NULL)
|
if (address == NULL)
|
||||||
{
|
{
|
||||||
if (data->connection_attempts)
|
if (G_UNLIKELY (data->enumeration_completed))
|
||||||
{
|
|
||||||
g_object_unref (data->task);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
data->enumeration_completed = TRUE;
|
||||||
data->completed = TRUE;
|
g_debug ("GSocketClient: Address enumeration completed (out of addresses)");
|
||||||
if (!error)
|
|
||||||
|
/* As per API docs: We only care about error if its the first call,
|
||||||
|
after that the enumerator is done.
|
||||||
|
|
||||||
|
Note that we don't care about cancellation errors because
|
||||||
|
task_completed_or_cancelled() above should handle that.
|
||||||
|
|
||||||
|
If this fails and nothing is in progress then we will complete task here.
|
||||||
|
*/
|
||||||
|
if ((data->enumerated_at_least_once && !data->connection_attempts && !data->connection_in_progress) ||
|
||||||
|
!data->enumerated_at_least_once)
|
||||||
{
|
{
|
||||||
|
g_debug ("GSocketClient: Address enumeration failed: %s", error ? error->message : NULL);
|
||||||
if (data->last_error)
|
if (data->last_error)
|
||||||
{
|
{
|
||||||
|
g_clear_error (&error);
|
||||||
error = data->last_error;
|
error = data->last_error;
|
||||||
data->last_error = NULL;
|
data->last_error = NULL;
|
||||||
}
|
}
|
||||||
else
|
else if (!error)
|
||||||
{
|
{
|
||||||
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
_("Unknown error on connect"));
|
_("Unknown error on connect"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
complete_connection_with_error (data, error);
|
||||||
}
|
}
|
||||||
g_task_return_error (data->task, error);
|
|
||||||
|
/* Enumeration should never trigger again, drop our ref */
|
||||||
g_object_unref (data->task);
|
g_object_unref (data->task);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data->enumerated_at_least_once = TRUE;
|
||||||
|
g_debug ("GSocketClient: Address enumeration succeeded");
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED,
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED,
|
||||||
data->connectable, NULL);
|
data->connectable, NULL);
|
||||||
|
|
||||||
if (G_IS_PROXY_ADDRESS (address) &&
|
|
||||||
data->client->priv->enable_proxy)
|
|
||||||
data->proxy_addr = g_object_ref (G_PROXY_ADDRESS (address));
|
|
||||||
|
|
||||||
g_clear_error (&data->last_error);
|
g_clear_error (&data->last_error);
|
||||||
|
|
||||||
socket = create_socket (data->client, address, &data->last_error);
|
socket = create_socket (data->client, address, &data->last_error);
|
||||||
@ -1793,6 +1896,10 @@ g_socket_client_enumerator_callback (GObject *object,
|
|||||||
attempt->cancellable = g_cancellable_new ();
|
attempt->cancellable = g_cancellable_new ();
|
||||||
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
|
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
|
||||||
attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS);
|
attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS);
|
||||||
|
|
||||||
|
if (G_IS_PROXY_ADDRESS (address) && data->client->priv->enable_proxy)
|
||||||
|
attempt->proxy_addr = g_object_ref (G_PROXY_ADDRESS (address));
|
||||||
|
|
||||||
g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL);
|
g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL);
|
||||||
g_source_attach (attempt->timeout_source, g_task_get_context (data->task));
|
g_source_attach (attempt->timeout_source, g_task_get_context (data->task));
|
||||||
data->connection_attempts = g_slist_append (data->connection_attempts, attempt);
|
data->connection_attempts = g_slist_append (data->connection_attempts, attempt);
|
||||||
@ -1802,6 +1909,7 @@ g_socket_client_enumerator_callback (GObject *object,
|
|||||||
g_object_ref (attempt->cancellable), g_object_unref);
|
g_object_ref (attempt->cancellable), g_object_unref);
|
||||||
|
|
||||||
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
|
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
|
||||||
|
g_debug ("GSocketClient: Starting TCP connection attempt");
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, attempt->connection);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, attempt->connection);
|
||||||
g_socket_connection_connect_async (G_SOCKET_CONNECTION (attempt->connection),
|
g_socket_connection_connect_async (G_SOCKET_CONNECTION (attempt->connection),
|
||||||
address,
|
address,
|
||||||
@ -1854,17 +1962,36 @@ g_socket_client_connect_async (GSocketClient *client,
|
|||||||
else
|
else
|
||||||
data->enumerator = g_socket_connectable_enumerate (connectable);
|
data->enumerator = g_socket_connectable_enumerate (connectable);
|
||||||
|
|
||||||
/* The flow and ownership here isn't quite obvious:
|
/* This function tries to match the behavior of g_socket_client_connect ()
|
||||||
- The task starts an async attempt to connect.
|
which is simple enough but much of it is done in parallel to be as responsive
|
||||||
- Each attempt holds a single ref on task.
|
as possible as per Happy Eyeballs (RFC 8305). This complicates flow quite a
|
||||||
- Each attempt may create new attempts by timing out (not a failure) so
|
bit but we can describe it in 3 sections:
|
||||||
there are multiple attempts happening in parallel.
|
|
||||||
- Upon failure an attempt will start a new attempt that steals its ref
|
Firstly we have address enumeration (DNS):
|
||||||
until there are no more attempts left and it drops its ref.
|
- This may be triggered multiple times by enumerator_next_async().
|
||||||
- Upon success it will cancel all other attempts and continue on
|
- It also has its own cancellable (data->enumeration_cancellable).
|
||||||
to the rest of the connection (tls, proxies, etc) which do not
|
- Enumeration is done lazily because GNetworkAddressAddressEnumerator
|
||||||
happen in parallel and at the very end drop its ref.
|
also does work in parallel and may lazily add new addresses.
|
||||||
- Upon cancellation an attempt drops its ref.
|
- If the first enumeration errors then the task errors. Otherwise all enumerations
|
||||||
|
will potentially be used (until task or enumeration is cancelled).
|
||||||
|
|
||||||
|
Then we start attempting connections (TCP):
|
||||||
|
- Each connection is independent and kept in a ConnectionAttempt object.
|
||||||
|
- They each hold a ref on the main task and have their own cancellable.
|
||||||
|
- Multiple attempts may happen in parallel as per Happy Eyeballs.
|
||||||
|
- Upon failure or timeouts more connection attempts are made.
|
||||||
|
- If no connections succeed the task errors.
|
||||||
|
- Upon success they are kept in a list of successful connections.
|
||||||
|
|
||||||
|
Lastly we connect at the application layer (TLS, Proxies):
|
||||||
|
- These are done in serial.
|
||||||
|
- The reasoning here is that Happy Eyeballs is about making bad connections responsive
|
||||||
|
at the IP/TCP layers. Issues at the application layer are generally not due to
|
||||||
|
connectivity issues but rather misconfiguration.
|
||||||
|
- Upon failure it will try the next TCP connection until it runs out and
|
||||||
|
the task errors.
|
||||||
|
- Upon success it cancels everything remaining (enumeration and connections)
|
||||||
|
and returns the connection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
data->task = g_task_new (client, cancellable, callback, user_data);
|
data->task = g_task_new (client, cancellable, callback, user_data);
|
||||||
@ -1872,6 +1999,11 @@ g_socket_client_connect_async (GSocketClient *client,
|
|||||||
g_task_set_source_tag (data->task, g_socket_client_connect_async);
|
g_task_set_source_tag (data->task, g_socket_client_connect_async);
|
||||||
g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free);
|
g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free);
|
||||||
|
|
||||||
|
data->enumeration_cancellable = g_cancellable_new ();
|
||||||
|
if (cancellable)
|
||||||
|
g_cancellable_connect (cancellable, G_CALLBACK (on_connection_cancelled),
|
||||||
|
g_object_ref (data->enumeration_cancellable), g_object_unref);
|
||||||
|
|
||||||
enumerator_next_async (data, FALSE);
|
enumerator_next_async (data, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1990,6 +2122,7 @@ g_socket_client_connect_to_uri_async (GSocketClient *client,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
g_debug("g_socket_client_connect_to_uri_async");
|
||||||
g_socket_client_connect_async (client,
|
g_socket_client_connect_async (client,
|
||||||
connectable, cancellable,
|
connectable, cancellable,
|
||||||
callback, user_data);
|
callback, user_data);
|
||||||
|
Loading…
Reference in New Issue
Block a user