[libsoup] http2: send goaway frame before closing the connection



commit 273f13296ba5ec4a0d64541fedc0bd3302977c7d
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Tue May 25 15:20:20 2021 +0200

    http2: send goaway frame before closing the connection

 libsoup/http1/soup-client-message-io-http1.c |  9 +++
 libsoup/http2/soup-client-message-io-http2.c | 95 ++++++++++++++++++++++++++--
 libsoup/soup-client-message-io.c             |  8 +++
 libsoup/soup-client-message-io.h             |  6 ++
 libsoup/soup-connection.c                    | 51 ++++++++++-----
 5 files changed, 147 insertions(+), 22 deletions(-)
---
diff --git a/libsoup/http1/soup-client-message-io-http1.c b/libsoup/http1/soup-client-message-io-http1.c
index 627348c5..aab8e848 100644
--- a/libsoup/http1/soup-client-message-io-http1.c
+++ b/libsoup/http1/soup-client-message-io-http1.c
@@ -954,6 +954,14 @@ soup_client_message_io_http1_run_until_read_async (SoupClientMessageIO *iface,
         io_run_until_read_async (io, task);
 }
 
+static gboolean
+soup_client_message_io_http1_close_async (SoupClientMessageIO *io,
+                                          SoupConnection      *conn,
+                                          GAsyncReadyCallback  callback)
+{
+        return FALSE;
+}
+
 static gboolean
 soup_client_message_io_http1_skip (SoupClientMessageIO *iface,
                                    SoupMessage         *msg,
@@ -1132,6 +1140,7 @@ static const SoupClientMessageIOFuncs io_funcs = {
         soup_client_message_io_http1_run,
         soup_client_message_io_http1_run_until_read,
         soup_client_message_io_http1_run_until_read_async,
+        soup_client_message_io_http1_close_async,
         soup_client_message_io_http1_skip,
         soup_client_message_io_http1_is_open,
         soup_client_message_io_http1_in_progress,
diff --git a/libsoup/http2/soup-client-message-io-http2.c b/libsoup/http2/soup-client-message-io-http2.c
index 3f382afb..4916ed4b 100644
--- a/libsoup/http2/soup-client-message-io-http2.c
+++ b/libsoup/http2/soup-client-message-io-http2.c
@@ -81,6 +81,10 @@ typedef struct {
         GSource *reset_stream_source;
         GSource *idle_read_source;
         gboolean is_shutdown;
+
+        GTask *close_task;
+        GSource *close_source;
+        gboolean goaway_sent;
 } SoupClientMessageIOHTTP2;
 
 typedef struct {
@@ -119,6 +123,7 @@ typedef struct {
 static gboolean io_read (SoupClientMessageIOHTTP2 *, gboolean, GCancellable *, GError **);
 static void io_write_until_stream_reset_is_sent (SoupHTTP2MessageData *data);
 static void io_idle_read (SoupClientMessageIOHTTP2 *io);
+static void io_close (SoupClientMessageIOHTTP2 *io);
 
 static void
 NGCHECK (int return_code)
@@ -270,6 +275,20 @@ advance_state_from (SoupHTTP2MessageData *data,
         data->state = to;
 }
 
+static void
+soup_client_message_io_http2_terminate_session (SoupClientMessageIOHTTP2 *io)
+{
+        if (io->goaway_sent)
+                return;
+
+        if (g_hash_table_size (io->messages) != 0)
+                return;
+
+        io->goaway_sent = TRUE;
+        NGCHECK (nghttp2_session_terminate_session (io->session, NGHTTP2_NO_ERROR));
+        io_close (io);
+}
+
 /* HTTP2 read callbacks */
 
 static int
@@ -402,6 +421,7 @@ on_frame_recv_callback (nghttp2_session     *session,
                           frame->goaway.opaque_data ? (char *)frame->goaway.opaque_data : "");
                 handle_goaway (io, frame->goaway.error_code, frame->goaway.last_stream_id);
                 io->is_shutdown = TRUE;
+                soup_client_message_io_http2_terminate_session (io);
                 return 0;
         }
 
@@ -951,9 +971,6 @@ soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
 
         h2_debug (io, data, "Finished: %s", completion == SOUP_MESSAGE_IO_COMPLETE ? "completed" : 
"interrupted");
 
-        // ret = nghttp2_session_terminate_session (io->session, NGHTTP2_NO_ERROR);
-        // g_assert (ret == 0);
-
        completion_cb = data->completion_cb;
        completion_data = data->completion_data;
 
@@ -980,8 +997,12 @@ soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
 
        g_object_unref (msg);
 
-        if (!io->is_shutdown)
-                io_write_until_stream_reset_is_sent (data);
+        if (io->is_shutdown) {
+                soup_client_message_io_http2_terminate_session (io);
+                return;
+        }
+
+        io_write_until_stream_reset_is_sent (data);
         if (g_hash_table_size (io->messages) == 0)
                 io_idle_read (io);
 }
@@ -1499,6 +1520,64 @@ soup_client_message_io_http2_run_until_read_async (SoupClientMessageIO *iface,
         io_run_until_read_async (msg, task);
 }
 
+static gboolean
+io_close_ready (GObject                  *stream,
+                SoupClientMessageIOHTTP2 *io)
+{
+        io_close (io);
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+io_close (SoupClientMessageIOHTTP2 *io)
+{
+        GError *error = NULL;
+
+        if (io->close_source) {
+                g_source_destroy (io->close_source);
+                g_clear_pointer (&io->close_source, g_source_unref);
+        }
+
+        while (nghttp2_session_want_write (io->session) && !error)
+                io_write (io, FALSE, FALSE, &error);
+
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                g_error_free (error);
+                io->close_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM 
(io->ostream), NULL);
+                g_source_set_callback (io->close_source, (GSourceFunc)io_close_ready, io, NULL);
+                g_source_attach (io->close_source, g_main_context_get_thread_default ());
+                return;
+        }
+
+        if (io->close_task) {
+                if (error)
+                        g_task_return_error (io->close_task, error);
+                else
+                        g_task_return_boolean (io->close_task, TRUE);
+                g_clear_object (&io->close_task);
+                return;
+        }
+
+        g_clear_error (&error);
+}
+
+static gboolean
+soup_client_message_io_http2_close_async (SoupClientMessageIO *iface,
+                                          SoupConnection      *conn,
+                                          GAsyncReadyCallback  callback)
+{
+        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
+
+        if (io->goaway_sent && !io->close_source)
+                return FALSE;
+
+        g_assert (!io->close_task);
+        io->close_task = g_task_new (conn, NULL, callback, NULL);
+        soup_client_message_io_http2_terminate_session (io);
+        return TRUE;
+}
+
 static void
 soup_client_message_io_http2_destroy (SoupClientMessageIO *iface)
 {
@@ -1512,7 +1591,12 @@ soup_client_message_io_http2_destroy (SoupClientMessageIO *iface)
                 g_source_destroy (io->idle_read_source);
                 g_clear_pointer (&io->idle_read_source, g_source_unref);
         }
+        if (io->close_source) {
+                g_source_destroy (io->close_source);
+                g_source_unref (io->close_source);
+        }
         g_clear_object (&io->stream);
+        g_clear_object (&io->close_task);
         g_clear_pointer (&io->async_context, g_main_context_unref);
         g_clear_pointer (&io->session, nghttp2_session_del);
         g_clear_pointer (&io->messages, g_hash_table_unref);
@@ -1533,6 +1617,7 @@ static const SoupClientMessageIOFuncs io_funcs = {
         soup_client_message_io_http2_run,
         soup_client_message_io_http2_run_until_read,
         soup_client_message_io_http2_run_until_read_async,
+        soup_client_message_io_http2_close_async,
         soup_client_message_io_http2_skip,
         soup_client_message_io_http2_is_open,
         soup_client_message_io_http2_in_progress,
diff --git a/libsoup/soup-client-message-io.c b/libsoup/soup-client-message-io.c
index c9c78450..8ec93f3d 100644
--- a/libsoup/soup-client-message-io.c
+++ b/libsoup/soup-client-message-io.c
@@ -89,6 +89,14 @@ soup_client_message_io_run_until_read_async (SoupClientMessageIO *io,
         io->funcs->run_until_read_async (io, msg, io_priority, cancellable, callback, user_data);
 }
 
+gboolean
+soup_client_message_io_close_async (SoupClientMessageIO *io,
+                                    SoupConnection      *conn,
+                                    GAsyncReadyCallback  callback)
+{
+        return io->funcs->close_async (io, conn, callback);
+}
+
 gboolean
 soup_client_message_io_skip (SoupClientMessageIO *io,
                              SoupMessage         *msg,
diff --git a/libsoup/soup-client-message-io.h b/libsoup/soup-client-message-io.h
index 2a4d3b38..ddf8b249 100644
--- a/libsoup/soup-client-message-io.h
+++ b/libsoup/soup-client-message-io.h
@@ -41,6 +41,9 @@ typedef struct {
                                                GCancellable              *cancellable,
                                                GAsyncReadyCallback        callback,
                                                gpointer                   user_data);
+        gboolean      (*close_async)          (SoupClientMessageIO       *io,
+                                               SoupConnection            *conn,
+                                               GAsyncReadyCallback        callback);
         gboolean      (*skip)                 (SoupClientMessageIO       *io,
                                                SoupMessage               *msg,
                                                gboolean                   blocking,
@@ -83,6 +86,9 @@ void          soup_client_message_io_run_until_read_async (SoupClientMessageIO
                                                            GCancellable              *cancellable,
                                                            GAsyncReadyCallback        callback,
                                                            gpointer                   user_data);
+gboolean      soup_client_message_io_close_async          (SoupClientMessageIO       *io,
+                                                           SoupConnection            *conn,
+                                                           GAsyncReadyCallback        callback);
 gboolean      soup_client_message_io_skip                 (SoupClientMessageIO       *io,
                                                            SoupMessage               *msg,
                                                            gboolean                   blocking,
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 5c2f0040..6ead5709 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -888,6 +888,33 @@ soup_connection_tunnel_handshake (SoupConnection *conn,
         return TRUE;
 }
 
+static void
+soup_connection_disconnected (SoupConnection *conn)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        if (priv->connection) {
+                GIOStream *connection;
+
+                connection = priv->connection;
+                priv->connection = NULL;
+
+                g_io_stream_close (connection, NULL, NULL);
+                g_signal_handlers_disconnect_by_data (connection, conn);
+                g_object_unref (connection);
+        }
+
+        g_signal_emit (conn, signals[DISCONNECTED], 0);
+}
+
+static void
+client_message_io_closed_cb (SoupConnection *conn,
+                             GAsyncResult   *result)
+{
+        g_task_propagate_boolean (G_TASK (result), NULL);
+        soup_connection_disconnected (conn);
+}
+
 /**
  * soup_connection_disconnect:
  * @conn: a connection
@@ -898,13 +925,11 @@ soup_connection_tunnel_handshake (SoupConnection *conn,
 void
 soup_connection_disconnect (SoupConnection *conn)
 {
-        SoupConnectionPrivate *priv;
-        SoupConnectionState old_state;
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
 
-        g_return_if_fail (SOUP_IS_CONNECTION (conn));
-        priv = soup_connection_get_instance_private (conn);
+        if (priv->state == SOUP_CONNECTION_DISCONNECTED)
+                return;
 
-        old_state = priv->state;
         soup_connection_set_state (conn, SOUP_CONNECTION_DISCONNECTED);
 
         if (priv->cancellable) {
@@ -912,19 +937,11 @@ soup_connection_disconnect (SoupConnection *conn)
                 priv->cancellable = NULL;
         }
 
-        if (priv->connection) {
-                GIOStream *connection;
-
-                connection = priv->connection;
-                priv->connection = NULL;
-
-                g_io_stream_close (connection, NULL, NULL);
-                g_signal_handlers_disconnect_by_data (connection, conn);
-                g_object_unref (connection);
-        }
+        if (priv->io_data &&
+            soup_client_message_io_close_async (priv->io_data, conn, 
(GAsyncReadyCallback)client_message_io_closed_cb))
+                return;
 
-        if (old_state != SOUP_CONNECTION_DISCONNECTED)
-                g_signal_emit (conn, signals[DISCONNECTED], 0);
+        soup_connection_disconnected (conn);
 }
 
 GSocket *


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]