mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-28 00:16:15 +01:00
1d28fd530c
This test ensures that g_socket_client_connect_to_host_async() fails if it is cancelled, but it's not cancelled until after 1 millisecond. Our CI testers are hitting that race window, and Milan is able to reproduce the crash locally as well. Switching it from 1ms to 0ms is enough for Milan to avoid the crash, but not enough for our CI, so let's move the cancellation to a GSocketClientEvent callback where the timing is completely deterministic. Hopefully fixes #2221
186 lines
5.4 KiB
C
186 lines
5.4 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
|
*
|
|
* Copyright (C) 2018 Igalia S.L.
|
|
*
|
|
* 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.1 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <gio/gio.h>
|
|
|
|
static void
|
|
on_connected (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GSocketConnection *conn;
|
|
GError *error = NULL;
|
|
|
|
conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_object_unref (conn);
|
|
g_main_loop_quit (user_data);
|
|
}
|
|
|
|
static void
|
|
test_happy_eyeballs (void)
|
|
{
|
|
GSocketClient *client;
|
|
GSocketService *service;
|
|
GError *error = NULL;
|
|
guint16 port;
|
|
GMainLoop *loop;
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
service = g_socket_service_new ();
|
|
port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
|
|
g_assert_no_error (error);
|
|
g_socket_service_start (service);
|
|
|
|
/* All of the magic here actually happens in slow-connect-preload.c
|
|
* which as you would guess is preloaded. So this is just making a
|
|
* normal connection that happens to take 600ms each time. This will
|
|
* trigger the logic to make multiple parallel connections.
|
|
*/
|
|
client = g_socket_client_new ();
|
|
g_socket_client_connect_to_host_async (client, "localhost", port, NULL, on_connected, loop);
|
|
g_main_loop_run (loop);
|
|
|
|
g_main_loop_unref (loop);
|
|
g_object_unref (service);
|
|
g_object_unref (client);
|
|
}
|
|
|
|
static void
|
|
on_connected_cancelled (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GSocketConnection *conn;
|
|
GError *error = NULL;
|
|
|
|
conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_assert_null (conn);
|
|
|
|
g_error_free (error);
|
|
g_main_loop_quit (user_data);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GCancellable *cancellable;
|
|
gboolean completed;
|
|
} EventCallbackData;
|
|
|
|
static void
|
|
on_event (GSocketClient *client,
|
|
GSocketClientEvent event,
|
|
GSocketConnectable *connectable,
|
|
GIOStream *connection,
|
|
EventCallbackData *data)
|
|
{
|
|
if (data->cancellable && event == G_SOCKET_CLIENT_CONNECTED)
|
|
{
|
|
g_cancellable_cancel (data->cancellable);
|
|
}
|
|
else if (event == G_SOCKET_CLIENT_COMPLETE)
|
|
{
|
|
data->completed = TRUE;
|
|
g_assert_null (connection);
|
|
}
|
|
}
|
|
|
|
static void
|
|
test_happy_eyeballs_cancel_delayed (void)
|
|
{
|
|
GSocketClient *client;
|
|
GSocketService *service;
|
|
GError *error = NULL;
|
|
guint16 port;
|
|
GMainLoop *loop;
|
|
EventCallbackData data = { NULL, FALSE };
|
|
|
|
/* This just tests that cancellation works as expected, still emits the completed signal,
|
|
* and never returns a connection */
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
service = g_socket_service_new ();
|
|
port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
|
|
g_assert_no_error (error);
|
|
g_socket_service_start (service);
|
|
|
|
client = g_socket_client_new ();
|
|
data.cancellable = g_cancellable_new ();
|
|
g_socket_client_connect_to_host_async (client, "localhost", port, data.cancellable, on_connected_cancelled, loop);
|
|
g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_true (data.completed);
|
|
g_main_loop_unref (loop);
|
|
g_object_unref (service);
|
|
g_object_unref (client);
|
|
g_object_unref (data.cancellable);
|
|
}
|
|
|
|
static void
|
|
test_happy_eyeballs_cancel_instant (void)
|
|
{
|
|
GSocketClient *client;
|
|
GSocketService *service;
|
|
GError *error = NULL;
|
|
guint16 port;
|
|
GMainLoop *loop;
|
|
GCancellable *cancel;
|
|
EventCallbackData data = { NULL, FALSE };
|
|
|
|
/* This tests the same things as above, test_happy_eyeballs_cancel_delayed(), but
|
|
* with different timing since it sends an already cancelled cancellable */
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
service = g_socket_service_new ();
|
|
port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
|
|
g_assert_no_error (error);
|
|
g_socket_service_start (service);
|
|
|
|
client = g_socket_client_new ();
|
|
cancel = g_cancellable_new ();
|
|
g_cancellable_cancel (cancel);
|
|
g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop);
|
|
g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_true (data.completed);
|
|
g_main_loop_unref (loop);
|
|
g_object_unref (service);
|
|
g_object_unref (client);
|
|
g_object_unref (cancel);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_test_add_func ("/socket-client/happy-eyeballs/slow", test_happy_eyeballs);
|
|
g_test_add_func ("/socket-client/happy-eyeballs/cancellation/instant", test_happy_eyeballs_cancel_instant);
|
|
g_test_add_func ("/socket-client/happy-eyeballs/cancellation/delayed", test_happy_eyeballs_cancel_delayed);
|
|
|
|
|
|
return g_test_run ();
|
|
}
|