diff mbox series

[FFmpeg-devel] http: support retry on connection error

Message ID AM0PR04MB6467B92899C0EDF919C4447DF5180@AM0PR04MB6467.eurprd04.prod.outlook.com
State New
Headers show
Series [FFmpeg-devel] http: support retry on connection error | expand

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Eran Kornblau Oct. 25, 2020, 1:40 p.m. UTC
Hi,

This patch adds 2 options to http:
- reconnect_on_status - a list of http status codes that should be retried. the list can contain explicit status codes or the strings 4xx/5xx.
- reconnect_on_err - reconnects on arbitrary errors during connect, e.g. ECONNRESET/ETIMEDOUT.

The retry employs the same exponential backoff logic as the existing reconnect/reconnect_at_eof flags.

Related tickets:
https://trac.ffmpeg.org/ticket/6066
https://trac.ffmpeg.org/ticket/7768

Thanks!

Eran
Subject: [PATCH] http: support retry on connection error

added 2 new options:
- reconnect_on_status - a list of http status codes that should be
    retried. the list can contain explicit status codes / the strings
    4xx/5xx.
- reconnect_on_err - reconnects on arbitrary errors during connect, e.g.
    ECONNRESET/ETIMEDOUT

the retry employs the same exponential backoff logic as the existing
reconnect/reconnect_at_eof flags.

related tickets:
https://trac.ffmpeg.org/ticket/6066
https://trac.ffmpeg.org/ticket/7768
---
 libavformat/http.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 60 insertions(+), 2 deletions(-)

Comments

Eran Kornblau Nov. 2, 2020, 6:49 a.m. UTC | #1
Ping

Thanks,
Eran

From: Eran Kornblau
Sent: Sunday, October 25, 2020 3:40 PM
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Subject: [PATCH] http: support retry on connection error

Hi,

This patch adds 2 options to http:
- reconnect_on_status - a list of http status codes that should be retried. the list can contain explicit status codes or the strings 4xx/5xx.
- reconnect_on_err - reconnects on arbitrary errors during connect, e.g. ECONNRESET/ETIMEDOUT.

The retry employs the same exponential backoff logic as the existing reconnect/reconnect_at_eof flags.

Related tickets:
https://trac.ffmpeg.org/ticket/6066
https://trac.ffmpeg.org/ticket/7768

Thanks!

Eran
Eran Kornblau Nov. 8, 2020, 6:58 a.m. UTC | #2
Pinging again...

Thanks,
Eran

From: Eran Kornblau
Sent: Sunday, October 25, 2020 3:40 PM
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org<mailto:ffmpeg-devel@ffmpeg.org>>
Subject: [PATCH] http: support retry on connection error

Hi,

This patch adds 2 options to http:
- reconnect_on_status - a list of http status codes that should be retried. the list can contain explicit status codes or the strings 4xx/5xx.
- reconnect_on_err - reconnects on arbitrary errors during connect, e.g. ECONNRESET/ETIMEDOUT.

The retry employs the same exponential backoff logic as the existing reconnect/reconnect_at_eof flags.

Related tickets:
https://trac.ffmpeg.org/ticket/6066
https://trac.ffmpeg.org/ticket/7768

Thanks!

Eran
Eran Kornblau Nov. 17, 2020, 8:52 p.m. UTC | #3
Another ping... would be great to have some feedback on this...

Eran

From: Eran Kornblau
Sent: Sunday, November 8, 2020 8:59 AM
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Subject: RE: [PATCH] http: support retry on connection error

Pinging again...

Thanks,
Eran

From: Eran Kornblau
Sent: Sunday, October 25, 2020 3:40 PM
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org<mailto:ffmpeg-devel@ffmpeg.org>>
Subject: [PATCH] http: support retry on connection error

Hi,

This patch adds 2 options to http:
- reconnect_on_status - a list of http status codes that should be retried. the list can contain explicit status codes or the strings 4xx/5xx.
- reconnect_on_err - reconnects on arbitrary errors during connect, e.g. ECONNRESET/ETIMEDOUT.

The retry employs the same exponential backoff logic as the existing reconnect/reconnect_at_eof flags.

Related tickets:
https://trac.ffmpeg.org/ticket/6066
https://trac.ffmpeg.org/ticket/7768

Thanks!

Eran
Marton Balint Nov. 18, 2020, 8:46 p.m. UTC | #4
On Tue, 17 Nov 2020, Eran Kornblau wrote:

> Hi,
>
> This patch adds 2 options to http:
> - reconnect_on_status - a list of http status codes that should be retried. the list can contain explicit status codes or the strings 4xx/5xx.
> - reconnect_on_err - reconnects on arbitrary errors during connect, e.g. ECONNRESET/ETIMEDOUT.
>
> The retry employs the same exponential backoff logic as the existing reconnect/reconnect_at_eof flags.
>
> Related tickets:
> https://trac.ffmpeg.org/ticket/6066
> https://trac.ffmpeg.org/ticket/7768

> From f412ca316f26f8c52d9763a33703ab06134feb37 Mon Sep 17 00:00:00 2001
> From: erankor <eran.kornblau@kaltura.com>
> Date: Sun, 25 Oct 2020 15:25:13 +0200
> Subject: [PATCH] http: support retry on connection error
> 
> added 2 new options:
> - reconnect_on_status - a list of http status codes that should be
>     retried. the list can contain explicit status codes / the strings
>     4xx/5xx.

Maybe better name this option reconnect_on_http_error. Also this is a 
flags-like option, so use AV_OPT_TYPE_FLAGS with 4xx and 5xx as flags.


> - reconnect_on_err - reconnects on arbitrary errors during connect, e.g.
>     ECONNRESET/ETIMEDOUT

Maybe reconnect_on_network_error better reflects that this reconnects on 
underlying protocol (TCP/TLS) error.

Anyhow, the new options should be added to docs/protocols.texi.

> 
> the retry employs the same exponential backoff logic as the existing
> reconnect/reconnect_at_eof flags.
> 
> related tickets:
> https://trac.ffmpeg.org/ticket/6066
> https://trac.ffmpeg.org/ticket/7768
> ---
>  libavformat/http.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 60 insertions(+), 2 deletions(-)
> 
> diff --git a/libavformat/http.c b/libavformat/http.c
> index 3d25d652d3..ea14ef0c47 100644
> --- a/libavformat/http.c
> +++ b/libavformat/http.c
> @@ -119,8 +119,10 @@ typedef struct HTTPContext {
>      char *method;
>      int reconnect;
>      int reconnect_at_eof;
> +    int reconnect_on_err;
>      int reconnect_streamed;
>      int reconnect_delay_max;
> +    char *reconnect_on_status;
>      int listen;
>      char *resource;
>      int reply_code;
> @@ -164,6 +166,8 @@ static const AVOption options[] = {
>      { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
>      { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
>      { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
> +    { "reconnect_on_err", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_err), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
> +    { "reconnect_on_status", "list of http status codes to reconnect on. the list can include specific status codes / 4xx / 5xx", OFFSET(reconnect_on_status), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },

AV_OPT_TYPE_FLAGS, as I explained above.

>      { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
>      { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D },
>      { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E },
> @@ -258,21 +262,75 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
>      return location_changed;
>  }
> 
> +static int http_should_reconnect(HTTPContext *s, int err)
> +{
> +    const char *status_group;
> +    char http_code[4];
> +
> +    switch (err) {
> +    case AVERROR_HTTP_BAD_REQUEST:
> +    case AVERROR_HTTP_UNAUTHORIZED:
> +    case AVERROR_HTTP_FORBIDDEN:
> +    case AVERROR_HTTP_NOT_FOUND:
> +    case AVERROR_HTTP_OTHER_4XX:
> +        status_group = "4xx";
> +        break;
> +
> +    case AVERROR_HTTP_SERVER_ERROR:
> +        status_group = "5xx";
> +        break;
> +
> +    default:
> +        return s->reconnect_on_err;
> +    }
> +
> +    if (!s->reconnect_on_status) {
> +        return 0;
> +    }
> +
> +    if (av_match_list(status_group, s->reconnect_on_status, ',') > 0) {
> +        return 1;
> +    }
> +
> +    snprintf(http_code, sizeof(http_code), "%d", s->http_code);
> +
> +    return av_match_list(http_code, s->reconnect_on_status, ',') > 0;
> +}
> +
>  /* return non zero if error */
>  static int http_open_cnx(URLContext *h, AVDictionary **options)
>  {
>      HTTPAuthType cur_auth_type, cur_proxy_auth_type;
>      HTTPContext *s = h->priv_data;
>      int location_changed, attempts = 0, redirects = 0;
> +    int reconnect_delay = 0;
> +    uint64_t off;
> +
>  redo:
>      av_dict_copy(options, s->chained_options, 0);
>
>      cur_auth_type       = s->auth_state.auth_type;
>      cur_proxy_auth_type = s->auth_state.auth_type;
> 
> +    off = s->off;
>      location_changed = http_open_cnx_internal(h, options);

Are you sure you get AVERROR_HTTP_* here? Also if you follow the code 
there is special handling of 401/407, that should be done first before 
your checks, no?

> -    if (location_changed < 0)
> -        goto fail;
> +    if (location_changed < 0) {
> +        if (!http_should_reconnect(s, location_changed) ||
> +            reconnect_delay > s->reconnect_delay_max)
> +            goto fail;
> +
> +        av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay);
> +        location_changed = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback);
> +        if (location_changed != AVERROR(ETIMEDOUT))
> +            goto fail;
> +        reconnect_delay = 1 + 2 * reconnect_delay;
> +
> +        /* restore the offset (http_connect resets it) */
> +        s->off = off;
> +
> +        ffurl_closep(&s->hd);
> +        goto redo;
> +    }
>
>      attempts++;
>      if (s->http_code == 401) {

Another comment is that if you want to reconnect based on errors 
classified as 4xx or 5xx, then probably you should revisit 
ff_http_averror(400, xxx) calls and check if they are indeed caused by 
client behaviour. Because if the server is violating the protocol, then 
they should be ff_http_averror(500, xxx) IMHO.

Regards,
Marton
Eran Kornblau Dec. 3, 2020, 8:52 a.m. UTC | #5
Hi Marton,

Thank you for the feedback, and sorry for my late reply :)
Please see my responses inline, updated patch attached.

Thanks
Eran

> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of Marton Balint
> Sent: Wednesday, November 18, 2020 10:46 PM
> To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH] http: support retry on connection error
> 
> 
> > - reconnect_on_status - a list of http status codes that should be
> >     retried. the list can contain explicit status codes / the strings
> >     4xx/5xx.
> 
> Maybe better name this option reconnect_on_http_error. Also this is a flags-like option, so use AV_OPT_TYPE_FLAGS with 4xx and 5xx as flags.
> 
Rename - done.

Flags - the patch supports both explicit status codes (e.g. 503) and status groups (e.g. 4xx).
For example, it is possible to specify -reconnect_on_http_error '4xx,503'.

In my understanding, AV_OPT_TYPE_FLAGS is a simple int, so the only to support what I wrote above, is to assign bits
to specific status codes, and I don't think we want to go there... (because every time someone will want to retry on a new status code,
we will need to update the code)

> 
> > - reconnect_on_err - reconnects on arbitrary errors during connect, e.g.
> >     ECONNRESET/ETIMEDOUT
> 
> Maybe reconnect_on_network_error better reflects that this reconnects on 
> underlying protocol (TCP/TLS) error.
> 
> Anyhow, the new options should be added to docs/protocols.texi.
> 
Rename & docs - done.

> > 
> > +    { "reconnect_on_err", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_err), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
> > +    { "reconnect_on_status", "list of http status codes to reconnect on. the list can include specific status codes / 4xx / 5xx", OFFSET(reconnect_on_status), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
> 
> AV_OPT_TYPE_FLAGS, as I explained above.
> 
Replied above

> > 
> >      location_changed = http_open_cnx_internal(h, options);
> 
> Are you sure you get AVERROR_HTTP_* here? Also if you follow the code 
> there is special handling of 401/407, that should be done first before 
> your checks, no?
> 
1. Yes, I tested the change before submitting, used some test PHP server to simulate errors.
    This is the call stack that propagates the AVERROR_HTTP_* codes -
    http_open_cnx_internal
    http_connect
    http_read_header
    process_line
    check_http_code

2. In case of 401/407, check_http_code returns 0, and therefore http_open_cnx_internal return 0 as well (assuming there is no other error...). 
    So, it doesn't enter 'if (location_changed < 0)' and it behaves the same way it did before my changes.

> 
> Another comment is that if you want to reconnect based on errors 
> classified as 4xx or 5xx, then probably you should revisit 
> ff_http_averror(400, xxx) calls and check if they are indeed caused by 
> client behaviour. Because if the server is violating the protocol, then 
> they should be ff_http_averror(500, xxx) IMHO.
> 
Checked the code, I see 2 cases of ff_http_averror(400, xxx) -
1. HTTP server received an unexpected http method (would have been more correct to use 405, but don't think it matters...)
2. HTTP server received an invalid http version string
So, I think it's fine.


> Regards,
> Marton
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fffmpeg.org%2Fmailman%2Flistinfo%2Fffmpeg-devel&amp;data=04%7C01%7Ceran.kornblau%40kaltura.com%7C590db1ef6c4d484bb34508d88c030453%7C0c503748de3f4e2597e26819d53a42b6%7C0%7C0%7C637413291875784243%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=hemm499pmQQQEDOc4FXeq1EThvOT7FyfjWRsylFvdm0%3D&amp;reserved=0
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
diff mbox series

Patch

diff --git a/libavformat/http.c b/libavformat/http.c
index 3d25d652d3..ea14ef0c47 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -119,8 +119,10 @@  typedef struct HTTPContext {
     char *method;
     int reconnect;
     int reconnect_at_eof;
+    int reconnect_on_err;
     int reconnect_streamed;
     int reconnect_delay_max;
+    char *reconnect_on_status;
     int listen;
     char *resource;
     int reply_code;
@@ -164,6 +166,8 @@  static const AVOption options[] = {
     { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
     { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
+    { "reconnect_on_err", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_err), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
+    { "reconnect_on_status", "list of http status codes to reconnect on. the list can include specific status codes / 4xx / 5xx", OFFSET(reconnect_on_status), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
     { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
     { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D },
     { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E },
@@ -258,21 +262,75 @@  static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
     return location_changed;
 }
 
+static int http_should_reconnect(HTTPContext *s, int err)
+{
+    const char *status_group;
+    char http_code[4];
+
+    switch (err) {
+    case AVERROR_HTTP_BAD_REQUEST:
+    case AVERROR_HTTP_UNAUTHORIZED:
+    case AVERROR_HTTP_FORBIDDEN:
+    case AVERROR_HTTP_NOT_FOUND:
+    case AVERROR_HTTP_OTHER_4XX:
+        status_group = "4xx";
+        break;
+
+    case AVERROR_HTTP_SERVER_ERROR:
+        status_group = "5xx";
+        break;
+
+    default:
+        return s->reconnect_on_err;
+    }
+
+    if (!s->reconnect_on_status) {
+        return 0;
+    }
+
+    if (av_match_list(status_group, s->reconnect_on_status, ',') > 0) {
+        return 1;
+    }
+
+    snprintf(http_code, sizeof(http_code), "%d", s->http_code);
+
+    return av_match_list(http_code, s->reconnect_on_status, ',') > 0;
+}
+
 /* return non zero if error */
 static int http_open_cnx(URLContext *h, AVDictionary **options)
 {
     HTTPAuthType cur_auth_type, cur_proxy_auth_type;
     HTTPContext *s = h->priv_data;
     int location_changed, attempts = 0, redirects = 0;
+    int reconnect_delay = 0;
+    uint64_t off;
+
 redo:
     av_dict_copy(options, s->chained_options, 0);
 
     cur_auth_type       = s->auth_state.auth_type;
     cur_proxy_auth_type = s->auth_state.auth_type;
 
+    off = s->off;
     location_changed = http_open_cnx_internal(h, options);
-    if (location_changed < 0)
-        goto fail;
+    if (location_changed < 0) {
+        if (!http_should_reconnect(s, location_changed) ||
+            reconnect_delay > s->reconnect_delay_max)
+            goto fail;
+
+        av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay);
+        location_changed = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback);
+        if (location_changed != AVERROR(ETIMEDOUT))
+            goto fail;
+        reconnect_delay = 1 + 2 * reconnect_delay;
+
+        /* restore the offset (http_connect resets it) */
+        s->off = off;
+
+        ffurl_closep(&s->hd);
+        goto redo;
+    }
 
     attempts++;
     if (s->http_code == 401) {