Fix the retry-on-broken-connection codepath for SoupRequest
authorDan Winship <danw@gnome.org>
Mon, 9 Jul 2012 18:36:09 +0000 (14:36 -0400)
committerDan Winship <danw@gnome.org>
Fri, 13 Jul 2012 22:10:20 +0000 (18:10 -0400)
The retry-if-the-server-closes-the-connection-immediately code was
only getting run in the SoupMessage API codepaths. Fit it into the
right place in the SoupRequest paths too, and test that from
connection-test.

Might fix https://bugzilla.gnome.org/show_bug.cgi?id=679527 ?

libsoup/soup-message-io.c
libsoup/soup-misc-private.h
libsoup/soup-session-async.c
libsoup/soup-session-sync.c
libsoup/soup-session.c
tests/connection-test.c

index 42ddb7d..db29b11 100644 (file)
@@ -169,40 +169,6 @@ soup_message_io_finished (SoupMessage *msg)
 }
 
 static gboolean
-request_is_idempotent (SoupMessage *msg)
-{
-       /* FIXME */
-       return (msg->method == SOUP_METHOD_GET);
-}
-
-static void
-io_error (SoupMessage *msg, GError *error)
-{
-       SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
-       SoupMessageIOData *io = priv->io_data;
-
-       if (error && error->domain == G_TLS_ERROR) {
-               soup_message_set_status_full (msg,
-                                             SOUP_STATUS_SSL_FAILED,
-                                             error->message);
-       } else if (io->mode == SOUP_MESSAGE_IO_CLIENT &&
-                  io->read_state <= SOUP_MESSAGE_IO_STATE_HEADERS &&
-                  io->read_header_buf->len == 0 &&
-                  soup_connection_get_ever_used (io->item->conn) &&
-                  !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) &&
-                  request_is_idempotent (msg)) {
-               /* Connection got closed, but we can safely try again */
-               io->item->state = SOUP_MESSAGE_RESTARTING;
-       } else if (!SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code))
-               soup_message_set_status (msg, SOUP_STATUS_IO_ERROR);
-
-       if (error)
-               g_error_free (error);
-
-       soup_message_io_finished (msg);
-}
-
-static gboolean
 read_headers (SoupMessage *msg, GCancellable *cancellable, GError **error)
 {
        SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
@@ -815,6 +781,25 @@ soup_message_io_get_source (SoupMessage *msg, GCancellable *cancellable,
 }
 
 static gboolean
+request_is_restartable (SoupMessage *msg, GError *error)
+{
+       SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+       SoupMessageIOData *io = priv->io_data;
+
+       if (!io)
+               return FALSE;
+
+       return (io->mode == SOUP_MESSAGE_IO_CLIENT &&
+               io->read_state <= SOUP_MESSAGE_IO_STATE_HEADERS &&
+               io->read_header_buf->len == 0 &&
+               soup_connection_get_ever_used (io->item->conn) &&
+               !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) &&
+               !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) &&
+               error->domain != G_TLS_ERROR &&
+               SOUP_METHOD_IS_IDEMPOTENT (msg->method));
+}
+
+static gboolean
 io_run_until (SoupMessage *msg,
              SoupMessageIOState read_state, SoupMessageIOState write_state,
              GCancellable *cancellable, GError **error)
@@ -847,6 +832,15 @@ io_run_until (SoupMessage *msg,
        }
 
        if (my_error) {
+               if (request_is_restartable (msg, my_error)) {
+                       /* Connection got closed, but we can safely try again */
+                       g_error_free (my_error);
+                       g_set_error_literal (error, SOUP_HTTP_ERROR,
+                                            SOUP_STATUS_TRY_AGAIN, "");
+                       g_object_unref (msg);
+                       return FALSE;
+               }
+
                g_propagate_error (error, my_error);
                g_object_unref (msg);
                return FALSE;
@@ -903,7 +897,17 @@ io_run (SoupMessage *msg, gpointer user_data)
                io->io_source = soup_message_io_get_source (msg, NULL, io_run, msg);
                g_source_attach (io->io_source, io->async_context);
        } else if (error && priv->io_data == io) {
-               io_error (msg, error);
+               if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN))
+                       io->item->state = SOUP_MESSAGE_RESTARTING;
+               else if (error->domain == G_TLS_ERROR) {
+                       soup_message_set_status_full (msg,
+                                                     SOUP_STATUS_SSL_FAILED,
+                                                     error->message);
+               } else if (!SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code))
+                       soup_message_set_status (msg, SOUP_STATUS_IO_ERROR);
+
+               g_error_free (error);
+               soup_message_io_finished (msg);
        }
 
        g_object_unref (msg);
index 06978e8..8276b8a 100644 (file)
@@ -26,4 +26,19 @@ GIOStream *soup_socket_get_iostream   (SoupSocket *sock);
 #define SOUP_SOCKET_USE_PROXY     "use-proxy"
 SoupURI *soup_socket_get_http_proxy_uri (SoupSocket *sock);
 
+/* At some point it might be possible to mark additional methods
+ * safe or idempotent...
+ */
+#define SOUP_METHOD_IS_SAFE(method) (method == SOUP_METHOD_GET || \
+                                    method == SOUP_METHOD_HEAD || \
+                                    method == SOUP_METHOD_OPTIONS || \
+                                    method == SOUP_METHOD_PROPFIND)
+
+#define SOUP_METHOD_IS_IDEMPOTENT(method) (method == SOUP_METHOD_GET || \
+                                          method == SOUP_METHOD_HEAD || \
+                                          method == SOUP_METHOD_OPTIONS || \
+                                          method == SOUP_METHOD_PROPFIND || \
+                                          method == SOUP_METHOD_PUT || \
+                                          method == SOUP_METHOD_DELETE)
+
 #endif /* SOUP_URI_PRIVATE_H */
index c9eda19..ad006ee 100644 (file)
@@ -660,6 +660,13 @@ try_run_until_read (SoupMessageQueueItem *item)
                return;
        }
 
+       if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) {
+               item->state = SOUP_MESSAGE_RESTARTING;
+               soup_message_io_finished (item->msg);
+               g_error_free (error);
+               return;
+       }
+
        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
                send_request_return_result (item, NULL, error);
                return;
index 3ac290a..1284176 100644 (file)
@@ -493,8 +493,15 @@ soup_session_send_request (SoupSession   *session,
                        break;
 
                /* Send request, read headers */
-               if (!soup_message_io_run_until_read (msg, item->cancellable, &my_error))
-                       break;
+               if (!soup_message_io_run_until_read (msg, item->cancellable, &my_error)) {
+                       if (g_error_matches (my_error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) {
+                               item->state = SOUP_MESSAGE_RESTARTING;
+                               soup_message_io_finished (item->msg);
+                               g_clear_error (&my_error);
+                               continue;
+                       } else
+                               break;
+               }
 
                stream = soup_message_io_get_response_istream (msg, &my_error);
                if (!stream)
index 23f9e94..bcd0be9 100644 (file)
@@ -17,6 +17,7 @@
 #include "soup-connection.h"
 #include "soup-marshal.h"
 #include "soup-message-private.h"
+#include "soup-misc-private.h"
 #include "soup-message-queue.h"
 #include "soup-proxy-resolver-static.h"
 #include "soup-session-private.h"
@@ -850,22 +851,6 @@ auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
                session, msg, auth, retrying);
 }
 
-/* At some point it might be possible to mark additional methods
- * safe or idempotent...
- */
-#define SOUP_METHOD_IS_SAFE(method) (method == SOUP_METHOD_GET || \
-                                    method == SOUP_METHOD_HEAD || \
-                                    method == SOUP_METHOD_OPTIONS || \
-                                    method == SOUP_METHOD_PROPFIND)
-
-#define SOUP_METHOD_IS_IDEMPOTENT(method) (method == SOUP_METHOD_GET || \
-                                          method == SOUP_METHOD_HEAD || \
-                                          method == SOUP_METHOD_OPTIONS || \
-                                          method == SOUP_METHOD_PROPFIND || \
-                                          method == SOUP_METHOD_PUT || \
-                                          method == SOUP_METHOD_DELETE)
-
-
 #define SOUP_SESSION_WOULD_REDIRECT_AS_GET(session, msg) \
        ((msg)->status_code == SOUP_STATUS_SEE_OTHER || \
         ((msg)->status_code == SOUP_STATUS_FOUND && \
index ee2a78b..ddf0c98 100644 (file)
@@ -238,7 +238,6 @@ request_started_socket_collector (SoupSession *session, SoupMessage *msg,
 
        debug_printf (1, "      socket queue overflowed!\n");
        errors++;
-       soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
 }
 
 static void
@@ -298,21 +297,138 @@ do_timeout_test_for_session (SoupSession *session)
 }
 
 static void
+do_timeout_req_test_for_session (SoupSession *session)
+{
+       SoupRequester *requester;
+       SoupRequest *req;
+       SoupMessage *msg;
+       GInputStream *stream;
+       SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
+       SoupURI *timeout_uri;
+       GError *error = NULL;
+       int i;
+
+       requester = soup_requester_new ();
+       soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester));
+       g_object_unref (requester);
+
+       g_signal_connect (session, "request-started",
+                         G_CALLBACK (request_started_socket_collector),
+                         &sockets);
+
+       debug_printf (1, "    First request\n");
+       timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
+       req = soup_requester_request_uri (requester, timeout_uri, NULL);
+       soup_uri_free (timeout_uri);
+
+       if (SOUP_IS_SESSION_SYNC (session))
+               stream = soup_request_send (req, NULL, &error);
+       else
+               stream = soup_test_request_send_async_as_sync (req, NULL, &error);
+
+       if (!stream) {
+               debug_printf (1, "      Unexpected error on send: %s\n",
+                             error->message);
+               errors++;
+               g_clear_error (&error);
+       } else {
+               if (SOUP_IS_SESSION_SYNC (session))
+                       g_input_stream_close (stream, NULL, &error);
+               else
+                       soup_test_stream_close_async_as_sync (stream, NULL, &error);
+               if (error) {
+                       debug_printf (1, "  Unexpected error on close: %s\n",
+                                     error->message);
+                       errors++;
+                       g_clear_error (&error);
+               }
+       }
+
+       if (sockets[1]) {
+               debug_printf (1, "      Message was retried??\n");
+               errors++;
+               sockets[1] = sockets[2] = sockets[3] = NULL;
+       }
+       g_object_unref (req);
+
+       debug_printf (1, "    Second request\n");
+       req = soup_requester_request_uri (requester, base_uri, NULL);
+
+       if (SOUP_IS_SESSION_SYNC (session))
+               stream = soup_request_send (req, NULL, &error);
+       else
+               stream = soup_test_request_send_async_as_sync (req, NULL, &error);
+
+       if (!stream) {
+               debug_printf (1, "      Unexpected error on send: %s\n",
+                             error->message);
+               errors++;
+               g_clear_error (&error);
+       } else {
+               if (SOUP_IS_SESSION_SYNC (session))
+                       g_input_stream_close (stream, NULL, &error);
+               else
+                       soup_test_stream_close_async_as_sync (stream, NULL, &error);
+               if (error) {
+                       debug_printf (1, "  Unexpected error on close: %s\n",
+                                     error->message);
+                       errors++;
+                       g_clear_error (&error);
+               }
+       }
+
+       msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
+       if (msg->status_code != SOUP_STATUS_OK) {
+               debug_printf (1, "      Unexpected response: %d %s\n",
+                             msg->status_code, msg->reason_phrase);
+               errors++;
+       }
+       if (sockets[1] != sockets[0]) {
+               debug_printf (1, "      Message was not retried on existing connection\n");
+               errors++;
+       } else if (!sockets[2]) {
+               debug_printf (1, "      Message was not retried after disconnect\n");
+               errors++;
+       } else if (sockets[2] == sockets[1]) {
+               debug_printf (1, "      Message was retried on closed connection??\n");
+               errors++;
+       } else if (sockets[3]) {
+               debug_printf (1, "      Message was retried again??\n");
+               errors++;
+       }
+       g_object_unref (msg);
+       g_object_unref (req);
+
+       for (i = 0; sockets[i]; i++)
+               g_object_unref (sockets[i]);
+}
+
+static void
 do_persistent_connection_timeout_test (void)
 {
        SoupSession *session;
 
        debug_printf (1, "\nUnexpected timing out of persistent connections\n");
 
-       debug_printf (1, "  Async session\n");
+       debug_printf (1, "  Async session, message API\n");
        session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
        do_timeout_test_for_session (session);
        soup_test_session_abort_unref (session);
 
-       debug_printf (1, "  Sync session\n");
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       do_timeout_req_test_for_session (session);
+       soup_test_session_abort_unref (session);
+
+       debug_printf (1, "  Sync session, message API\n");
        session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
        do_timeout_test_for_session (session);
        soup_test_session_abort_unref (session);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
+       do_timeout_req_test_for_session (session);
+       soup_test_session_abort_unref (session);
 }
 
 static GMainLoop *max_conns_loop;