X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fgun%2Fconnection_pool%2Fworker.ex;h=bf57e9e5fd9ed9c457aa4ff4c6965c9060de9c5f;hb=9c672ecbb5d4477cd16d2139a2cb66d3923ac5c8;hp=ebde4bbf6f2bdacf44a21e9b310d6bb75c073662;hpb=58a4f350a8bc361d793cb96442f856362c18f195;p=akkoma diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index ebde4bbf6..bf57e9e5f 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -1,38 +1,75 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do alias Pleroma.Gun - use GenServer + use GenServer, restart: :temporary @registry Pleroma.Gun.ConnectionPool + def start_link([key | _] = opts) do + GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}}) + end + @impl true - def init([uri, key, opts, client_pid]) do - time = :os.system_time(:second) - # Register before opening connection to prevent race conditions - with {:ok, _owner} <- Registry.register(@registry, key, {nil, [client_pid], 1, time}), - {:ok, conn_pid} <- Gun.Conn.open(uri, opts), + def init([_key, _uri, _opts, _client_pid] = opts) do + {:ok, nil, {:continue, {:connect, opts}}} + end + + @impl true + def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do + with {:ok, conn_pid, protocol} <- Gun.Conn.open(uri, opts), Process.link(conn_pid) do + time = :erlang.monotonic_time(:millisecond) + {_, _} = - Registry.update_value(@registry, key, fn {_, used_by, crf, last_reference} -> - {conn_pid, used_by, crf, last_reference} + Registry.update_value(@registry, key, fn _ -> + {conn_pid, [client_pid], 1, time} end) send(client_pid, {:conn_pid, conn_pid}) - {:ok, %{key: key, timer: nil}, :hibernate} + + {:noreply, + %{ + key: key, + timer: nil, + client_monitors: %{client_pid => Process.monitor(client_pid)}, + protocol: protocol + }, :hibernate} else - err -> {:stop, err} + err -> + {:stop, {:shutdown, err}, nil} end end @impl true - def handle_cast({:add_client, client_pid, send_pid_back}, %{key: key} = state) do - time = :os.system_time(:second) + def handle_cast({:add_client, client_pid}, state) do + case handle_call(:add_client, {client_pid, nil}, state) do + {:reply, conn_pid, state, :hibernate} -> + send(client_pid, {:conn_pid, conn_pid}) + {:noreply, state, :hibernate} + end + end + + @impl true + def handle_cast({:remove_client, client_pid}, state) do + case handle_call(:remove_client, {client_pid, nil}, state) do + {:reply, _, state, :hibernate} -> + {:noreply, state, :hibernate} + end + end - {{conn_pid, _, _, _}, _} = + @impl true + def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} = state) do + time = :erlang.monotonic_time(:millisecond) + + {{conn_pid, used_by, _, _}, _} = Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time} end) - if send_pid_back, do: send(client_pid, {:conn_pid, conn_pid}) + :telemetry.execute( + [:pleroma, :connection_pool, :client, :add], + %{client_pid: client_pid, clients: used_by}, + %{key: state.key, protocol: protocol} + ) state = if state.timer != nil do @@ -42,16 +79,23 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do state end - {:noreply, state, :hibernate} + ref = Process.monitor(client_pid) + + state = put_in(state.client_monitors[client_pid], ref) + {:reply, conn_pid, state, :hibernate} end @impl true - def handle_cast({:remove_client, client_pid}, %{key: key} = state) do + def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do {{_conn_pid, used_by, _crf, _last_reference}, _} = Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> {conn_pid, List.delete(used_by, client_pid), crf, last_reference} end) + {ref, state} = pop_in(state.client_monitors[client_pid]) + + Process.demonitor(ref, [:flush]) + timer = if used_by == [] do max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) @@ -60,7 +104,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do nil end - {:noreply, %{state | timer: timer}, :hibernate} + {:reply, :ok, %{state | timer: timer}, :hibernate} end @impl true @@ -70,26 +114,36 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do {:stop, :normal, state} end + @impl true + def handle_info({:gun_up, _pid, _protocol}, state) do + {:noreply, state, :hibernate} + end + # Gracefully shutdown if the connection got closed without any streams left @impl true def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do {:stop, :normal, state} end - # Otherwise, shutdown with an error + # Otherwise, wait for retry @impl true - def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams} = down_message, state) do - {:stop, {:error, down_message}, state} + def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) do + {:noreply, state, :hibernate} end @impl true - def handle_call(:idle_close, _, %{key: key} = state) do - Registry.unregister(@registry, key) - {:stop, :normal, state} + def handle_info({:DOWN, _ref, :process, pid, reason}, state) do + :telemetry.execute( + [:pleroma, :connection_pool, :client, :dead], + %{client_pid: pid, reason: reason}, + %{key: state.key} + ) + + handle_cast({:remove_client, pid}, state) end # LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478 defp crf(time_delta, prev_crf) do - 1 + :math.pow(0.5, time_delta / 100) * prev_crf + 1 + :math.pow(0.5, 0.0001 * time_delta) * prev_crf end end