Fix a few recent request API bugs, add some more tests
authorDan Winship <danw@gnome.org>
Fri, 4 May 2012 00:20:29 +0000 (20:20 -0400)
committerDan Winship <danw@gnome.org>
Fri, 4 May 2012 00:25:28 +0000 (20:25 -0400)
libsoup/soup-message-io.c
libsoup/soup-message-queue.h
libsoup/soup-session-async.c
tests/misc-test.c

index a2b6bb5..d721c1d 100644 (file)
@@ -149,8 +149,12 @@ soup_message_io_stop (SoupMessage *msg)
                io->unpause_source = NULL;
        }
 
-       if (io->read_state < SOUP_MESSAGE_IO_STATE_FINISHING)
-               g_io_stream_close (io->iostream, NULL, NULL);
+       if (io->read_state < SOUP_MESSAGE_IO_STATE_FINISHING) {
+               if (io->item && io->item->conn)
+                       soup_connection_disconnect (io->item->conn);
+               else
+                       g_io_stream_close (io->iostream, NULL, NULL);
+       }
 }
 
 void
index 7e7a308..54c2971 100644 (file)
@@ -45,7 +45,8 @@ struct _SoupMessageQueueItem {
 
        guint paused            : 1;
        guint new_api           : 1;
-       guint redirection_count : 31;
+       guint io_started        : 1;
+       guint redirection_count : 29;
 
        SoupMessageQueueItemState state;
 
index 0f3aeff..4f88206 100644 (file)
@@ -562,6 +562,7 @@ send_request_restarted (SoupSession *session, SoupMessageQueueItem *item)
 {
        /* We won't be needing this, then. */
        g_object_set_data (G_OBJECT (item->msg), "SoupSessionAsync:ostream", NULL);
+       item->io_started = FALSE;
 }
 
 static void
@@ -587,7 +588,7 @@ send_request_finished (SoupSession *session, SoupMessageQueueItem *item)
                size = g_memory_output_stream_get_data_size (mostream);
                data = size ? g_memory_output_stream_steal_data (mostream) : g_strdup ("");
                istream = g_memory_input_stream_new_from_data (data, size, g_free);
-       } else {
+       } else if (item->io_started) {
                /* The message finished before becoming readable. This
                 * will happen, eg, if it's cancelled from got-headers.
                 * Do nothing; the op will complete via read_ready_cb()
@@ -702,6 +703,7 @@ try_run_until_read (SoupMessageQueueItem *item)
 static void
 send_request_running (SoupSession *session, SoupMessageQueueItem *item)
 {
+       item->io_started = TRUE;
        try_run_until_read (item);
 }
 
index 261672c..39d63d3 100644 (file)
@@ -5,7 +5,9 @@
 
 #include <string.h>
 
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
 #include <libsoup/soup.h>
+#include <libsoup/soup-requester.h>
 
 #include "test-utils.h"
 
@@ -217,7 +219,7 @@ do_callback_unref_test (void)
        GMainLoop *loop;
        char *bad_uri;
 
-       debug_printf (1, "\nCallback unref handling\n");
+       debug_printf (1, "\nCallback unref handling (msg api)\n");
 
        /* Get a guaranteed-bad URI */
        addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
@@ -269,6 +271,123 @@ do_callback_unref_test (void)
        /* Otherwise, if we haven't crashed, we're ok. */
 }
 
+static void
+cur_one_completed (GObject *source, GAsyncResult *result, gpointer session)
+{
+       SoupRequest *one = SOUP_REQUEST (source);
+       GError *error = NULL;
+
+       debug_printf (2, "  Request 1 completed\n");
+       if (soup_request_send_finish (one, result, &error)) {
+               debug_printf (1, "  Request 1 succeeded?\n");
+               errors++;
+       } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT)) {
+               debug_printf (1, "  Unexpected error on Request 1: %s\n",
+                             error->message);
+               errors++;
+       }
+       g_clear_error (&error);
+
+       g_object_unref (session);
+}
+
+static gboolean
+cur_idle_quit (gpointer loop)
+{
+       g_main_loop_quit (loop);
+       return FALSE;
+}
+
+static void
+cur_two_completed (GObject *source, GAsyncResult *result, gpointer loop)
+{
+       SoupRequest *two = SOUP_REQUEST (source);
+       GError *error = NULL;
+
+       debug_printf (2, "  Request 2 completed\n");
+       if (soup_request_send_finish (two, result, &error)) {
+               debug_printf (1, "  Request 2 succeeded?\n");
+               errors++;
+       } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT)) {
+               debug_printf (1, "  Unexpected error on Request 2: %s\n",
+                             error->message);
+               errors++;
+       }
+       g_clear_error (&error);
+
+       g_idle_add (cur_idle_quit, loop); 
+}
+
+static void
+do_callback_unref_req_test (void)
+{
+       SoupServer *bad_server;
+       SoupAddress *addr;
+       SoupSession *session;
+       SoupRequester *requester;
+       SoupRequest *one, *two;
+       GMainLoop *loop;
+       char *bad_uri;
+
+       debug_printf (1, "\nCallback unref handling (request api)\n");
+
+       /* Get a guaranteed-bad URI */
+       addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
+       soup_address_resolve_sync (addr, NULL);
+       bad_server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
+                                     NULL);
+       g_object_unref (addr);
+
+       bad_uri = g_strdup_printf ("http://127.0.0.1:%u/",
+                                  soup_server_get_port (bad_server));
+       g_object_unref (bad_server);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session);
+
+       requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+
+       loop = g_main_loop_new (NULL, TRUE);
+
+       one = soup_requester_request (requester, bad_uri, NULL);
+       g_object_add_weak_pointer (G_OBJECT (one), (gpointer *)&one);
+       two = soup_requester_request (requester, bad_uri, NULL);
+       g_object_add_weak_pointer (G_OBJECT (two), (gpointer *)&two);
+       g_free (bad_uri);
+
+       soup_request_send_async (one, NULL, cur_one_completed, session);
+       g_object_unref (one);
+       soup_request_send_async (two, NULL, cur_two_completed, loop);
+       g_object_unref (two);
+
+       g_main_loop_run (loop);
+       g_main_loop_unref (loop);
+
+       if (session) {
+               g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session);
+               debug_printf (1, "  Session not destroyed?\n");
+               errors++;
+               g_object_unref (session);
+       }
+       if (one) {
+               g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one);
+               debug_printf (1, "  Request 1 not destroyed?\n");
+               errors++;
+               g_object_unref (one);
+       }
+       if (two) {
+               g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two);
+               debug_printf (1, "  Request 2 not destroyed?\n");
+               errors++;
+               g_object_unref (two);
+       }
+
+       /* Otherwise, if we haven't crashed, we're ok. */
+}
+
 /* SoupSession should clean up all signal handlers on a message after
  * it is finished, allowing the message to be reused if desired.
  * #559054
@@ -472,7 +591,7 @@ do_early_abort_test (void)
        GMainContext *context;
        GMainLoop *loop;
 
-       debug_printf (1, "\nAbort with pending connection\n");
+       debug_printf (1, "\nAbort with pending connection (msg api)\n");
 
        session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
        msg = soup_message_new_from_uri ("GET", base_uri);
@@ -530,6 +649,138 @@ do_early_abort_test (void)
 }
 
 static void
+ear_one_completed (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+       GError *error = NULL;
+
+       debug_printf (2, "  Request 1 completed\n");
+       if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
+               debug_printf (1, "  Request 1 succeeded?\n");
+               errors++;
+       } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
+               debug_printf (1, "  Unexpected error on Request 1: %s\n",
+                             error->message);
+               errors++;
+       }
+       g_clear_error (&error);
+}
+
+static void
+ear_two_completed (GObject *source, GAsyncResult *result, gpointer loop)
+{
+       GError *error = NULL;
+
+       debug_printf (2, "  Request 2 completed\n");
+       if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
+               debug_printf (1, "  Request 2 succeeded?\n");
+               errors++;
+       } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
+               debug_printf (1, "  Unexpected error on Request 2: %s\n",
+                             error->message);
+               errors++;
+       }
+       g_clear_error (&error);
+
+       g_main_loop_quit (loop);
+}
+
+static void
+ear_three_completed (GObject *source, GAsyncResult *result, gpointer loop)
+{
+       GError *error = NULL;
+
+       debug_printf (2, "  Request 3 completed\n");
+       if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
+               debug_printf (1, "  Request 3 succeeded?\n");
+               errors++;
+       } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+               debug_printf (1, "  Unexpected error on Request 3: %s\n",
+                             error->message);
+               errors++;
+       }
+       g_clear_error (&error);
+
+       g_main_loop_quit (loop);
+}
+
+static void
+ear_request_started (SoupSession *session, SoupMessage *msg,
+                    SoupSocket *socket, gpointer cancellable)
+{
+       g_cancellable_cancel (cancellable);
+}
+
+static void
+do_early_abort_req_test (void)
+{
+       SoupSession *session;
+       SoupRequester *requester;
+       SoupRequest *req;
+       GMainContext *context;
+       GMainLoop *loop;
+       GCancellable *cancellable;
+
+       debug_printf (1, "\nAbort with pending connection (request api)\n");
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+       req = soup_requester_request_uri (requester, base_uri, NULL);
+
+       context = g_main_context_default ();
+       loop = g_main_loop_new (context, TRUE);
+       soup_request_send_async (req, NULL, ear_one_completed, NULL);
+       g_object_unref (req);
+       g_main_context_iteration (context, FALSE);
+
+       soup_session_abort (session);
+       while (g_main_context_pending (context))
+               g_main_context_iteration (context, FALSE);
+       soup_test_session_abort_unref (session);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+       req = soup_requester_request_uri (requester, base_uri, NULL);
+
+       g_signal_connect (session, "connection-created",
+                         G_CALLBACK (ea_connection_created), NULL);
+       soup_request_send_async (req, NULL, ear_two_completed, loop);
+       g_main_loop_run (loop);
+       g_object_unref (req);
+
+       while (g_main_context_pending (context))
+               g_main_context_iteration (context, FALSE);
+
+       soup_test_session_abort_unref (session);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+       req = soup_requester_request_uri (requester, base_uri, NULL);
+
+       cancellable = g_cancellable_new ();
+       g_signal_connect (session, "request-started",
+                         G_CALLBACK (ear_request_started), cancellable);
+       soup_request_send_async (req, cancellable, ear_three_completed, loop);
+       g_main_loop_run (loop);
+       g_object_unref (req);
+       g_object_unref (cancellable);
+
+       while (g_main_context_pending (context))
+               g_main_context_iteration (context, FALSE);
+
+       soup_test_session_abort_unref (session);
+       g_main_loop_unref (loop);
+}
+
+static void
 do_one_accept_language_test (const char *language, const char *expected_header)
 {
        SoupSession *session;
@@ -642,7 +893,7 @@ do_cancel_while_reading_test (void)
 {
        SoupSession *session;
 
-       debug_printf (1, "\nCancelling message while reading response\n");
+       debug_printf (1, "\nCancelling message while reading response (msg api)\n");
 
        debug_printf (1, "  Async session\n");
        session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
@@ -655,6 +906,108 @@ do_cancel_while_reading_test (void)
        soup_test_session_abort_unref (session);
 }
 
+static gboolean
+cancel_request_timeout (gpointer cancellable)
+{
+       g_cancellable_cancel (cancellable);
+       return FALSE;
+}
+
+static gpointer
+cancel_request_thread (gpointer cancellable)
+{
+       g_usleep (100000); /* .1s */
+       g_cancellable_cancel (cancellable);
+       return NULL;
+}
+
+static void
+cancel_request_finished (GObject *source, GAsyncResult *result, gpointer loop)
+{
+       GError *error = NULL;
+
+       if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
+               debug_printf (1, "  Request succeeded?\n");
+               errors++;
+       } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+               debug_printf (1, "  Unexpected error: %s\n",
+                             error->message);
+               errors++;
+       }
+       g_clear_error (&error);
+
+       g_main_loop_quit (loop);
+}
+
+static void
+do_cancel_while_reading_req_test_for_session (SoupRequester *requester)
+{
+       SoupRequest *req;
+       SoupURI *uri;
+       GCancellable *cancellable;
+
+       uri = soup_uri_new_with_base (base_uri, "/slow");
+       req = soup_requester_request_uri (requester, uri, NULL);
+       soup_uri_free (uri);
+
+       cancellable = g_cancellable_new ();
+
+       if (SOUP_IS_SESSION_ASYNC (soup_request_get_session (req))) {
+               GMainLoop *loop;
+
+               loop = g_main_loop_new (NULL, FALSE);
+               g_timeout_add (100, cancel_request_timeout, cancellable);
+               soup_request_send_async (req, cancellable,
+                                        cancel_request_finished, loop);
+               g_main_loop_run (loop);
+               g_main_loop_unref (loop);
+       } else {
+               GThread *thread;
+               GError *error = NULL;
+
+               thread = g_thread_new ("cancel_request_thread", cancel_request_thread, cancellable);
+               soup_request_send (req, cancellable, &error);
+               if (!error) {
+                       debug_printf (1, "  Request succeeded?\n");
+                       errors++;
+               } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       debug_printf (1, "  Unexpected error: %s\n",
+                                     error->message);
+                       errors++;
+               }
+               g_thread_unref (thread);
+       }
+
+       g_object_unref (req);
+       g_object_unref (cancellable);
+}
+
+static void
+do_cancel_while_reading_req_test (void)
+{
+       SoupSession *session;
+       SoupRequester *requester;
+
+       debug_printf (1, "\nCancelling message while reading response (request api)\n");
+
+       debug_printf (1, "  Async session\n");
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+       do_cancel_while_reading_req_test_for_session (requester);
+       soup_test_session_abort_unref (session);
+
+       debug_printf (1, "  Sync session\n");
+       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
+                                        SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
+                                        NULL);
+       requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+       do_cancel_while_reading_req_test_for_session (requester);
+       soup_test_session_abort_unref (session);
+}
+
 static void
 do_aliases_test_for_session (SoupSession *session,
                             const char *redirect_protocol)
@@ -878,11 +1231,14 @@ main (int argc, char **argv)
 
        do_host_test ();
        do_callback_unref_test ();
+       do_callback_unref_req_test ();
        do_msg_reuse_test ();
        do_star_test ();
        do_early_abort_test ();
+       do_early_abort_req_test ();
        do_accept_language_test ();
        do_cancel_while_reading_test ();
+       do_cancel_while_reading_req_test ();
        do_aliases_test ();
        do_dot_dot_test ();
        do_ipv6_test ();