Search code examples
elixirerlang-otp

Elixir's GenServer handle_call, handle_info, handle_cast not being invoked


I've implemented a simple Application -> DynamicSupervisor system where the Application creates a single DynamicSupervisor on startup and then sends a message to it to start some work. The problem is that none of the messages (through GenServer's cast, Kernel's send) actually get caught. Trying to GenServer.call() throws the following error

no function clause matching in DynamicSupervisor.handle_call/3

which is odd since I have implemented it (to that spec). I'm sure that the DynamicSupervisor module gets started and doesn't quit on startup.

Application module code:

defmodule Simulacra.Application do
  use Application

  def start(_type, _args) do
    children = [
      Simulacra.MainSupervisor
    ]

    opts = [strategy: :one_for_one, name: Simulacra.Supervisor]
    {:ok, pid} = Supervisor.start_link(children, opts)
    start_supervisors(Supervisor.which_children(pid))
    {:ok, pid}
  end

  defp start_supervisors([h|_t] = list) when is_list(list) do
    {_, pid, _, _} = h
    start_supervisors(pid)
  end

  defp start_supervisors([]) do
    #noop
    IO.puts "Something"
  end

  defp start_supervisors(pid) do
    GenServer.cast(pid, :start)
    send(pid, :ping)
    GenServer.call(pid, :ping) <-- Throws an error
  end
end

Supervisor code:

defmodule Simulacra.MainSupervisor do
  @moduledoc false
  use DynamicSupervisor

  def start_link([]) do
    DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init(_noop) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end

  def handle_info(:ping, state) do
    IO.puts "sth"
  end

  def handle_cast(:start, state) do
    # do_something
    {:noreply, state}
  end

  def handle_call(:ping, _from, _x) do
    {:reply, "bing", "bong"}
  end

Solution

  • DynamicSupervisor is a custom implementation of GenServer behaviour.

    The only overridable function it has is child_spec/1.

    Your casts are although effectively ignored. When you cast a message, or send an info, VM simply ignores it when the process can not process it (or even if it does not exist.) call/3 is synchronous, so the sender expects a reply back, that is why you see it raises.

    Try GenServer.cast pid, :foo, you’ll receive :ok back, because these messages are not promised to be delivered.