defmodule Mojito.Pool.Poolboy do @moduledoc false ## Mojito.Pool.Poolboy is an HTTP client with high-performance, easy-to-use ## connection pools based on the Poolboy library. ## ## Pools are maintained automatically by Mojito, requests are matched to ## the correct pool without user intervention, and multiple pools can be ## used for the same destination in order to reduce concurrency bottlenecks. ## ## Config parameters are explained in the `Mojito` moduledocs. @behaviour Mojito.Pool alias Mojito.{Config, Request, Utils} require Logger @doc ~S""" Performs an HTTP request using a connection pool, creating that pool if it didn't already exist. Requests are always matched to a pool that is connected to the correct destination host and port. """ @impl true def request(%{} = request) do with {:ok, valid_request} <- Request.validate_request(request), {:ok, _proto, host, port} <- Utils.decompose_url(valid_request.url), pool_key <- pool_key(host, port), {:ok, pool} <- get_pool(pool_key) do do_request(pool, pool_key, valid_request) end end defp do_request(pool, pool_key, request) do case Mojito.Pool.Poolboy.Single.request(pool, request) do {:error, %{reason: :checkout_timeout}} -> case start_pool(pool_key) do {:ok, pid} -> Mojito.Pool.Poolboy.Single.request(pid, request) error -> error end other -> other end end ## Returns a pool for the given destination, starting one or more ## if necessary. @doc false @spec get_pool(any) :: {:ok, pid} | {:error, Mojito.error()} def get_pool(pool_key) do case get_pools(pool_key) do [] -> opts = Mojito.Pool.pool_opts(pool_key) 1..opts[:pools] |> Enum.each(fn _ -> start_pool(pool_key) end) get_pool(pool_key) pools -> {:ok, Enum.random(pools)} end end ## Returns all pools for the given destination. @doc false @spec get_pools(any) :: [pid] defp get_pools(pool_key) do Mojito.Pool.Poolboy.Registry |> Registry.lookup(pool_key) |> Enum.map(fn {_, pid} -> pid end) end ## Starts a new pool for the given destination. @doc false @spec start_pool(any) :: {:ok, pid} | {:error, Mojito.error()} def start_pool(pool_key) do old_trap_exit = Process.flag(:trap_exit, true) try do GenServer.call( Mojito.Pool.Poolboy.Manager, {:start_pool, pool_key}, Config.timeout() ) rescue e -> {:error, e} catch :exit, _ -> {:error, :checkout_timeout} after Process.flag(:trap_exit, old_trap_exit) end |> Utils.wrap_return_value() end ## Returns a key representing the given destination. @doc false @spec pool_key(String.t(), pos_integer) :: Mojito.Pool.pool_key() def pool_key(host, port) do {host, port} end end