Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# 1.9.12

## Enhancement

- Add configurable `conn_max_idle_time` Finch pool option. Set
`config :graph_conn, conn_max_idle_time: ms` to proactively drop idle
HTTP connections before the upstream gateway closes them. Defaults to
`nil` (Finch default: `:infinity`, preserving existing behaviour).

# 1.9.11

## Update
Expand Down
16 changes: 14 additions & 2 deletions lib/graph_conn/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule GraphConn.Supervisor do
{Finch,
name: Module.concat(base_name, Finch),
pools: %{
default: [{:size, 50}, {:count, 1} | _conn_opts()]
default: _pool_opts()
}},
{GraphConn.ConnectionManager, [base_name, config]}
]
Expand All @@ -44,14 +44,26 @@ defmodule GraphConn.Supervisor do

defp _name(base_name), do: Module.concat(base_name, Supervisor)

defp _pool_opts do
base = [{:size, 50}, {:count, 1} | _conn_opts()]

:graph_conn
|> Application.get_env(:conn_max_idle_time)
|> case do
nil -> base
timeout -> [{:conn_max_idle_time, timeout} | base]
end
end

defp _conn_opts do
insecure? = Application.get_env(:graph_conn, :insecure) == true
proxy = Application.get_env(:graph_conn, :proxy, false)

if ca_cert_file = Application.get_env(:graph_conn, :ca_cert),
do: :public_key.cacerts_load(ca_cert_file)

case {insecure?, proxy} do
{insecure?, proxy}
|> case do
{true, false} ->
transport_opts = [verify: :verify_none]
[conn_opts: [transport_opts: transport_opts]]
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule GraphConn.MixProject do
def project do
[
app: :graph_conn,
version: "1.9.9",
version: "1.9.12",
elixir: "~> 1.17",
start_permanent: true,
test_coverage: [tool: ExCoveralls],
Expand Down
86 changes: 86 additions & 0 deletions test/graph_conn/supervisor_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
defmodule GraphConn.SupervisorTest do
use ExUnit.Case, async: false

alias GraphConn.{Request, TestConn}

describe "Finch conn_max_idle_time" do
setup do
original = Application.get_env(:graph_conn, :conn_max_idle_time)
test_pid = self()

"supervisor-test-idle-#{inspect(self())}"
|> :telemetry.attach(
[:finch, :conn_max_idle_time_exceeded],
fn _, _, _, _ -> send(test_pid, :idle_exceeded) end,
nil
)

fn ->
:telemetry.detach("supervisor-test-idle-#{inspect(test_pid)}")

original
|> case do
nil -> Application.delete_env(:graph_conn, :conn_max_idle_time)
val -> Application.put_env(:graph_conn, :conn_max_idle_time, val)
end

TestConn
|> Module.concat(Supervisor)
|> Process.whereis()
|> case do
nil -> :ok
pid -> Process.exit(pid, :test_cleanup)
end
end
|> on_exit()

:ok
end

test "idle connection is discarded when conn_max_idle_time is configured" do
Application.put_env(:graph_conn, :conn_max_idle_time, 50)

config = Application.get_env(:graph_conn, TestConn)
assert {:ok, _pid} = _start_connection(config)
assert_receive {:conn_status_changed, :ready}

# Wait longer than the configured idle time so the connection ages
Process.sleep(100)

# Trigger a checkout — Finch discards the stale connection and emits the event
assert {:ok, _} = TestConn.execute(:action, %Request{path: "capabilities"})

assert_receive :idle_exceeded, 500
assert :ok = TestConn.stop()
end

test "idle connection is kept when conn_max_idle_time is not configured" do
Application.delete_env(:graph_conn, :conn_max_idle_time)

config = Application.get_env(:graph_conn, TestConn)
assert {:ok, _pid} = _start_connection(config)
assert_receive {:conn_status_changed, :ready}

Process.sleep(100)

assert {:ok, _} = TestConn.execute(:action, %Request{path: "capabilities"})

refute_receive :idle_exceeded, 200
assert :ok = TestConn.stop()
end
end

defp _start_connection(config) do
config
|> TestConn.start_supervisor(%{forward_to: self()})
|> case do
{:ok, pid} ->
assert Process.alive?(pid)
{:ok, pid}

{:error, {:already_started, pid}} ->
Process.exit(pid, :we_need_new_connection)
_start_connection(config)
end
end
end
Loading