vlc | branch: master | Rémi Denis-Courmont <[email protected]> | 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 *); _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
