open conn in separate task

This commit is contained in:
Alexander Strizhakov 2020-02-24 19:56:27 +03:00
commit 8efae966b1
No known key found for this signature in database
GPG key ID: 022896A53AEF1381
12 changed files with 384 additions and 304 deletions

View file

@ -6,9 +6,10 @@ defmodule Pleroma.Gun.API do
@callback open(charlist(), pos_integer(), map()) :: {:ok, pid()}
@callback info(pid()) :: map()
@callback close(pid()) :: :ok
@callback await_up(pid) :: {:ok, atom()} | {:error, atom()}
@callback await_up(pid, pos_integer()) :: {:ok, atom()} | {:error, atom()}
@callback connect(pid(), map()) :: reference()
@callback await(pid(), reference()) :: {:response, :fin, 200, []}
@callback set_owner(pid(), pid()) :: :ok
def open(host, port, opts), do: api().open(host, port, opts)
@ -16,11 +17,13 @@ defmodule Pleroma.Gun.API do
def close(pid), do: api().close(pid)
def await_up(pid), do: api().await_up(pid)
def await_up(pid, timeout \\ 5_000), do: api().await_up(pid, timeout)
def connect(pid, opts), do: api().connect(pid, opts)
def await(pid, ref), do: api().await(pid, ref)
def set_owner(pid, owner), do: api().set_owner(pid, owner)
defp api, do: Pleroma.Config.get([Pleroma.Gun.API], Pleroma.Gun)
end

View file

@ -118,7 +118,10 @@ defmodule Pleroma.Gun.API.Mock do
end
@impl API
def await_up(_pid), do: {:ok, :http}
def await_up(_pid, _timeout), do: {:ok, :http}
@impl API
def set_owner(_pid, _owner), do: :ok
@impl API
def connect(pid, %{host: _, port: 80}) do

View file

@ -6,6 +6,11 @@ defmodule Pleroma.Gun.Conn do
@moduledoc """
Struct for gun connection data
"""
alias Pleroma.Gun.API
alias Pleroma.Pool.Connections
require Logger
@type gun_state :: :up | :down
@type conn_state :: :active | :idle
@ -26,4 +31,145 @@ defmodule Pleroma.Gun.Conn do
last_reference: 0,
crf: 1,
retries: 0
@spec open(String.t() | URI.t(), atom(), keyword()) :: :ok | nil
def open(url, name, opts \\ [])
def open(url, name, opts) when is_binary(url), do: open(URI.parse(url), name, opts)
def open(%URI{} = uri, name, opts) do
pool_opts = Pleroma.Config.get([:connections_pool], [])
opts =
opts
|> Enum.into(%{})
|> Map.put_new(:retry, pool_opts[:retry] || 0)
|> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 100)
|> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
Logger.debug("opening new connection #{Connections.compose_uri_log(uri)}")
conn_pid =
if Connections.count(name) < opts[:max_connection] do
do_open(uri, opts)
else
try_do_open(name, uri, opts)
end
if is_pid(conn_pid) do
conn = %Pleroma.Gun.Conn{
conn: conn_pid,
gun_state: :up,
conn_state: :active,
last_reference: :os.system_time(:second)
}
:ok = API.set_owner(conn_pid, Process.whereis(name))
Connections.add_conn(name, key, conn)
end
end
defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
connect_opts =
uri
|> destination_opts()
|> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
with open_opts <- Map.delete(opts, :tls_opts),
{:ok, conn} <- API.open(proxy_host, proxy_port, open_opts),
{:ok, _} <- API.await_up(conn, opts[:await_up_timeout]),
stream <- API.connect(conn, connect_opts),
{:response, :fin, 200, _} <- API.await(conn, stream) do
conn
else
error ->
Logger.warn(
"Received error on opening connection with http proxy #{
Connections.compose_uri_log(uri)
} #{inspect(error)}"
)
nil
end
end
defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
version =
proxy_type
|> to_string()
|> String.last()
|> case do
"4" -> 4
_ -> 5
end
socks_opts =
uri
|> destination_opts()
|> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
|> Map.put(:version, version)
opts =
opts
|> Map.put(:protocols, [:socks])
|> Map.put(:socks_opts, socks_opts)
with {:ok, conn} <- API.open(proxy_host, proxy_port, opts),
{:ok, _} <- API.await_up(conn, opts[:await_up_timeout]) do
conn
else
error ->
Logger.warn(
"Received error on opening connection with socks proxy #{
Connections.compose_uri_log(uri)
} #{inspect(error)}"
)
nil
end
end
defp do_open(%URI{host: host, port: port} = uri, opts) do
{_type, host} = Pleroma.HTTP.Adapter.domain_or_ip(host)
with {:ok, conn} <- API.open(host, port, opts),
{:ok, _} <- API.await_up(conn, opts[:await_up_timeout]) do
conn
else
error ->
Logger.warn(
"Received error on opening connection #{Connections.compose_uri_log(uri)} #{
inspect(error)
}"
)
nil
end
end
defp destination_opts(%URI{host: host, port: port}) do
{_type, host} = Pleroma.HTTP.Adapter.domain_or_ip(host)
%{host: host, port: port}
end
defp add_http2_opts(opts, "https", tls_opts) do
Map.merge(opts, %{protocols: [:http2], transport: :tls, tls_opts: tls_opts})
end
defp add_http2_opts(opts, _, _), do: opts
defp try_do_open(name, uri, opts) do
Logger.debug("try to open conn #{Connections.compose_uri_log(uri)}")
with [{close_key, least_used} | _conns] <-
Connections.get_unused_conns(name),
:ok <- Pleroma.Gun.API.close(least_used.conn) do
Connections.remove_conn(name, close_key)
do_open(uri, opts)
else
[] -> nil
end
end
end

View file

@ -32,7 +32,7 @@ defmodule Pleroma.Gun do
defdelegate close(pid), to: :gun
@impl API
defdelegate await_up(pid), to: :gun
defdelegate await_up(pid, timeout \\ 5_000), to: :gun
@impl API
defdelegate connect(pid, opts), to: :gun
@ -42,4 +42,7 @@ defmodule Pleroma.Gun do
@spec flush(pid() | reference()) :: :ok
defdelegate flush(pid), to: :gun
@impl API
defdelegate set_owner(pid, owner), to: :gun
end