defmodule Srh.Redis.ClientRegistry do use GenServer def start_link() do GenServer.start_link(__MODULE__, %{}, []) end def init(_opts) do { :ok, %{ worker_pids: [], last_worker_index: 0, currently_borrowed_pids: [] } } end def find_worker(registry) do GenServer.call(registry, {:find_worker}) end def borrow_worker(registry) do GenServer.call(registry, {:borrow_worker}) end def return_worker(registry, pid) do GenServer.cast(registry, {:return_worker, pid}) end def add_worker(registry, pid) do GenServer.cast(registry, {:add_worker, pid}) end def destroy_workers(registry) do GenServer.cast(registry, {:destroy_workers}) end def handle_call({:borrow_worker}, _from, state) do case do_find_worker(state) do {{:error, msg}, state_update} -> {:reply, {:error, msg}, state_update} {{:ok, pid}, state_update} -> # We want to put this pid into the borrowed pids state list { :reply, {:ok, pid}, %{ state_update | currently_borrowed_pids: [pid | state_update.currently_borrowed_pids] |> Enum.uniq() } } end end def handle_call({:find_worker}, _from, state) do {res, state_update} = do_find_worker(state) {:reply, res, state_update} end def handle_call(_msg, _from, state) do {:reply, :ok, state} end def handle_cast({:add_worker, pid}, state) do Process.monitor(pid) { :noreply, %{ state | worker_pids: [pid | state.worker_pids] |> Enum.uniq() } } end def handle_cast({:destroy_workers}, state) do for worker_pid <- state.worker_pids do Srh.Redis.ClientWorker.destroy_redis(worker_pid) end {:noreply, %{state | worker_pids: [], last_worker_index: 0}} end def handle_cast({:return_worker, pid}, state) do # Remove it from the borrowed array { :noreply, %{state | currently_borrowed_pids: List.delete(state.currently_borrowed_pids, pid)} } end def handle_cast(_msg, state) do {:noreply, state} end def handle_info({:DOWN, pid, :normal, _ref}, state) do {:noreply, %{state | worker_pids: List.delete(state.worker_pids, pid)}} end def handle_info(_msg, state) do {:noreply, state} end defp do_find_worker(state) do filtered_pids = state.worker_pids |> Enum.filter(&(!Enum.member?(state.currently_borrowed_pids, &1))) case length(filtered_pids) do 0 -> {{:error, :none_available}, state} len -> target = state.last_worker_index + 1 corrected_target = case target >= len do true -> 0 false -> target end { {:ok, Enum.at(state.worker_pids, corrected_target)}, %{state | last_worker_index: corrected_target} } end end end