[FFmpeg-devel] [PATCH 1/1] avformat/tcp: support timeout for getaddrinfo For fixing stucking when resolving addrinfo

Xinzheng Zhang zhangxzheng at gmail.com
Tue May 31 13:05:40 CEST 2016


From: xinzhengzhang <zhangxzheng at gmail.com>

---
 libavformat/tcp.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 194 insertions(+)

diff --git a/libavformat/tcp.c b/libavformat/tcp.c
index 4ac061a..dc3e0bd 100644
--- a/libavformat/tcp.c
+++ b/libavformat/tcp.c
@@ -32,11 +32,15 @@
 #if HAVE_POLL_H
 #include <poll.h>
 #endif
+#if HAVE_PTHREADS
+#include <pthread.h>
+#endif
 
 typedef struct TCPContext {
     const AVClass *class;
     int fd;
     int listen;
+    int addrinfo_timeout;
     int open_timeout;
     int rw_timeout;
     int listen_timeout;
@@ -52,6 +56,7 @@ static const AVOption options[] = {
     { "listen",          "Listen for incoming connections",  OFFSET(listen),         AV_OPT_TYPE_INT, { .i64 = 0 },     0,       2,       .flags = D|E },
     { "timeout",     "set timeout (in microseconds) of socket I/O operations", OFFSET(rw_timeout),     AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
     { "listen_timeout",  "Connection awaiting timeout (in milliseconds)",      OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
+    { "addrinfo_timeout", "set timeout (in microseconds) for getaddrinfo()",   OFFSET(addrinfo_timeout), AV_OPT_TYPE_INT, { .i64 = -1 },       -1, INT_MAX, .flags = D|E },
     { "send_buffer_size", "Socket send buffer size (in bytes)",                OFFSET(send_buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
     { "recv_buffer_size", "Socket receive buffer size (in bytes)",             OFFSET(recv_buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
     { "ijkapplication",  "AVApplicationContext",             OFFSET(app_ctx),        AV_OPT_TYPE_INT64, { .i64 = 0 },   INT64_MIN, INT64_MAX, .flags = D },
@@ -65,6 +70,189 @@ static const AVClass tcp_class = {
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
+#ifdef HAVE_PTHREADS
+
+typedef struct TCPAddrinfoRequest
+{
+    AVBufferRef *buffer;
+
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+
+    int64_t timeout;    // in microseconds;
+    AVIOInterruptCB interrupt_callback;
+
+    char            *hostname;
+    char            *servname;
+    struct addrinfo  hints;
+    struct addrinfo *res;
+
+    volatile int     finished;
+    int              ret;
+} TCPAddrinfoRequest;
+
+static void tcp_getaddrinfo_request_free(TCPAddrinfoRequest *req)
+{
+    freeaddrinfo(req->res);
+
+    av_freep(&req->servname);
+    av_freep(&req->hostname);
+    pthread_cond_destroy(&req->cond);
+    pthread_mutex_destroy(&req->mutex);
+    av_freep(&req);
+}
+
+static void tcp_getaddrinfo_request_free_buffer(void *opaque, uint8_t *data)
+{
+    TCPAddrinfoRequest *req = (TCPAddrinfoRequest *)opaque;
+    tcp_getaddrinfo_request_free(req);
+}
+
+static int tcp_getaddrinfo_request_create(TCPAddrinfoRequest **request,
+                                          const char *hostname,
+                                          const char *servname,
+                                          const struct addrinfo *hints,
+                                          int64_t timeout,
+                                          const AVIOInterruptCB *int_cb)
+{
+    TCPAddrinfoRequest *req = (TCPAddrinfoRequest *) av_mallocz(sizeof(TCPAddrinfoRequest));
+    if (!req)
+        return AVERROR(ENOMEM);
+
+    if (pthread_mutex_init(&req->mutex, NULL)) {
+        av_freep(&req);
+        return AVERROR(ENOMEM);
+    }
+
+    if (pthread_cond_init(&req->cond, NULL)) {
+        pthread_mutex_destroy(&req->mutex);
+        av_freep(&req);
+        return AVERROR(ENOMEM);
+    }
+
+    req->timeout            = timeout;
+    req->interrupt_callback = *int_cb;
+
+    if (hostname && *hostname) {
+        req->hostname = av_strdup(hostname);
+        if (!req->hostname)
+            goto fail;
+    }
+
+    if (servname) {
+        req->servname = av_strdup(servname);
+        if (!req->hostname)
+            goto fail;
+    }
+
+    if (hints) {
+        req->hints.ai_family   = hints->ai_family;
+        req->hints.ai_socktype = hints->ai_socktype;
+        req->hints.ai_protocol = hints->ai_protocol;
+        req->hints.ai_flags    = hints->ai_flags;
+    }
+
+    req->buffer = av_buffer_create(NULL, 0, tcp_getaddrinfo_request_free_buffer, req, 0);
+    if (!req->buffer)
+        goto fail;
+
+    *request = req;
+    return 0;
+fail:
+    tcp_getaddrinfo_request_free(req);
+    return AVERROR(ENOMEM);
+}
+
+static void *tcp_getaddrinfo_worker(void *arg)
+{
+    TCPAddrinfoRequest *req = arg;
+
+    req->ret = getaddrinfo(req->hostname, req->servname, &req->hints, &req->res);
+
+    pthread_mutex_lock(&req->mutex);
+    req->finished = 1;
+    pthread_cond_signal(&req->cond);
+    pthread_mutex_unlock(&req->mutex);
+
+    av_buffer_unref(&req->buffer);
+    return NULL;
+}
+
+static int tcp_getaddrinfo_nonblock(const char *hostname, const char *servname,
+                                 const struct addrinfo *hints, struct addrinfo **res,
+                                 int64_t timeout,
+                                 const AVIOInterruptCB *int_cb)
+{
+    int     ret;
+    int64_t start;
+    int64_t now;
+    AVBufferRef        *req_ref = NULL;
+    TCPAddrinfoRequest *req     = NULL;
+    pthread_t work_thread;
+
+    if (hostname && !hostname[0])
+        hostname = NULL;
+
+    if (timeout <= 0)
+        return getaddrinfo(hostname, servname, hints, res);
+
+    ret = tcp_getaddrinfo_request_create(&req, hostname, servname, hints, timeout, int_cb);
+    if (ret)
+        goto fail;
+
+    req_ref = av_buffer_ref(req->buffer);
+    if (ret)
+        goto fail;
+
+    /* FIXME: using a thread pool would be better. */
+    ret = pthread_create(&work_thread, NULL, tcp_getaddrinfo_worker, req);
+    if (ret) {
+        ret = AVERROR(ret);
+        goto fail;
+    }
+
+    pthread_detach(work_thread);
+
+    start = av_gettime();
+    now   = start;
+
+    pthread_mutex_lock(&req->mutex);
+    while (!req->finished && start + timeout > now) {
+        int64_t wait_time = now + 100000;
+        struct timespec tv = { .tv_sec  =  wait_time / 1000000,
+                               .tv_nsec = (wait_time % 1000000) * 1000 };
+#if HAVE_PTHREAD_COND_TIMEWAIT_MONOTONIC_NP
+        ret = pthread_cond_timedwait_monotonic_np(&req->cond, &req->mutex, &tv);
+else
+        ret = pthread_cond_timedwait(&req->cond, &req->mutex, &tv);
+#endif
+        if (ret == 0 || ret != ETIMEDOUT)
+            break;
+
+        if (ff_check_interrupt(&req->interrupt_callback))
+            return AVERROR_EXIT;
+
+        now = av_gettime();
+    }
+    pthread_mutex_unlock(&req->mutex);
+
+    if (!req->finished && ret != 0) {
+        ret = AVERROR(ret);
+        goto fail;
+    }
+
+    ret      = req->ret;
+    *res     = req->res;
+    req->res = NULL;
+    av_buffer_unref(&req_ref);
+    return ret;
+fail:
+    av_buffer_unref(&req_ref);
+    return ret;
+}
+
+#endif
+
 /* return non zero if error */
 static int tcp_open(URLContext *h, const char *uri, int flags)
 {
@@ -111,10 +299,16 @@ static int tcp_open(URLContext *h, const char *uri, int flags)
     snprintf(portstr, sizeof(portstr), "%d", port);
     if (s->listen)
         hints.ai_flags |= AI_PASSIVE;
+#ifdef HAVE_PTHREADS
+    ret = tcp_getaddrinfo_nonblock(hostname, portstr, &hints, &ai, s->addrinfo_timeout, &h->interrupt_callback);
+#else
+    if (s->addrinfo_timeout > 0)
+        av_log(h, AV_LOG_WARNING, "Ignore addrinfo_timeout without pthreads support.\n")
     if (!hostname[0])
         ret = getaddrinfo(NULL, portstr, &hints, &ai);
     else
         ret = getaddrinfo(hostname, portstr, &hints, &ai);
+#endif
     if (ret) {
         av_log(h, AV_LOG_ERROR,
                "Failed to resolve hostname %s: %s\n",
-- 
2.6.4 (Apple Git-63)



More information about the ffmpeg-devel mailing list