[libsoup] Async request never completes if message is cancelled before reading body data while being paused
- From: Carlos Garcia Campos <carlosgc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsoup] Async request never completes if message is cancelled before reading body data while being paused
- Date: Wed, 3 May 2017 16:20:53 +0000 (UTC)
commit 9f7ab7bcff77567ddf92aef206e8ec3626111aeb
Author: Carlos Garcia Campos <cgarcia igalia com>
Date: Thu Oct 20 09:02:23 2016 +0200
Async request never completes if message is cancelled before reading body data while being paused
The async ready cabllack is never called in this case. I noticed this in WebKit, it happens if you
open a website that is HTTP authenticated and close the tab without responding to the auth dialog.
What happens internally is that we pause the message on authenticate signal, and then we cancel the
message. The message and its io are properly finished, but the request async ready callback is never
called, which caused the WebKit object to be leaked, because it takes a reference of the object on
send_sync and releases on the async ready callback.
The sequence of libsoup internals is the following one:
1- Message starts normally and on ready async_send_request_running is called that sets io_started = TRUE
and starts the io
2- Before message io started to read the body data, it can be authenticate or got-headers, for example,
the message is paused.
3- Next call to try_run_until_read calls soup_message_io_run_until_read() that returns
G_IO_ERROR_WOULD_BLOCK
because the message is paused.
4- Because of the G_IO_ERROR_WOULD_BLOCK try_run_until_read creates a SoupMessageSource. Since the
message is
paused but there's io in progress, the SoupMessageSource is created witout a bae source, and
therefore it's
not cancellable with the passed GCancellable.
5- The message is cancelled, soup_session_cancel_message checks that the message is paused, so it marks
the
item as not paused, and also unpause the message io, sets the status message, cancels the cancellable
and
kicks the item queue.
6- When the item is processed again the status is changed to FINISHING and then soup_message_finished is
called.
7- async_send_request_finished() is called due to message finished signal emission, but returns early
without
calling async_send_request_return_result because item->io_started is TRUE (see step 1).
8- The SoupMessageSource is still alive, but since it's paused and now message doesn't have io because
it was
finished, the source is never dispatched (message_source_check always returns false because paused is
TRUE
and io is NULL).
The problem is in this last step, I think the source should be dispatched when the io becomes NULL and the
source was created paused.
https://bugzilla.gnome.org/show_bug.cgi?id=773257
libsoup/soup-message-io.c | 2 +-
tests/requester-test.c | 79 +++++++++++++++++++++++++++++++++++---------
2 files changed, 64 insertions(+), 17 deletions(-)
---
diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c
index a0a052b..e893ec2 100644
--- a/libsoup/soup-message-io.c
+++ b/libsoup/soup-message-io.c
@@ -817,7 +817,7 @@ message_source_check (GSource *source)
SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (message_source->msg);
SoupMessageIOData *io = priv->io_data;
- if (!io || io->paused)
+ if (io && io->paused)
return FALSE;
else
return TRUE;
diff --git a/tests/requester-test.c b/tests/requester-test.c
index 04c1d02..6d6d572 100644
--- a/tests/requester-test.c
+++ b/tests/requester-test.c
@@ -17,6 +17,12 @@ SoupBuffer *response, *auth_response;
#define REDIRECT_HTML_BODY "<html><body>Try again</body></html>\r\n"
#define AUTH_HTML_BODY "<html><body>Unauthorized</body></html>\r\n"
+typedef enum {
+ NO_CANCEL,
+ SYNC_CANCEL,
+ PAUSE_AND_CANCEL_ON_IDLE
+} CancelPolicy;
+
static gboolean
slow_finish_message (gpointer msg)
{
@@ -205,6 +211,32 @@ cancel_message (SoupMessage *msg, gpointer session)
soup_session_cancel_message (session, msg, SOUP_STATUS_FORBIDDEN);
}
+typedef struct {
+ SoupMessage *msg;
+ SoupSession *session;
+} CancelData;
+
+static gboolean
+cancel_message_idle (CancelData *data)
+{
+ cancel_message (data->msg, data->session);
+ return FALSE;
+}
+
+static void
+pause_and_cancel_message (SoupMessage *msg, gpointer session)
+{
+ CancelData *data = g_new (CancelData, 1);
+ GSource *source = g_idle_source_new ();
+
+ soup_session_pause_message (session, msg);
+ data->msg = msg;
+ data->session = session;
+ g_source_set_callback (source, (GSourceFunc)cancel_message_idle, data, g_free);
+ g_source_attach (source, soup_session_get_async_context (session));
+ g_source_unref (source);
+}
+
static void
request_started (SoupSession *session, SoupMessage *msg,
SoupSocket *socket, gpointer user_data)
@@ -219,7 +251,7 @@ static void
do_async_test (SoupSession *session, SoupURI *uri,
GAsyncReadyCallback callback, guint expected_status,
SoupBuffer *expected_response,
- gboolean persistent, gboolean cancel)
+ gboolean persistent, CancelPolicy cancel_policy)
{
SoupRequester *requester;
SoupRequest *request;
@@ -234,16 +266,24 @@ do_async_test (SoupSession *session, SoupURI *uri,
requester = NULL;
data.body = g_string_new (NULL);
- data.cancel = cancel;
+ data.cancel = cancel_policy != NO_CANCEL;
if (requester)
request = soup_requester_request_uri (requester, uri, NULL);
else
request = soup_session_request_uri (session, uri, NULL);
msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
- if (cancel) {
+ switch (cancel_policy) {
+ case SYNC_CANCEL:
g_signal_connect (msg, "got-headers",
G_CALLBACK (cancel_message), session);
+ break;
+ case PAUSE_AND_CANCEL_ON_IDLE:
+ g_signal_connect (msg, "got-headers",
+ G_CALLBACK (pause_and_cancel_message), session);
+ break;
+ case NO_CANCEL:
+ break;
}
started_id = g_signal_connect (session, "request-started",
@@ -294,34 +334,41 @@ do_test_for_thread_and_context (SoupSession *session, SoupURI *base_uri)
debug_printf (1, " basic test\n");
do_async_test (session, base_uri, test_sent,
SOUP_STATUS_OK, response,
- TRUE, FALSE);
+ TRUE, NO_CANCEL);
debug_printf (1, " chunked test\n");
uri = soup_uri_new_with_base (base_uri, "/chunked");
do_async_test (session, uri, test_sent,
SOUP_STATUS_OK, response,
- TRUE, FALSE);
+ TRUE, NO_CANCEL);
soup_uri_free (uri);
debug_printf (1, " auth test\n");
uri = soup_uri_new_with_base (base_uri, "/auth");
do_async_test (session, uri, auth_test_sent,
SOUP_STATUS_UNAUTHORIZED, auth_response,
- TRUE, FALSE);
+ TRUE, NO_CANCEL);
soup_uri_free (uri);
debug_printf (1, " non-persistent test\n");
uri = soup_uri_new_with_base (base_uri, "/non-persistent");
do_async_test (session, uri, test_sent,
SOUP_STATUS_OK, response,
- FALSE, FALSE);
+ FALSE, NO_CANCEL);
soup_uri_free (uri);
debug_printf (1, " cancellation test\n");
uri = soup_uri_new_with_base (base_uri, "/");
do_async_test (session, uri, test_sent,
SOUP_STATUS_FORBIDDEN, NULL,
- FALSE, TRUE);
+ FALSE, SYNC_CANCEL);
+ soup_uri_free (uri);
+
+ debug_printf (1, " cancellation after paused test\n");
+ uri = soup_uri_new_with_base (base_uri, "/");
+ do_async_test (session, uri, test_sent,
+ SOUP_STATUS_FORBIDDEN, NULL,
+ FALSE, PAUSE_AND_CANCEL_ON_IDLE);
soup_uri_free (uri);
}
@@ -433,7 +480,7 @@ do_plain_test_in_thread (gconstpointer data)
static void
do_sync_request (SoupSession *session, SoupRequest *request,
guint expected_status, SoupBuffer *expected_response,
- gboolean persistent, gboolean cancel)
+ gboolean persistent, CancelPolicy cancel_policy)
{
GInputStream *in;
SoupMessage *msg;
@@ -445,7 +492,7 @@ do_sync_request (SoupSession *session, SoupRequest *request,
SoupSocket *socket = NULL;
msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
- if (cancel) {
+ if (cancel_policy == SYNC_CANCEL) {
g_signal_connect (msg, "got-headers",
G_CALLBACK (cancel_message), session);
}
@@ -456,7 +503,7 @@ do_sync_request (SoupSession *session, SoupRequest *request,
in = soup_request_send (request, NULL, &error);
g_signal_handler_disconnect (session, started_id);
- if (cancel) {
+ if (cancel_policy == SYNC_CANCEL) {
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
g_clear_error (&error);
g_object_unref (msg);
@@ -523,7 +570,7 @@ do_sync_tests_for_session (SoupSession *session, SoupURI *base_uri)
request = soup_session_request_uri (session, uri, NULL);
do_sync_request (session, request,
SOUP_STATUS_OK, response,
- TRUE, FALSE);
+ TRUE, NO_CANCEL);
g_object_unref (request);
debug_printf (1, " chunked test\n");
@@ -534,7 +581,7 @@ do_sync_tests_for_session (SoupSession *session, SoupURI *base_uri)
request = soup_session_request_uri (session, uri, NULL);
do_sync_request (session, request,
SOUP_STATUS_OK, response,
- TRUE, FALSE);
+ TRUE, NO_CANCEL);
g_object_unref (request);
debug_printf (1, " auth test\n");
@@ -545,7 +592,7 @@ do_sync_tests_for_session (SoupSession *session, SoupURI *base_uri)
request = soup_session_request_uri (session, uri, NULL);
do_sync_request (session, request,
SOUP_STATUS_UNAUTHORIZED, auth_response,
- TRUE, FALSE);
+ TRUE, NO_CANCEL);
g_object_unref (request);
debug_printf (1, " non-persistent test\n");
@@ -556,7 +603,7 @@ do_sync_tests_for_session (SoupSession *session, SoupURI *base_uri)
request = soup_session_request_uri (session, uri, NULL);
do_sync_request (session, request,
SOUP_STATUS_OK, response,
- FALSE, FALSE);
+ FALSE, NO_CANCEL);
g_object_unref (request);
debug_printf (1, " cancel test\n");
@@ -567,7 +614,7 @@ do_sync_tests_for_session (SoupSession *session, SoupURI *base_uri)
request = soup_session_request_uri (session, uri, NULL);
do_sync_request (session, request,
SOUP_STATUS_FORBIDDEN, NULL,
- TRUE, TRUE);
+ TRUE, SYNC_CANCEL);
g_object_unref (request);
soup_uri_free (uri);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]