Skip to content

Commit f611ff8

Browse files
author
polvorin
authored
Minimum delay between connections (#49)
* Minimum delay between connections So far mero had support for throttling connections attempts when there are connection failures. This add a new, general throttling of connections (regardless if they succed or not) as a min wait time between attempts. It's useful when there are _large_ number of hosts connecting to the same memcache backed. * Formatting * Spelling
1 parent 8e9fdf2 commit f611ff8

2 files changed

Lines changed: 58 additions & 34 deletions

File tree

src/mero_conf.erl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
pool_connection_unused_max_time/1,
5959
connection_unused_max_time/1,
6060
pool_max_connection_delay_time/1,
61+
pool_min_connection_interval/1,
6162
max_connection_delay_time/1,
6263
stat_callback/0,
6364
stat_callback/1,
@@ -209,6 +210,16 @@ connection_unused_max_time(Val) ->
209210
pool_max_connection_delay_time(Pool) ->
210211
get_env_per_pool(max_connection_delay_time, Pool).
211212

213+
%% @doc: min delay between connection attempts
214+
-spec pool_min_connection_interval(Pool :: atom()) -> integer().
215+
pool_min_connection_interval(Pool) ->
216+
try
217+
get_env_per_pool(min_connection_interval, Pool)
218+
catch _:_ ->
219+
%% Don't want to make this mandatory, but the rest are mandatory already.
220+
undefined
221+
end.
222+
212223
-spec max_connection_delay_time(mero_conf_value(integer())) -> ok.
213224
max_connection_delay_time(Val) ->
214225
application:set_env(mero, max_connection_delay_time, Val).

src/mero_pool.erl

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@
7979
num_failed_connecting :: non_neg_integer(),
8080

8181
reconnect_wait_time :: non_neg_integer(),
82+
min_connection_interval_ms :: non_neg_integer() | undefined,
8283
worker_module :: atom(),
8384
callback_info :: mfargs(),
84-
pool :: term()}).
85+
pool :: term(),
86+
last_connection_attempt :: non_neg_integer()}).
8587

8688
-callback transaction(client(), atom(), [term()]) -> {error, term()} | {client(), {ok, any()}}.
8789
-callback close(client(), Reason :: term()) -> _.
@@ -190,7 +192,8 @@ init(Parent, ClusterName, Host, Port, PoolName, WrkModule) ->
190192
reconnect_wait_time = ?RECONNECT_WAIT_TIME,
191193
pool = PoolName,
192194
callback_info = CallBackInfo,
193-
worker_module = WrkModule},
195+
worker_module = WrkModule,
196+
last_connection_attempt = 0},
194197
timer:send_interval(5000, reload_pool_min_max_settings),
195198
pool_loop(schedule_expiration(reload_pool_min_max_settings(State)), Parent, Deb)
196199
end.
@@ -250,7 +253,9 @@ pool_loop(State, Parent, Deb) ->
250253
State#pool_st.host,
251254
State#pool_st.port,
252255
State#pool_st.callback_info),
253-
?MODULE:pool_loop(State, Parent, Deb)
256+
?MODULE:pool_loop(State#pool_st{
257+
last_connection_attempt = erlang:system_time(millisecond)},
258+
Parent, Deb)
254259
end;
255260
reload_pool_min_max_settings ->
256261
?MODULE:pool_loop(reload_pool_min_max_settings(State), Parent, Deb);
@@ -289,43 +294,50 @@ get_connection(State, {Pid, Ref} = _From) ->
289294

290295

291296
maybe_spawn_connect(#pool_st{
292-
cluster = ClusterName,
293297
free = Free,
298+
num_connecting = Connecting,
294299
num_connected = Connected,
295300
max_connections = MaxConn,
296-
min_connections = MinConn,
297-
num_connecting = Connecting,
298-
num_failed_connecting = NumFailed,
299-
worker_module = WrkModule,
300-
callback_info = CallbackInfo,
301-
reconnect_wait_time = WaitTime,
302-
pool = Pool,
303-
host = Host,
304-
port = Port} = State) ->
301+
min_connections = MinConn} = State) ->
305302
%% Length could be big.. better to not have more than a few dozens of sockets
306303
%% May be worth to keep track of the length of the free in a counter.
307304

308305
FreeSockets = length(Free),
309-
Needed = calculate_needed(FreeSockets, Connected, Connecting, MaxConn, MinConn),
310-
case {Needed, NumFailed, Connecting} of
311-
%% Need sockets and no failed connections are reported..
312-
%% we create new ones
313-
{Needed, NumFailed, _} when Needed > 0, NumFailed < 1 ->
314-
spawn_connections(ClusterName, Pool, WrkModule, Host, Port, CallbackInfo, Needed),
315-
State#pool_st{num_connecting = Connecting + Needed};
316-
317-
%% Wait before reconnection if more than one successive
318-
%% connection attempt has failed. Don't open more than
319-
%% one connection until an attempt has succeeded again.
320-
{Needed, _, 0} when Needed > 0 ->
321-
erlang:send_after(WaitTime, self(), connect),
322-
State#pool_st{num_connecting = Connecting + 1};
323-
324-
%% We dont need sockets or we have failed connections
325-
%% we wait before reconnecting.
326-
{_, _, _} ->
327-
State
328-
end.
306+
Needed = max(0, calculate_needed(FreeSockets, Connected, Connecting, MaxConn, MinConn)),
307+
maybe_spawn_connect(State, Needed, erlang:system_time(millisecond)).
308+
309+
%% Do not spawn new connections if
310+
%% - There is no need for new connection
311+
%% - There is minimum interval between connections, and that hasn't elapsed yet since the last
312+
%% connection
313+
%% - There are in-flight connection attempts
314+
maybe_spawn_connect(State = #pool_st{min_connection_interval_ms = Min,
315+
last_connection_attempt = Last,
316+
num_connecting = Connecting},
317+
Needed,
318+
Now) when Min /= undefined, (Now - Last) < Min;
319+
Connecting > 0;
320+
Needed == 0->
321+
State;
322+
maybe_spawn_connect(State = #pool_st{num_failed_connecting = NumFailed,
323+
reconnect_wait_time = WaitTime,
324+
num_connecting = Connecting}, _Needed, _Now)
325+
when NumFailed > 0 ->
326+
%% Wait before reconnection if more than one successive
327+
%% connection attempt has failed. Don't open more than
328+
%% one connection until an attempt has succeeded again.
329+
erlang:send_after(WaitTime, self(), connect),
330+
State#pool_st{num_connecting = Connecting + 1};
331+
maybe_spawn_connect(State = #pool_st{num_connecting = Connecting,
332+
pool = Pool,
333+
worker_module = WrkModule,
334+
cluster = ClusterName,
335+
host = Host,
336+
port = Port,
337+
callback_info = CallbackInfo}, Needed, Now) ->
338+
spawn_connections(ClusterName, Pool, WrkModule, Host, Port, CallbackInfo, Needed),
339+
State#pool_st{num_connecting = Connecting + Needed, last_connection_attempt = Now}.
340+
329341

330342
calculate_needed(FreeSockets, Connected, Connecting, MaxConn, MinConn) ->
331343
TotalSockets = Connected + Connecting,
@@ -503,7 +515,8 @@ filter_expired(#conn{updated = Updated} = Conn, {Now, TTL, ExpConns, ActConns})
503515
%% terminate by themselves (because of timeouts, errors, inactivity, etc)
504516
reload_pool_min_max_settings(State = #pool_st{cluster = ClusterName}) ->
505517
State#pool_st{min_connections = mero_conf:pool_min_free_connections(ClusterName),
506-
max_connections = mero_conf:pool_max_connections(ClusterName)}.
518+
max_connections = mero_conf:pool_max_connections(ClusterName),
519+
min_connection_interval_ms = mero_conf:pool_min_connection_interval(ClusterName)}.
507520

508521
safe_send(PoolName, Cmd) ->
509522
catch PoolName ! Cmd.

0 commit comments

Comments
 (0)