lib/shopify/graphql/limiter/partition_monitor.ex

if Code.ensure_loaded?(GenStage) do
  defmodule Shopify.GraphQL.Limiter.PartitionMonitor do
    use GenServer

    alias Shopify.GraphQL.{ Helpers, Limiter }

    #
    # client
    #

    @spec monitor?(Keyword.t()) :: boolean
    def monitor?(limiter_opts) do
      Keyword.get(limiter_opts, :monitor, true)
    end

    @spec name(Supervisor.name()) :: Supervisor.name()
    def name(partition) do
      Helpers.Limiter.process_name(partition, PartitionMonitor)
    end

    @spec pid(Supervisor.name()) :: pid
    def pid(partition) do
      partition
      |> name()
      |> Helpers.Limiter.pid()
    end

    @spec start(Supervisor.name(), Keyword.t()) :: reference
    def start(partition, limiter_opts) do
      timeout = Keyword.get(limiter_opts, :monitor_timeout, 3_500)

      Process.send_after(pid(partition), :check, timeout)
    end

    @spec start_link(Keyword.t()) :: GenServer.on_start()
    def start_link(opts) do
      if monitor?(opts[:limiter_opts]) do
        GenServer.start_link(__MODULE__, opts, name: name(opts[:partition]))
      else
        :ignore
      end
    end

    #
    # callbacks
    #

    @impl true
    def init(opts) do
      limiter_opts = Keyword.get(opts, :limiter_opts, [])

      partition = Keyword.get(opts, :partition)

      state = Map.new()
      state = Map.put(state, :limiter_opts, limiter_opts)
      state = Map.put(state, :partition, partition)
      state = Map.put(state, :timer, start(partition, limiter_opts))

      { :ok, state }
    end

    @impl true
    def handle_info(:check, state) do
      partition = Map.get(state, :partition)

      if Limiter.Partition.idle?(partition) do
        Supervisor.stop(partition)
      else
        { :noreply, %{ state | timer: start(partition, state[:limiter_opts]) } }
      end
    end
  end
end