[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