Skip to content

Commit 90011ff

Browse files
PeterCxyjosevalim
authored andcommitted
Use connection establishment time for max_lifetime calculations
The holder's ts is reset on every update, which happens on pings too.
1 parent 4d7d6d8 commit 90011ff

3 files changed

Lines changed: 78 additions & 11 deletions

File tree

integration_test/connection_pool/max_lifetime_test.exs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,51 @@ defmodule MaxLifetimeTest do
44
alias TestPool, as: P
55
alias TestAgent, as: A
66

7+
defmodule ManyPingsConnection do
8+
def connect(opts) do
9+
{:ok, %{parent: Keyword.fetch!(opts, :parent), pings: 0}}
10+
end
11+
12+
def disconnect(err, state) do
13+
send(state.parent, {:max_lifetime_disconnect, err, state.pings})
14+
:ok
15+
end
16+
17+
def checkout(state), do: {:ok, state}
18+
def checkin(state), do: {:ok, state}
19+
20+
def ping(state) do
21+
state = %{state | pings: state.pings + 1}
22+
send(state.parent, {:ping, state.pings})
23+
{:ok, state}
24+
end
25+
end
26+
27+
test "disconnects and reconnects when idle ping fires before max_lifetime" do
28+
opts = [
29+
parent: self(),
30+
pool: DBConnection.ConnectionPool,
31+
pool_size: 1,
32+
connection_listeners: [self()],
33+
max_lifetime: 200..200,
34+
idle_interval: 50,
35+
backoff_min: 10
36+
]
37+
38+
{:ok, _pool} = DBConnection.start_link(ManyPingsConnection, opts)
39+
40+
assert_receive {:connected, conn}
41+
assert_receive {:ping, 1}
42+
43+
assert_receive {:max_lifetime_disconnect,
44+
%DBConnection.ConnectionError{message: "max_lifetime exceeded"}, pings},
45+
2_000
46+
47+
assert pings > 0
48+
assert_receive {:disconnected, ^conn}
49+
assert_receive {:connected, ^conn}
50+
end
51+
752
test "disconnects and reconnects when idle ping fires after max_lifetime" do
853
stack = [
954
{:ok, :state},

lib/db_connection/connection.ex

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ defmodule DBConnection.Connection do
6161
pool: pool,
6262
tag: tag,
6363
timer: nil,
64+
connected_at: nil,
6465
backoff: Backoff.new(opts),
6566
connection_listeners: Keyword.get(opts, :connection_listeners, []),
6667
after_connect: Keyword.get(opts, :after_connect),
@@ -85,14 +86,24 @@ defmodule DBConnection.Connection do
8586
else
8687
{:ok, state} when after_connect != nil ->
8788
ref = make_ref()
89+
connected_at = System.monotonic_time()
8890
:gen_statem.cast(self(), {:after_connect, ref})
89-
{:keep_state, %{s | state: state, client: {ref, :connect}}}
91+
{:keep_state, %{s | state: state, client: {ref, :connect}, connected_at: connected_at}}
9092

9193
{:ok, state} ->
9294
backoff = backoff && Backoff.reset(backoff)
9395
ref = make_ref()
96+
connected_at = System.monotonic_time()
9497
:gen_statem.cast(self(), {:connected, ref})
95-
{:keep_state, %{s | state: state, client: {ref, :connect}, backoff: backoff}}
98+
99+
{:keep_state,
100+
%{
101+
s
102+
| state: state,
103+
client: {ref, :connect},
104+
backoff: backoff,
105+
connected_at: connected_at
106+
}}
96107

97108
{:error, err} when is_nil(backoff) ->
98109
Logger.error(
@@ -154,7 +165,7 @@ defmodule DBConnection.Connection do
154165
demonitor(client)
155166
cancel_timer(timer)
156167
:ok = apply(mod, :disconnect, [err, state])
157-
s = %{s | state: nil, client: :closed, timer: nil}
168+
s = %{s | state: nil, client: :closed, timer: nil, connected_at: nil}
158169

159170
notify_connection_listeners(:disconnected, s)
160171

@@ -470,8 +481,8 @@ defmodule DBConnection.Connection do
470481
pool_update(state, %{s | client: nil, backoff: backoff})
471482
end
472483

473-
defp pool_update(state, %{pool: pool, tag: tag, mod: mod} = s) do
474-
case Holder.update(pool, tag, mod, state) do
484+
defp pool_update(state, %{pool: pool, tag: tag, mod: mod, connected_at: connected_at} = s) do
485+
case Holder.update(pool, tag, mod, state, connected_at) do
475486
{:ok, ref} ->
476487
{:keep_state, %{s | client: {ref, :pool}, state: state}, :hibernate}
477488

lib/db_connection/holder.ex

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ defmodule DBConnection.Holder do
88
@timeout 15000
99
@time_unit 1000
1010

11-
Record.defrecord(:conn, [:connection, :module, :state, :lock, :ts, deadline: nil, status: :ok])
11+
Record.defrecord(:conn, [
12+
:connection,
13+
:module,
14+
:state,
15+
:lock,
16+
:connected_at,
17+
deadline: nil,
18+
status: :ok
19+
])
20+
1221
Record.defrecord(:pool_ref, [:pool, :reference, :deadline, :holder, :lock])
1322

1423
@type t :: :ets.tid()
@@ -17,20 +26,22 @@ defmodule DBConnection.Holder do
1726
## Holder API
1827

1928
@spec new(pid, reference, module, term) :: t
20-
def new(pool, ref, mod, state) do
29+
@spec new(pid, reference, module, term, integer) :: t
30+
def new(pool, ref, mod, state, connected_at \\ System.monotonic_time()) do
2131
# Insert before setting heir so that pool can't receive empty table
2232
holder = :ets.new(__MODULE__, [:public, :ordered_set, decentralized_counters: true])
2333

24-
conn = conn(connection: self(), module: mod, state: state, ts: System.monotonic_time())
34+
conn = conn(connection: self(), module: mod, state: state, connected_at: connected_at)
2535
true = :ets.insert_new(holder, conn)
2636

2737
:ets.setopts(holder, {:heir, pool, ref})
2838
holder
2939
end
3040

3141
@spec update(pid, reference, module, term) :: {:ok, t} | :error
32-
def update(pool, ref, mod, state) do
33-
holder = new(pool, ref, mod, state)
42+
@spec update(pid, reference, module, term, integer) :: {:ok, t} | :error
43+
def update(pool, ref, mod, state, connected_at \\ System.monotonic_time()) do
44+
holder = new(pool, ref, mod, state, connected_at)
3445

3546
try do
3647
:ets.give_away(holder, pool, {:checkin, ref, System.monotonic_time()})
@@ -244,7 +255,7 @@ defmodule DBConnection.Holder do
244255
@spec maybe_disconnect(t, integer, non_neg_integer, {integer, non_neg_integer} | nil) ::
245256
boolean()
246257
def maybe_disconnect(holder, start, interval_ms, lifetime) do
247-
ts = :ets.lookup_element(holder, :conn, conn(:ts) + 1)
258+
ts = :ets.lookup_element(holder, :conn, conn(:connected_at) + 1)
248259

249260
disconnect_all_reason(start, interval_ms, ts, holder) ||
250261
max_lifetime_reason(lifetime, ts, holder)

0 commit comments

Comments
 (0)