0
votes

I have made this genserver

defmodule Recorder do
  use GenServer

  def start_link(args) do
    id = Map.get(args, :id)
    GenServer.start_link(__MODULE__, args, name: id)
  end

  def init(state) do
    schedule_fetch_call()
    {:ok, state}
  end

  def handle_info(:jpeg_fetch, state) do
    spawn(fn ->
      IO.inspect("I am being called")
      IO.inspect(DateTime.utc_now())
      Fincher.request(:get, state.url) |> IO.inspect()
    end)
    schedule_fetch_call()
    {:noreply, Map.put(state, :run, true)}
  end

  defp schedule_fetch_call do
    Process.send_after(self(), :jpeg_fetch, 1000)
  end
end

I am running it 1 request per second with such a state.

  defp get_children do
    Enum.map([
      %{
        id: :hdipc,
        url: "http://77.66.206.55/jpgmulreq/1/image.jpg?key=1516975535684&lq=1&COUNTER"
      }
    ], fn(camera) ->
      Supervisor.child_spec({Recorder, camera}, id: camera.id)
    end)
  end

in the application.ex.

I am using spawn here but I don't want to use spawn, what is the most logical and ideal way to solve this problem.

  1. where GenServer will make a request each second.

  2. Also don't wait for the request to complete, as the request can take more than a second.

  3. There are a few other certain operations I want to do in case of HTTP request-response.

I don't want to make genserver exhausted and crash. but to handle the back pressure of requests, which will be going each second (Not genstage as demand is not certain).

Is it possible to use GenServer in such a way that it can run without spawn and handle requests? any guidance or help would be wonderful.

1

1 Answers

0
votes

It looks like a perfect use-case for DynamicSupervisor.

Define the worker as a GenServer to perform a work.

defmodule Recorder.Worker do
  use GenServer

  def start_link(opts) do
    {id, opts} = Map.pop!(opts, :id)
    GenServer.start_link(__MODULE__, opts, name: id)
  end

  def init(state) do
    schedule_fetch_call()
    {:ok, state}
  end

  def handle_info(:jpeg_fetch, state) do
    result = Fincher.request(:get, state.url)
    schedule_fetch_call()
    {:noreply, Map.put(state, :run, {DateTime.utc_now(), result})}
  end

  defp schedule_fetch_call,
    do: Process.send_after(self(), :jpeg_fetch, 1000)
end

Then define a DynamicSupervisor as

defmodule Recorder.Supervisor do
  use DynamicSupervisor

  def start_link(opts),
    do: DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__)

  def start_child(opts),
    do: DynamicSupervisor.start_child(__MODULE__, {Recorder.Worker, opts})

  @impl DynamicSupervisor
  def init(opts),
    do: DynamicSupervisor.init(strategy: :one_for_one, extra_arguments: [opts])
end

Now start as many DynamicSupervisors, also supervised, as you need.


I believe I have already recommended my Tarearbol library that (besides everything else,) simplifies the above with Dynamic Workers Management to somewhat like

defmodule Recorder.Worker do
  use Tarearbol.DynamicManager

  def children_specs do
    Enum.into([%{id: :hdipc, url: "..."}], %{}, &{&1.id, &1})
  end

  def perform(_id, state) do
    result = Fincher.request(:get, state.url)
    {:noreply, Map.put(state, :run, {DateTime.utc_now(), result})}
  end
end

With it, perform/2 will be executed after timeout option (defaulted to 1 second,) and one might also handle response timeouts with handle_timeout/1.

Examples from tests might be examined for inspiration.