Skip to content

Commit 4a77fb7

Browse files
authored
Add DBConnection.disconnect_all/3 (#240)
1 parent 42a1b99 commit 4a77fb7

File tree

8 files changed

+270
-53
lines changed

8 files changed

+270
-53
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
defmodule TestPoolDisconnectAll do
2+
use ExUnit.Case, async: true
3+
4+
alias TestPool, as: P
5+
alias TestAgent, as: A
6+
alias TestQuery, as: Q
7+
alias TestResult, as: R
8+
9+
test "disconnect on checkin" do
10+
stack = [
11+
{:ok, :state},
12+
{:ok, %Q{}, %R{}, :new_state1},
13+
{:ok, %Q{}, %R{}, :new_state2},
14+
{:ok, :dead_state},
15+
{:ok, :final_state},
16+
{:ok, %Q{}, %R{}, :final_state1},
17+
{:ok, %Q{}, %R{}, :final_state2}
18+
]
19+
20+
{:ok, agent} = A.start_link(stack)
21+
22+
opts = [agent: agent, parent: self()]
23+
{:ok, pool} = P.start_link(opts)
24+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
25+
26+
P.disconnect_all(pool, 0)
27+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
28+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
29+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
30+
31+
err = %DBConnection.ConnectionError{message: "disconnect_all requested", severity: :info}
32+
33+
assert [
34+
connect: [_],
35+
handle_execute: [_, _, _, :state],
36+
handle_execute: [_, _, _, :new_state1],
37+
disconnect: [^err, :new_state2],
38+
connect: [_],
39+
handle_execute: [_, _, _, :final_state],
40+
handle_execute: [_, _, _, :final_state1]
41+
] = A.record(agent)
42+
end
43+
44+
@tag :idle_interval
45+
test "disconnect on ping" do
46+
parent = self()
47+
48+
stack = [
49+
{:ok, :state},
50+
{:ok, %Q{}, %R{}, :new_state},
51+
fn _, _ ->
52+
send(parent, :disconnecting)
53+
{:ok, :dead_state}
54+
end,
55+
{:ok, :final_state},
56+
{:ok, %Q{}, %R{}, :final_state1},
57+
{:ok, %Q{}, %R{}, :final_state2},
58+
]
59+
60+
{:ok, agent} = A.start_link(stack)
61+
62+
opts = [agent: agent, parent: self(), idle_interval: 50]
63+
{:ok, pool} = P.start_link(opts)
64+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
65+
66+
# High intervals do not affect ping because those are always disconnected.
67+
P.disconnect_all(pool, 45_000)
68+
assert_receive :disconnecting
69+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
70+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
71+
72+
err = %DBConnection.ConnectionError{message: "disconnect_all requested", severity: :info}
73+
74+
assert [
75+
connect: [_],
76+
handle_execute: [_, _, _, :state],
77+
disconnect: [^err, :new_state],
78+
connect: [_],
79+
handle_execute: [_, _, _, :final_state],
80+
handle_execute: [_, _, _, :final_state1]
81+
] = A.record(agent)
82+
end
83+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
defmodule TestOwnershipDisconnectAll do
2+
use ExUnit.Case, async: true
3+
4+
alias TestPool, as: P
5+
alias TestAgent, as: A
6+
alias TestQuery, as: Q
7+
alias TestResult, as: R
8+
alias DBConnection.Ownership
9+
10+
test "disconnect on checkin" do
11+
stack = [
12+
{:ok, :state},
13+
{:ok, %Q{}, %R{}, :new_state1},
14+
{:ok, %Q{}, %R{}, :new_state2},
15+
{:ok, %Q{}, %R{}, :new_state3},
16+
{:ok, :dead_state},
17+
{:ok, :final_state},
18+
{:ok, %Q{}, %R{}, :final_state1},
19+
{:ok, %Q{}, %R{}, :final_state2}
20+
]
21+
22+
{:ok, agent} = A.start_link(stack)
23+
24+
opts = [agent: agent, parent: self()]
25+
{:ok, pool} = P.start_link(opts)
26+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
27+
28+
P.disconnect_all(pool, 0)
29+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
30+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
31+
32+
assert Ownership.ownership_checkin(pool, []) == :ok
33+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
34+
assert P.execute(pool, %Q{}, [:param]) == {:ok, %Q{}, %R{}}
35+
36+
err = %DBConnection.ConnectionError{message: "disconnect_all requested", severity: :info}
37+
38+
assert [
39+
connect: [_],
40+
handle_execute: [_, _, _, :state],
41+
handle_execute: [_, _, _, :new_state1],
42+
handle_execute: [_, _, _, :new_state2],
43+
disconnect: [^err, :new_state3],
44+
connect: [_],
45+
handle_execute: [_, _, _, :final_state],
46+
handle_execute: [_, _, _, :final_state1]
47+
] = A.record(agent)
48+
end
49+
end

lib/db_connection.ex

+28-1
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,11 @@ defmodule DBConnection do
369369
Defaults to 1000ms.
370370
* `:queue_target` and `:queue_interval` - See "Queue config" below
371371
* `:max_restarts` and `:max_seconds` - Configures the `:max_restarts` and
372-
`:max_seconds` for the connection pool supervisor (see the `Supervisor` docs)
372+
`:max_seconds` for the connection pool supervisor (see the `Supervisor` docs).
373+
Typically speaking the connection process doesn't terminate, except due to
374+
faults in DBConnection. However, if backoff has been disabled, then they
375+
also terminate whenever a connection is disconnected (for instance, due to
376+
client or server errors)
373377
* `:show_sensitive_data_on_connection_error` - By default, `DBConnection`
374378
hides all information during connection errors to avoid leaking credentials
375379
or other sensitive information. You can set this option if you wish to
@@ -454,6 +458,29 @@ defmodule DBConnection do
454458
pool.child_spec({conn_mod, opts})
455459
end
456460

461+
@doc """
462+
Forces all connections in the pool to disconnect within the given interval.
463+
464+
Once this function is called, the pool will disconnect all of its connections
465+
as they are checked in or as they are pinged. Checked in connections will be
466+
randomly disconnected within the given time interval. Pinged connections are
467+
immediately disconnected - as they are idle (according to `:idle_interval`).
468+
469+
If the connection has a backoff configured (which is the case by default),
470+
disconnecting means an attempt at a new connection will be done immediately
471+
after, without starting a new process for each connection. However, if backoff
472+
has been disabled, the connection process will terminate. In such cases,
473+
disconnecting all connections may cause the pool supervisor to restart
474+
depending on the max_restarts/max_seconds configuration of the pool,
475+
so you will want to set those carefully.
476+
"""
477+
@spec disconnect_all(conn, non_neg_integer, opts :: Keyword.t()) :: :ok
478+
def disconnect_all(conn, interval, opts \\ []) when interval >= 0 do
479+
pool = Keyword.get(opts, :pool, DBConnection.ConnectionPool)
480+
interval = System.convert_time_unit(interval, :millisecond, :native)
481+
pool.disconnect_all(conn, interval, opts)
482+
end
483+
457484
@doc """
458485
Prepare a query with a database connection for later execution.
459486

0 commit comments

Comments
 (0)