aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/manual/gun.asciidoc26
-rw-r--r--src/gun.erl20
-rw-r--r--test/gun_SUITE.erl21
3 files changed, 63 insertions, 4 deletions
diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc
index cd34537..f8d5406 100644
--- a/doc/src/manual/gun.asciidoc
+++ b/doc/src/manual/gun.asciidoc
@@ -208,6 +208,7 @@ opts() :: #{
http2_opts => http2_opts(),
protocols => [http | http2],
retry => non_neg_integer(),
+ retry_fun => fun(),
retry_timeout => pos_integer(),
supervise => boolean(),
tcp_opts => [gen_tcp:connect_option()],
@@ -253,6 +254,29 @@ retry (5)::
Number of times Gun will try to reconnect on failure before giving up.
+retry_fun - see below::
+
+A fun that will be called before every reconnect attempt. It receives
+the current number of retries left and the Gun options. It returns the
+next number of retries left and the timeout to apply before reconnecting.
+
+The default fun will remove one to the number of retries and set the
+timeout to the `retry_timeout` value.
+
+The fun must be defined as follow:
+
+[source,erlang]
+----
+fun ((non_neg_integer(), opts()) -> #{
+ retries => non_neg_integer(),
+ timeout => pos_integer()
+})
+----
+
+The fun will never be called when the `retry` option is set to 0. When
+this function returns 0 in the `retries` value, Gun will do one last
+reconnect attempt before giving up.
+
retry_timeout (5000)::
Time between retries in milliseconds.
@@ -350,6 +374,8 @@ undocumented and must be set to `gun_ws_h`.
* *2.0*: The `connect_timeout` option has been split into
three options: `domain_lookup_timeout`, `connect_timeout`
and when applicable `tls_handshake_timeout`.
+* *2.0*: The option `retry_fun` was added. It can be used to
+ implement different reconnect strategies.
* *2.0*: The `transport_opts` option has been split into
two options: `tcp_opts` and `tls_opts`.
* *2.0*: Introduce the type `req_headers()` and extend the
diff --git a/src/gun.erl b/src/gun.erl
index bb275f5..74866ec 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -116,6 +116,8 @@
http2_opts => http2_opts(),
protocols => [http | http2],
retry => non_neg_integer(),
+ retry_fun => fun((non_neg_integer(), opts())
+ -> #{retries => non_neg_integer(), timeout => pos_integer()}),
retry_timeout => pos_integer(),
supervise => boolean(),
tcp_opts => [gen_tcp:connect_option()],
@@ -279,6 +281,8 @@ check_options([Opt = {protocols, L}|Opts]) when is_list(L) ->
end;
check_options([{retry, R}|Opts]) when is_integer(R), R >= 0 ->
check_options(Opts);
+check_options([{retry_fun, F}|Opts]) when is_function(F, 2) ->
+ check_options(Opts);
check_options([{retry_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
check_options(Opts);
check_options([{supervise, B}|Opts]) when B =:= true; B =:= false ->
@@ -770,15 +774,25 @@ default_transport(_) -> tcp.
%% @todo This is where we would implement the backoff mechanism presumably.
not_connected(_, {retries, 0, Reason}, State) ->
{stop, {shutdown, Reason}, State};
-not_connected(_, {retries, Retries, _}, State=#state{opts=Opts}) ->
- Timeout = maps:get(retry_timeout, Opts, 5000),
+not_connected(_, {retries, Retries0, _}, State=#state{opts=Opts}) ->
+ Fun = maps:get(retry_fun, Opts, fun default_retry_fun/2),
+ #{
+ timeout := Timeout,
+ retries := Retries
+ } = Fun(Retries0, Opts),
{next_state, domain_lookup, State,
- {state_timeout, Timeout, {retries, Retries - 1, not_connected}}};
+ {state_timeout, Timeout, {retries, Retries, not_connected}}};
not_connected({call, From}, {stream_info, _}, _) ->
{keep_state_and_data, {reply, From, {error, not_connected}}};
not_connected(Type, Event, State) ->
handle_common(Type, Event, ?FUNCTION_NAME, State).
+default_retry_fun(Retries, Opts) ->
+ #{
+ retries => Retries - 1,
+ timeout => maps:get(retry_timeout, Opts, 5000)
+ }.
+
domain_lookup(_, {retries, Retries, _}, State=#state{host=Host, port=Port, opts=Opts,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
TransOpts = maps:get(tcp_opts, Opts, []),
diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl
index 4f99594..4526a15 100644
--- a/test/gun_SUITE.erl
+++ b/test/gun_SUITE.erl
@@ -289,7 +289,7 @@ postpone_request_while_not_connected(_) ->
end,
timer:sleep(Timeout),
%% Start the server so that next retry will result in the client connecting successfully.
- {ok, ListenSocket} = gen_tcp:listen(23456, [binary, {active, false}]),
+ {ok, ListenSocket} = gen_tcp:listen(23456, [binary, {active, false}, {reuseaddr, true}]),
{ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000),
%% The client should now be up.
{ok, http} = gun:await_up(Pid),
@@ -395,6 +395,25 @@ retry_1(_) ->
error(timeout)
end.
+retry_fun(_) ->
+ doc("Ensure the retry_fun is used when provided."),
+ {ok, Pid} = gun:open("localhost", 12345, #{
+ retry => 5,
+ retry_fun => fun(_, _) -> #{retries => 0, timeout => 500} end,
+ retry_timeout => 5000
+ }),
+ Ref = monitor(process, Pid),
+ After = case os:type() of
+ {win32, _} -> 2800;
+ _ -> 800
+ end,
+ receive
+ {'DOWN', Ref, process, Pid, {shutdown, _}} ->
+ ok
+ after After ->
+ error(shutdown_too_late)
+ end.
+
retry_immediately(_) ->
doc("Ensure Gun retries immediately."),
%% We have to make a first successful connection in order to test this.