defmodule PushExWeb.SocketDrainer do @moduledoc false use GenServer require Logger def child_spec(options) when is_list(options) do tracker_ref = Keyword.get(options, :tracker_ref, PushEx.Instrumentation.Tracker) ranch_refs = Keyword.get(options, :ranch_refs, []) shutdown = Keyword.fetch!(options, :shutdown) %{ id: __MODULE__, start: {__MODULE__, :start_link, [tracker_ref, ranch_refs]}, shutdown: shutdown } end def start_link(tracker_ref, ranch_refs) do GenServer.start_link(__MODULE__, [tracker_ref, ranch_refs]) end def init([tracker_ref, ranch_refs]) do Process.flag(:trap_exit, true) {:ok, {tracker_ref, ranch_refs}} end def terminate(_reason, {tracker_ref, ranch_refs}) do suspend_ranch(ranch_refs) PushExWeb.Config.close_connections!(true) Logger.info("Waiting for sockets to drain for PushExWeb.PushSocket #{inspect(tracker_ref)}...") :ok = wait_for_drain_loop(tracker_ref) Logger.info("Sockets successfully drained for PushExWeb.PushSocket #{inspect(tracker_ref)}") end defp suspend_ranch(ranch_refs) do Enum.each(ranch_refs, fn ranch_ref -> Logger.info("Suspending listener for PushExWeb.PushSocket #{inspect(ranch_ref)}") :ok = :ranch.suspend_listener(ranch_ref) end) end defp wait_for_drain_loop(tracker_ref) do if connected_socket_count(tracker_ref) == 0 do :ok else kill_all_socket_connections(tracker_ref) Process.sleep(100) wait_for_drain_loop(tracker_ref) end end defp connected_socket_count(tracker_ref) do try do PushEx.Instrumentation.Tracker.connected_socket_count(pid: tracker_ref) catch _, _ -> 0 end end defp connected_socket_pids(tracker_ref) do try do PushEx.Instrumentation.Tracker.connected_transport_pids(pid: tracker_ref) catch _, _ -> [] end end defp kill_all_socket_connections(tracker_ref) do connected_socket_pids(tracker_ref) |> Enum.each(fn pid -> send(pid, %Phoenix.Socket.Broadcast{event: "disconnect"}) end) end end