[vlc-devel] [PATCH v4 2/3] access: srt: Refactoring to support connection recovery

Justin Kim justin.kim at collabora.com
Mon Mar 26 09:52:07 CEST 2018


This is a refactoring code to support SRT connection recovery.
While doing this work, SRT modules support new features comparing
to the previous ones.

 - Connection Recovery
   When lost a SRT connection, this module can detect and try to re-connect.

 - Interruptible by SRT APIs
   'srt_epoll_wait' will be interrupted when all socket descriptors
   are removed from its montoring list. Fortunately, SRT modules are using
   only one socket.

 - Platform Independent
   Now, SRT modules no longer require to use a pipe. Therfore, from this version,
   SRT modules can support Win32 platforms.

Based on code from:
Roman Diouskine <rdiouskine at haivision.com>

Signed-off-by: Justin Kim <justin.kim at collabora.com>
---
 modules/access/Makefile.am |   1 +
 modules/access/srt.c       | 341 +++++++++++++++++++++++++++------------------
 2 files changed, 203 insertions(+), 139 deletions(-)

diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
index d1578be151..6067e0f0d7 100644
--- a/modules/access/Makefile.am
+++ b/modules/access/Makefile.am
@@ -427,6 +427,7 @@ EXTRA_LTLIBRARIES += libaccess_mtp_plugin.la
 ### SRT ###
 
 libaccess_srt_plugin_la_SOURCES = access/srt.c
+libaccess_srt_plugin_la_CFLAGS = $(AM_CFLAGS) $(SRT_CFLAGS)
 libaccess_srt_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(SRT_CPPFLAGS)
 libaccess_srt_plugin_la_LIBADD = $(SRT_LIBS)
 libaccess_srt_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(accessdir)'
diff --git a/modules/access/srt.c b/modules/access/srt.c
index bf770a42a5..52a5bdf0f8 100644
--- a/modules/access/srt.c
+++ b/modules/access/srt.c
@@ -1,9 +1,11 @@
 /*****************************************************************************
  * srt.c: SRT (Secure Reliable Transport) input module
  *****************************************************************************
- * Copyright (C) 2017, Collabora Ltd.
+ * Copyright (C) 2017-2018, Collabora Ltd.
+ * Copyright (C) 2018, Haivision Systems Inc.
  *
  * Authors: Justin Kim <justin.kim at collabora.com>
+ *          Roman Diouskine <rdiouskine at haivision.com>
  *
  * 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
@@ -24,9 +26,6 @@
 # include "config.h"
 #endif
 
-#include <errno.h>
-#include <fcntl.h>
-
 #include <vlc_common.h>
 #include <vlc_interrupt.h>
 #include <vlc_fs.h>
@@ -61,21 +60,27 @@ struct stream_sys_t
 {
     SRTSOCKET   sock;
     int         i_poll_id;
-    int         i_poll_timeout;
-    int         i_latency;
-    size_t      i_chunk_size;
-    int         i_pipe_fds[2];
+    vlc_mutex_t lock;
+    bool        b_interrupted;
 };
 
 static void srt_wait_interrupted(void *p_data)
 {
     stream_t *p_stream = p_data;
     stream_sys_t *p_sys = p_stream->p_sys;
-    msg_Dbg( p_stream, "Waking up srt_epoll_wait");
-    if ( write( p_sys->i_pipe_fds[1], &( bool ) { true }, sizeof( bool ) ) < 0 )
+
+    vlc_mutex_lock( &p_sys->lock );
+    if ( p_sys->i_poll_id >= 0 &&  p_sys->sock != SRT_INVALID_SOCK )
     {
-        msg_Err( p_stream, "Failed to send data to pipe");
+        p_sys->b_interrupted = true;
+
+        msg_Dbg( p_stream, "Waking up srt_epoll_wait");
+
+        /* Removing all socket descriptors from the monitoring list
+         * wakes up SRT's threads. We only have one to remove. */
+        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
     }
+    vlc_mutex_unlock( &p_sys->lock );
 }
 
 static int Control(stream_t *p_stream, int i_query, va_list args)
@@ -102,99 +107,28 @@ static int Control(stream_t *p_stream, int i_query, va_list args)
     return i_ret;
 }
 
-static block_t *BlockSRT(stream_t *p_stream, bool *restrict eof)
+static bool srt_schedule_reconnect(stream_t *p_stream)
 {
-    stream_sys_t *p_sys = p_stream->p_sys;
-
-    block_t *pkt = block_Alloc( p_sys->i_chunk_size );
-
-    if ( unlikely( pkt == NULL ) )
-    {
-        return NULL;
-    }
-
-    vlc_interrupt_register( srt_wait_interrupted, p_stream);
-
-    SRTSOCKET ready[2];
-
-    if ( srt_epoll_wait( p_sys->i_poll_id,
-        ready, &(int) { 2 }, NULL, 0, p_sys->i_poll_timeout,
-        &(int) { p_sys->i_pipe_fds[0] }, &(int) { 1 }, NULL, 0 ) == -1 )
-    {
-        int srt_err = srt_getlasterror( NULL );
-
-        /* Assuming that timeout error is normal when SRT socket is connected. */
-        if ( srt_err == SRT_ETIMEOUT && srt_getsockstate( p_sys->sock ) == SRTS_CONNECTED )
-        {
-            goto skip;
-        }
-
-        msg_Err( p_stream, "released poll wait (reason : %s)", srt_getlasterror_str() );
-        goto endofstream;
-    }
-
-    bool cancel = 0;
-    int ret = read( p_sys->i_pipe_fds[0], &cancel, sizeof( bool ) );
-    if ( ret > 0 && cancel )
-    {
-        msg_Dbg( p_stream, "Cancelled running" );
-        goto endofstream;
-    }
-
-    int stat = srt_recvmsg( p_sys->sock, (char *)pkt->p_buffer, p_sys->i_chunk_size );
-
-    if ( stat == SRT_ERROR )
-    {
-        msg_Err( p_stream, "failed to recevie SRT packet (reason: %s)", srt_getlasterror_str() );
-        goto endofstream;
-    }
-
-    pkt->i_buffer = stat;
-    vlc_interrupt_unregister();
-    return pkt;
-
-endofstream:
-    msg_Dbg( p_stream, "EOS");
-   *eof = true;
-skip:
-    block_Release(pkt);
-    srt_clearlasterror();
-    vlc_interrupt_unregister();
-
-    return NULL;
-}
+    int         i_latency;
+    int         stat;
+    char        *psz_passphrase = NULL;
+    vlc_url_t   parsed_url = { 0 };
 
-static int Open(vlc_object_t *p_this)
-{
-    stream_t     *p_stream = (stream_t*)p_this;
-    stream_sys_t *p_sys = NULL;
-    vlc_url_t     parsed_url = { 0 };
     struct addrinfo hints = {
         .ai_socktype = SOCK_DGRAM,
     }, *res = NULL;
-    int stat;
-
-    char         *psz_passphrase = NULL;
 
-    p_sys = vlc_obj_malloc( p_this, sizeof( *p_sys ) );
-    if( unlikely( p_sys == NULL ) )
-        return VLC_ENOMEM;
+    stream_sys_t *p_sys = p_stream->p_sys;
+    bool failed = false;
 
     if ( vlc_UrlParse( &parsed_url, p_stream->psz_url ) == -1 )
     {
-        msg_Err( p_stream, "Failed to parse a given URL (%s)", p_stream->psz_url );
-        goto failed;
-    }
-
-    p_sys->i_chunk_size = var_InheritInteger( p_stream, "chunk-size" );
-    p_sys->i_poll_timeout = var_InheritInteger( p_stream, "poll-timeout" );
-    p_sys->i_latency = var_InheritInteger( p_stream, "latency" );
-    p_sys->i_poll_id = -1;
-    p_stream->p_sys = p_sys;
-    p_stream->pf_block = BlockSRT;
-    p_stream->pf_control = Control;
+        msg_Err( p_stream, "Failed to parse input URL (%s)",
+            p_stream->psz_url );
 
-    psz_passphrase = var_InheritString( p_stream, "passphrase" );
+        failed = true;
+        goto out;
+    }
 
     stat = vlc_getaddrinfo( parsed_url.psz_host, parsed_url.i_port, &hints, &res );
     if ( stat )
@@ -204,91 +138,217 @@ static int Open(vlc_object_t *p_this)
                  parsed_url.i_port,
                  gai_strerror( stat ) );
 
-        goto failed;
+        failed = true;
+        goto out;
+    }
+
+    /* Always start with a fresh socket */
+    if (p_sys->sock != SRT_INVALID_SOCK)
+    {
+        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
+        srt_close( p_sys->sock );
     }
 
     p_sys->sock = srt_socket( res->ai_family, SOCK_DGRAM, 0 );
-    if ( p_sys->sock == SRT_ERROR )
+    if ( p_sys->sock == SRT_INVALID_SOCK )
     {
         msg_Err( p_stream, "Failed to open socket." );
-        goto failed;
+        failed = true;
+        goto out;
     }
 
     /* Make SRT non-blocking */
-    srt_setsockopt( p_sys->sock, 0, SRTO_SNDSYN, &(bool) { false }, sizeof( bool ) );
+    srt_setsockopt( p_sys->sock, 0, SRTO_SNDSYN,
+        &(bool) { false }, sizeof( bool ) );
+    srt_setsockopt( p_sys->sock, 0, SRTO_RCVSYN,
+        &(bool) { false }, sizeof( bool ) );
 
     /* Make sure TSBPD mode is enable (SRT mode) */
-    srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDMODE, &(int) { 1 }, sizeof( int ) );
+    srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDMODE,
+        &(int) { 1 }, sizeof( int ) );
+
+    /* This is an access module so it is always a receiver */
+    srt_setsockopt( p_sys->sock, 0, SRTO_SENDER,
+        &(int) { 0 }, sizeof( int ) );
 
     /* Set latency */
-    srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDDELAY, &p_sys->i_latency, sizeof( int ) );
+    i_latency = var_InheritInteger( p_stream, "latency" );
+    srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDDELAY,
+        &i_latency, sizeof( int ) );
 
+    psz_passphrase = var_InheritString( p_stream, "passphrase" );
     if ( psz_passphrase != NULL && psz_passphrase[0] != '\0')
     {
         int i_key_length = var_InheritInteger( p_stream, "key-length" );
-
         srt_setsockopt( p_sys->sock, 0, SRTO_PASSPHRASE,
             psz_passphrase, strlen( psz_passphrase ) );
         srt_setsockopt( p_sys->sock, 0, SRTO_PBKEYLEN,
             &i_key_length, sizeof( int ) );
     }
 
-    p_sys->i_poll_id = srt_epoll_create();
-    if ( p_sys->i_poll_id == -1 )
+    srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
+        &(int) { SRT_EPOLL_ERR | SRT_EPOLL_IN });
+
+    /* Schedule a connect */
+    msg_Dbg( p_stream, "Schedule SRT connect (dest addresss: %s, port: %d).",
+        parsed_url.psz_host, parsed_url.i_port);
+
+    stat = srt_connect( p_sys->sock, res->ai_addr, res->ai_addrlen);
+    if ( stat == SRT_ERROR )
     {
-        msg_Err( p_stream, "Failed to create poll id for SRT socket." );
-        goto failed;
+        msg_Err( p_stream, "Failed to connect to server (reason: %s)",
+                 srt_getlasterror_str() );
+        failed = true;
     }
 
-    if ( vlc_pipe( p_sys->i_pipe_fds ) != 0 )
+out:
+    if (failed && p_sys->sock != SRT_INVALID_SOCK)
     {
-        msg_Err( p_stream, "Failed to create pipe fds." );
-        goto failed;
+        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
+        srt_close(p_sys->sock);
+        p_sys->sock = SRT_INVALID_SOCK;
     }
 
-    fcntl( p_sys->i_pipe_fds[0], F_SETFL, O_NONBLOCK | fcntl( p_sys->i_pipe_fds[0], F_GETFL ) );
+    vlc_UrlClean( &parsed_url );
+    freeaddrinfo( res );
+    free( psz_passphrase );
 
-    srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock, &(int) { SRT_EPOLL_IN } );
-    srt_epoll_add_ssock( p_sys->i_poll_id, p_sys->i_pipe_fds[0], &(int) { SRT_EPOLL_IN } );
+    return !failed;
+}
 
-    stat = srt_connect( p_sys->sock, res->ai_addr, sizeof (struct sockaddr) );
+static block_t *BlockSRT(stream_t *p_stream, bool *restrict eof)
+{
+    stream_sys_t *p_sys = p_stream->p_sys;
+    int i_chunk_size = var_InheritInteger( p_stream, "chunk-size" );
+    int i_poll_timeout = var_InheritInteger( p_stream, "poll-timeout" );
 
-    if ( stat == SRT_ERROR )
+    if ( vlc_killed() )
     {
-        msg_Err( p_stream, "Failed to connect to server." );
-        goto failed;
+        /* We are told to stop. Stop. */
+        return NULL;
     }
 
-    vlc_UrlClean( &parsed_url );
-    freeaddrinfo( res );
-    free (psz_passphrase);
-
-    return VLC_SUCCESS;
+    block_t *pkt = block_Alloc( i_chunk_size );
+    if ( unlikely( pkt == NULL ) )
+    {
+        return NULL;
+    }
 
-failed:
+    vlc_interrupt_register( srt_wait_interrupted, p_stream);
 
-    if ( parsed_url.psz_host != NULL
-      && parsed_url.psz_buffer != NULL)
+    SRTSOCKET ready[1];
+    int readycnt = 1;
+    while ( srt_epoll_wait( p_sys->i_poll_id,
+        ready, &readycnt, 0, 0,
+        i_poll_timeout, NULL, 0, NULL, 0 ) >= 0)
     {
-        vlc_UrlClean( &parsed_url );
+        if ( readycnt < 0  || ready[0] != p_sys->sock )
+        {
+            /* should never happen, force recovery */
+            srt_close(p_sys->sock);
+            p_sys->sock = SRT_INVALID_SOCK;
+        }
+
+        switch( srt_getsockstate( p_sys->sock ) )
+        {
+            case SRTS_CONNECTED:
+                /* Good to go */
+                break;
+            case SRTS_BROKEN:
+            case SRTS_NONEXIST:
+            case SRTS_CLOSED:
+                /* Failed. Schedule recovery. */
+                if ( !srt_schedule_reconnect( p_stream ) )
+                    msg_Err( p_stream, "Failed to schedule connect" );
+                /* Fall-through */
+            default:
+                /* Not ready */
+                continue;
+        }
+
+        int stat = srt_recvmsg( p_sys->sock,
+            (char *)pkt->p_buffer, i_chunk_size );
+        if ( stat > 0 )
+        {
+            pkt->i_buffer = stat;
+            goto out;
+        }
+
+        msg_Err( p_stream, "failed to receive packet, set EOS (reason: %s)",
+            srt_getlasterror_str() );
+        *eof = true;
+        break;
     }
 
-    if ( res != NULL )
+    /* if the poll reports errors for any reason at all
+     * including a timeout, or there is a read error,
+     * we skip the turn.
+     */
+
+    block_Release(pkt);
+    pkt = NULL;
+
+out:
+    vlc_interrupt_unregister();
+
+    /* Re-add the socket to the poll if we were interrupted */
+    vlc_mutex_lock( &p_sys->lock );
+    if ( p_sys->b_interrupted )
     {
-        freeaddrinfo( res );
+        srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
+            &(int) { SRT_EPOLL_ERR | SRT_EPOLL_IN } );
+        p_sys->b_interrupted = false;
     }
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return pkt;
+}
+
+static int Open(vlc_object_t *p_this)
+{
+    stream_t     *p_stream = (stream_t*)p_this;
+    stream_sys_t *p_sys = NULL;
+
+    p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
+    if( unlikely( p_sys == NULL ) )
+        return VLC_ENOMEM;
+
+    srt_startup();
 
-    vlc_close( p_sys->i_pipe_fds[0] );
-    vlc_close( p_sys->i_pipe_fds[1] );
+    vlc_mutex_init( &p_sys->lock );
 
-    if ( p_sys->i_poll_id != -1 )
+    p_stream->p_sys = p_sys;
+
+    p_sys->i_poll_id = srt_epoll_create();
+    if ( p_sys->i_poll_id == -1 )
     {
-        srt_epoll_release( p_sys->i_poll_id );
-        p_sys->i_poll_id = -1;
+        msg_Err( p_stream, "Failed to create poll id for SRT socket." );
+        goto failed;
     }
-    srt_close( p_sys->sock );
 
-    free (psz_passphrase);
+    if ( !srt_schedule_reconnect( p_stream ) )
+    {
+        msg_Err( p_stream, "Failed to schedule connect");
+
+        goto failed;
+    }
+
+    p_stream->pf_block = BlockSRT;
+    p_stream->pf_control = Control;
+
+    return VLC_SUCCESS;
+
+failed:
+    vlc_mutex_destroy( &p_sys->lock );
+
+    if ( p_sys != NULL )
+    {
+        if ( p_sys->sock != -1 ) srt_close( p_sys->sock );
+        if ( p_sys->i_poll_id != -1 ) srt_epoll_release( p_sys->i_poll_id );
+
+        vlc_obj_free( p_this, p_sys );
+        p_stream->p_sys = NULL;
+    }
 
     return VLC_EGENERIC;
 }
@@ -298,16 +358,19 @@ static void Close(vlc_object_t *p_this)
     stream_t     *p_stream = (stream_t*)p_this;
     stream_sys_t *p_sys = p_stream->p_sys;
 
-    if ( p_sys->i_poll_id != -1 )
+    if ( p_sys )
     {
+        vlc_mutex_destroy( &p_sys->lock );
+
+        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
+        srt_close( p_sys->sock );
         srt_epoll_release( p_sys->i_poll_id );
-        p_sys->i_poll_id = -1;
+
+        vlc_obj_free( p_this, p_sys );
+        p_stream->p_sys = NULL;
     }
-    msg_Dbg( p_stream, "closing server" );
-    srt_close( p_sys->sock );
 
-    vlc_close( p_sys->i_pipe_fds[0] );
-    vlc_close( p_sys->i_pipe_fds[1] );
+    srt_cleanup();
 }
 
 /* Module descriptor */
-- 
2.16.2



More information about the vlc-devel mailing list