GenServer/Supvisor - Help Needed!

Hey All! I’ve been struggling to implement some fairly basic functionality and was hoping somebody would be kind enough to point me in the right direction. I’ve been reading The Little Elixir and OTP Guidebook and would like to build on the ThySupervisor + ThyWorker example and implement it into a phoenix application. I’m struggling as I don’t know if I need an actual supervisor that starts up with the phoenix app and use ThySupervisor, which implements GenServer, as a server similiar to the Pooly application in the book or if I can somehow initialize the ThySupervisor upon starting the application and then dynamically add and kill process as I wish. Any insight would be greatly appreciated!

1 Like

I haven’t read the book, but i think you can just add your supervisor to the list of children in lib/<you_app_name>.ex.

  ...
  def start(_type, _args) do
    import Supervisor.Spec

    # Define workers and child supervisors to be supervised
    children = [
      # Start the Ecto repository
      supervisor(Test.Repo, []),
      # Start the endpoint when the application starts
      supervisor(Test.Endpoint, []),
      # Start your own worker by calling: Test.Worker.start_link(arg1, arg2, arg3)
      # worker(Test.Worker, [arg1, arg2, arg3]),
      supervisor(ThySupervisor, [])
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Test.Supervisor]
    Supervisor.start_link(children, opts)
  end
  ...
1 Like

I guess what is throwing me off is that in the book the ThySupervisor uses GenServer and not Supervisor. I’ll post some code when I get home, just thought I’d throw it out there now.

1 Like

I think it’s fine that it uses a genserver as long as it conforms to the supervisor behaviour.

1 Like

I guess I don’t fully understand supervisor behavior, in the example we are just passing a child specification list to the start link function similar to the example in the link below.

https://medium.com/@StevenLeiva1/elixir-supervisors-a-conceptual-understanding-ee0825f70cbe#.3h1jcodls

1 Like

When I add it to the list of children I get an undefined function error as it is expecting start_link/0.

defmodule ThySupervisor do
  use GenServer

  def start_link(child_spec_list) do
    GenServer.start_link(__MODULE__, [child_spec_list])
  end

  def start_child(supervisor, child_spec) do
    GenServer.call(supervisor, {:start_child, child_spec})
  end

  def terminate_child(supervisor, pid) when is_pid(pid) do
    GenServer.call(supervisor, {:terminate_child, pid})
  end

  def count_children(supervisor) do
    GenServer.call(supervisor, :count_children)
  end

  #Callbacks

  def init([child_spec_list]) do
    Process.flag(:trap_exit, true)
    state = child_spec_list
              |> start_children
              |> Enum.into(Map.new)
    {:ok, state}
  end

  def handle_call({:start_child, child_spec}, _from, state) do
    case start_child(child_spec) do
      {:ok, pid} ->
        new_state = state |> Map.put(pid, child_spec)
            {:reply, {:ok, pid}, new_state}
      :error ->
            {:reply, {:error, "error starting child"}, state}
      end
  end

  def handle_call({:terminate_child, pid}, _from, state) do
    case terminate_child(pid) do
        :ok ->
          new_state = state |> Map.delete(pid)
          {:reply, :ok, new_state}
        :error ->
          {:reply, {:error, "error terminating child"}, state}
    end
  end

  def handle_call(:count_children, _from, state) do
    {:reply, Map.size(state), state}
  end

  def handle_info({:EXIT, from, :killed}, state) do
    new_state = state |> Map.delete(from)
    {:noreply, new_state}
  end

  #Private Functions

  defp start_child({module, function, args}) do
    case apply(module, function, args) do
      pid when is_pid(pid) ->
        Process.link(pid)
        {:ok, pid}
     _ ->
      :error
    end
  end

  defp start_children([child_spec|rest]) do
    case start_child(child_spec) do
      {:ok, pid} ->
        [{pid, child_spec} | start_children(rest)]
      :error ->
        :error
    end
  end

  defp start_children([]), do: []

  defp terminate_child(pid) do
    Process.exit(pid, :kill)
    :ok
  end
end
1 Like

Maybe try passing supervisor(ThySupervisor, [[]]) to the list of children? Then an empty list would be the child_spec_list for start_link(child_spec_list).

1 Like