[vlc-commits] HTTP/2 output/send thread

Rémi Denis-Courmont git at videolan.org
Sun Dec 13 17:20:29 CET 2015


vlc | branch: master | Rémi Denis-Courmont <remi at remlab.net> | Mon Nov 30 22:31:21 2015 +0200| [b0da76edab4f945a2350b4958e3a9a9448cd045c] | committer: Rémi Denis-Courmont

HTTP/2 output/send thread

This is a background task to send outgoing HTTP/2 frames. This avoids
blocking other threads if the underlying TCP connection is congested.

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=b0da76edab4f945a2350b4958e3a9a9448cd045c
---

 modules/access/http/h2output.c |  284 ++++++++++++++++++++++++++++++++++++++++
 modules/access/http/h2output.h |   29 ++++
 2 files changed, 313 insertions(+)

diff --git a/modules/access/http/h2output.c b/modules/access/http/h2output.c
new file mode 100644
index 0000000..6662fe2
--- /dev/null
+++ b/modules/access/http/h2output.c
@@ -0,0 +1,284 @@
+/*****************************************************************************
+ * h2output.c: HTTP/2 send queue
+ *****************************************************************************
+ * Copyright (C) 2015 Rémi Denis-Courmont
+ *
+ * This program 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 program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <vlc_common.h>
+#include "h2frame.h"
+#include "h2output.h"
+#include "transport.h"
+
+#define VLC_H2_MAX_QUEUE (1u << 24)
+
+struct vlc_h2_queue
+{
+    struct vlc_h2_frame *first;
+    struct vlc_h2_frame **last;
+};
+
+struct vlc_h2_output
+{
+    struct vlc_tls *tls;
+
+    struct vlc_h2_queue prio;
+    struct vlc_h2_queue queue;
+    size_t size;
+    bool failed;
+
+    vlc_mutex_t lock;
+    vlc_cond_t wait;
+    vlc_thread_t thread;
+};
+
+/** Queues one outgoing HTTP/2. */
+static int vlc_h2_output_queue(struct vlc_h2_output *out,
+                               struct vlc_h2_queue *q, struct vlc_h2_frame *f)
+{
+    if (unlikely(f == NULL))
+        return -1; /* memory error */
+
+    /* Iterate the list to count size and find tail pointer */
+    struct vlc_h2_frame **lastp = &f;
+    size_t len = 0;
+
+    do
+    {
+        struct vlc_h2_frame *n = *lastp;
+
+        len += vlc_h2_frame_size(n);
+        lastp = &n->next;
+    }
+    while (*lastp != NULL);
+
+    vlc_mutex_lock(&out->lock);
+    if (out->failed)
+        goto error;
+
+    out->size += len;
+    if (out->size >= VLC_H2_MAX_QUEUE)
+    {   /* The queue is full. This should never happen but it can be triggered
+         * by an evil peer at the other end (e.g. sending a lot of pings and
+         * never receiving pongs. Returning an error is better than filling
+         * all memory. */
+        out->size -= len;
+        goto error;
+    }
+
+    assert(*(q->last) == NULL);
+    *(q->last) = f;
+    q->last = lastp;
+    vlc_cond_signal(&out->wait);
+    vlc_mutex_unlock(&out->lock);
+    return 0;
+
+error:
+    vlc_mutex_unlock(&out->lock);
+    while (f != NULL)
+    {
+        struct vlc_h2_frame *n = f->next;
+
+        free(f);
+        f = n;
+    }
+    return -1;
+}
+
+int vlc_h2_output_send_prio(struct vlc_h2_output *out, struct vlc_h2_frame *f)
+{
+    return vlc_h2_output_queue(out, &out->prio, f);
+}
+
+int vlc_h2_output_send(struct vlc_h2_output *out, struct vlc_h2_frame *f)
+{
+    return vlc_h2_output_queue(out, &out->queue, f);
+}
+
+/** Dequeues one outgoing HTTP/2. */
+static struct vlc_h2_frame *vlc_h2_output_dequeue(struct vlc_h2_output *out)
+{
+    struct vlc_h2_queue *q;
+    struct vlc_h2_frame *frame;
+    size_t len;
+
+    vlc_mutex_lock(&out->lock);
+    mutex_cleanup_push(&out->lock);
+
+    for (;;)
+    {
+        q = &out->prio;
+        if (q->first != NULL)
+            break;
+
+        q = &out->queue;
+        if (q->first != NULL)
+            break;
+
+        vlc_cond_wait(&out->wait, &out->lock);
+    }
+
+    frame = q->first;
+    q->first = frame->next;
+    if (frame->next == NULL)
+    {
+        assert(q->last == &frame->next);
+        q->last = &q->first;
+    }
+    assert(q->last != &frame->next);
+
+    len = vlc_h2_frame_size(frame);
+    assert(out->size >= len);
+    out->size -= len;
+
+    vlc_cleanup_pop();
+    vlc_mutex_unlock(&out->lock);
+
+    frame->next = NULL;
+    return frame;
+}
+
+static void vlc_h2_output_flush_unlocked(struct vlc_h2_output *out)
+{
+    for (struct vlc_h2_frame *f = out->prio.first, *n; f != NULL; f = n)
+    {
+        n = f->next;
+        free(f);
+    }
+    for (struct vlc_h2_frame *f = out->queue.first, *n; f != NULL; f = n)
+    {
+        n = f->next;
+        free(f);
+    }
+}
+
+/**
+ * Sends one HTTP/2 frame through TLS.
+ *
+ * This function sends a whole HTTP/2 frame through a TLS session, then
+ * releases the memory used by the frame.
+ *
+ * The caller must "own" the write side of the TLS session.
+ *
+ * @note This is a blocking function and may be a thread cancellation point.
+ *
+ * @return 0 on success, -1 if the connection failed
+ */
+static int vlc_h2_frame_send(struct vlc_tls *tls, struct vlc_h2_frame *f)
+{
+    size_t len = vlc_h2_frame_size(f);
+    ssize_t val;
+
+    vlc_cleanup_push(free, f);
+    val = vlc_https_send(tls, f->data, len);
+    vlc_cleanup_pop();
+    free(f);
+
+    return ((size_t)val == len) ? 0 : -1;
+}
+
+/** Output thread */
+static void *vlc_h2_output_thread(void *data)
+{
+    struct vlc_h2_output *out = data;
+    struct vlc_h2_frame *frame;
+
+    do
+    {
+        frame = vlc_h2_output_dequeue(out);
+        vlc_h2_frame_dump((vlc_object_t *)(out->tls), frame, "out");
+    }
+    while (vlc_h2_frame_send(out->tls, frame) == 0);
+
+    vlc_mutex_lock(&out->lock);
+    /* The connection can fail asynchronously. For the sake of simplicity, the
+     * caller will be notified only on the next attempt to send something. */
+    out->failed = true;
+    /* At this point, the caller will not touch the queue at all - until this
+     * thread terminates. So the lock is no longer needed here. */
+    vlc_mutex_unlock(&out->lock);
+    /* Lets not retain frames in memory useless in the mean time. */
+    vlc_h2_output_flush_unlocked(out);
+    out->prio.first = NULL;
+    out->prio.last = &out->prio.first;
+    out->queue.first = NULL;
+    out->queue.last = &out->queue.first;
+
+    return NULL;
+}
+
+static void *vlc_h2_client_output_thread(void *data)
+{
+    static const char http2_hello[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+    struct vlc_h2_output *out = data;
+
+    if (vlc_https_send(out->tls, http2_hello, 24) < 24)
+    {
+        vlc_mutex_lock(&out->lock);
+        out->failed = true;
+        vlc_mutex_unlock(&out->lock);
+        return NULL;
+    }
+
+    return vlc_h2_output_thread(data);
+}
+
+struct vlc_h2_output *vlc_h2_output_create(struct vlc_tls *tls, bool client)
+{
+    struct vlc_h2_output *out = malloc(sizeof (*out));
+    if (unlikely(out == NULL))
+        return NULL;
+
+    out->tls = tls;
+
+    out->prio.first = NULL;
+    out->prio.last = &out->prio.first;
+    out->queue.first = NULL;
+    out->queue.last = &out->queue.first;
+    out->size = 0;
+    out->failed = false;
+
+    vlc_mutex_init(&out->lock);
+    vlc_cond_init(&out->wait);
+
+    void *(*cb)(void *) = client ? vlc_h2_client_output_thread
+                                 : vlc_h2_output_thread;
+    if (vlc_clone(&out->thread, cb, out, VLC_THREAD_PRIORITY_INPUT))
+    {
+        vlc_cond_destroy(&out->wait);
+        vlc_mutex_destroy(&out->lock);
+        free(out);
+        out = NULL;
+    }
+    return out;
+}
+
+void vlc_h2_output_destroy(struct vlc_h2_output *out)
+{
+    vlc_cancel(out->thread);
+    vlc_join(out->thread, NULL);
+
+    vlc_cond_destroy(&out->wait);
+    vlc_mutex_destroy(&out->lock);
+    vlc_h2_output_flush_unlocked(out);
+    free(out);
+}
diff --git a/modules/access/http/h2output.h b/modules/access/http/h2output.h
new file mode 100644
index 0000000..9af2f59
--- /dev/null
+++ b/modules/access/http/h2output.h
@@ -0,0 +1,29 @@
+/*****************************************************************************
+ * h2output.h: HTTP/2 send queue declarations
+ *****************************************************************************
+ * Copyright (C) 2015 Rémi Denis-Courmont
+ *
+ * This program 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 program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+struct vlc_h2_output;
+struct vlc_h2_frame;
+struct vlc_tls;
+
+int vlc_h2_output_send_prio(struct vlc_h2_output *, struct vlc_h2_frame *);
+int vlc_h2_output_send(struct vlc_h2_output *, struct vlc_h2_frame *);
+
+struct vlc_h2_output *vlc_h2_output_create(struct vlc_tls *, bool client);
+void vlc_h2_output_destroy(struct vlc_h2_output *);



More information about the vlc-commits mailing list