Supervisors: Need help making heads or tails of a cryptic handle_call error

defmodule Supervisor.Main do
  use Supervisor

  def start_link(_args) do
    Supervisor.start_link(__MODULE__, name: __MODULE__)
  end

  def init(_) do
    Process.flag(:trap_exit, true)

    children = [
      PyOperatorSupervisor,
      Server.SomeMonitor
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end
defmodule PyOperatorSupervisor do
  use Supervisor

  @timeout 60_000

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

  @impl true
  def init(_) do
    Process.flag(:trap_exit, true)

    children = [
      :poolboy.child_spec(:py_pool,
        name: {:local, :py_pool},
        worker_module: Server.PyOperator,
        size: 10,
        max_overflow: 20
      )
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end

  def handle_call(foo, from, state) do
    task = LogBook.testing({:handle_call, arg1: foo, from: from, state: state}, __MODULE__, 28)

    {:reply, task, state}
  end

  def launch({data, py_module, py_lambda}) do
    :poolboy.transaction(
      :py_pool,
      fn pid ->
        GenServer.call(pid, {data, py_module, py_lambda}, @timeout)
      end,
      @timeout
    )
  end
end
defmodule Server.PyOperator do
  use GenServer
  use Export.Python

  def start_link(_) do
    GenServer.start_link(__MODULE__, %{})
  end

  @impl true
  def init(state) do
    Process.flag(:trap_exit, true)

    priv_path = Path.join(:code.priv_dir(:foo), "python")
    {:ok, py} = Python.start_link(python_path: priv_path)
    {:ok, Map.put(state, :py, py)}
  end

  @impl true
  def handle_call({args, py_module, py_lambda}, _from, %{py: py} = state) do
    report = Python.call(py, py_module, py_lambda, [args])
    {:ok, :message_logged} = LogBook.main(report, __MODULE__, 21, :quiet)
    {:reply, report, state}
  end

  @impl true
  def handle_info({:EXIT, _, :normal}, state), do: {:noreply, state}

  @impl true
  def handle_info({:DOWN, _, :process, _, :normal}, state), do: {:noreply, state}

  @impl true
  def terminate(_reason, %{py: py}) do
    Python.stop(py)
    :ok
  end
end
defmodule Server.SomeMonitor do
  use GenServer

  @timeout 5_000

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

  @impl true
  def init(state) do
    Process.flag(:trap_exit, true)
    Process.send(self(), :kickoff, state)

    {:ok, state}
  end

  @impl true
  def handle_info(
        :kickoff,
        state
      ) do
    DoWork.launch()

    Process.send(self(), :update, state)
    {:noreply, state}
  end

  @impl true
  def handle_info(:update, state) do
    timeout_reference = Process.send_after(self(), :job_timeout, @timeout)

    task =
      Task.Supervisor.async(
        PyOperatorSupervisor,
        fn -> PyOperatorSupervisor.launch({[], "foo", "bar"}) end
      )

    result = Task.await(task, 10_000)

    Process.cancel_timer(timeout_reference)

    case result do
      {:ok, some_response} ->
        LogBook.testing(some_response, __MODULE__, 46)
        {:ok, _} = Baz.main(some_response)
        Process.send(self(), :update, state)
        {:noreply, state}

      {:error, reason} ->
        LogBook.main(
          {:glitch, critical_error: reason, state: state},
          __MODULE__,
          55,
          :error
        )

        {:stop, reason, state}
    end

    {:noreply, state}
  end

  @impl true
  def handle_info(:job_timeout, state) do
    LogBook.main(
      "Still waiting for stuff to come in. Elapsed time: #{@timeout / 1000} second(s).",
      __MODULE__,
      70,
      :warning
    )

    {:noreply, state}
  end

  @impl true
  def handle_info(critical_error, state), do: {:stop, critical_error, state}

  @impl true
  def terminate(critical_error, state),
    do: LogBook.main({critical_error, state}, __MODULE__, 82, :error)
end

Error Message:

%{size: 2, type: 'Tuple', value: {{{:function_clause, [{:supervisor, :handle_call, [{:start_task, [{:nonode@nohost, Server.SomeMonitor, #PID<0.441.0>}, :monitor], :temporary, nil}, {#PID<0.441.0>, #Reference<0.1421632187.1679294465.116665>}, {:state, {:local, PyOperatorSupervisor}, :one_for_one, {[:py_pool], %{py_pool: {:child, #PID<0.418.0>, :py_pool, {:poolboy, :start_link, [[name: {:local, :py_pool}, worker_module: Server.PyOperator, size: 10, max_overflow: 20], []]}, :permanent, false, 5000, :worker, [:poolboy]}}}, :undefined, 3, 5, [], 0, :never, PyOperatorSupervisor, []}], [file: 'supervisor.erl', line: 435]}, {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 1146]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 1175]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]}, {GenServer, :call, [PyOperatorSupervisor, {:start_task, [{:nonode@nohost, Server.SomeMonitor, #PID<0.441.0>}, :monitor], :temporary, nil}, :infinity]}}, []}}

What is this error trying to communicate and how is the underlying issue resolved?

I’m not sure why the Erlang → Elixir error-translation isn’t handling this, but I believe this means that something is trying to GenServer.call with :start_task at a process that doesn’t have a matching handle_call.

I’d start by looking for :start_task in your code, as it doesn’t appear anywhere in what’s posted. :thinking:

1 Like

My suspicion is that is you are calling Task.Supervisor.start_child or similar on a regular supervisor (which is not a Task.Supervisor).

2 Likes

Thanks for your input :smile:

    task =
      Task.Supervisor.async(
        PyOperatorSupervisor,
        fn -> PyOperatorSupervisor.launch({[], "foo", "bar"}) end
      )

It seems you’re suggesting that in the code above PyOperatorSupervisor should be a Task.Supervisor. Am I on the right track or no?

1 Like

Exactly.

2 Likes

Thanks again.

Based on what I gathered from the docs, I made the following revision:

defmodule Supervisor.Main do
  use Supervisor

  def start_link(_args) do
    Supervisor.start_link(__MODULE__, name: __MODULE__)
  end

  def init(_) do
    Process.flag(:trap_exit, true)

    children = [
      {Task.Supervisor, name: PyOperatorSupervisor},
      Server.SomeMonitor
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end

Here’s the new error that’s cropped up:

%{size: 2, type: 'Tuple', value: {{{:noproc, {:gen_server, :call, [:py_pool, {:checkout, #Reference<0.3115803581.2621177857.257132>, true}, 60000]}}, {Task, :await, [%Task{owner: #PID<0.1725.0>, pid: #PID<0.2319.0>, ref: #Reference<0.3115803581.2621243393.257131>}, 10000]}}, []}} 

It seems that PyOperatorSupervisor isn’t being started? If so, why?

You most likely have application.ex file (if not, please say so). In there, you define the supervision tree of your application. You should list Supervisor.Main main as a child there.

In other words, no supervisor start dynamically. You need to start them all the way down from your application.ex start/2 definition.

1 Like
defmodule MyApp.Application do
  require Logger
  use Application

  def start(_type, _args) do
    :logger.add_handlers(:myApp)

    Print.text("Initiating MyApp", __MODULE__, 8)

    children = [
      MyAppDB.Repo,
      Supervisor.Main
    ]

    options = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, options)
  end
end

It seems this is not configured correctly, then, because the same :noproc error persists. What are your thoughts?

My goof was not using a different name for the Task Supervisor. Here’s what the working code looks like:

defmodule MyApp.Application do
  require Logger
  use Application

  def start(_type, _args) do
    :logger.add_handlers(:myApp)

    Print.text("Initiating MyApp", __MODULE__, 8)

    children = [
      MyAppDB.Repo,
      Supervisor.Main
    ]

    options = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, options)
  end
end
defmodule Supervisor.Main do
  use Supervisor

  def start_link(_args) do
    Supervisor.start_link(__MODULE__, name: __MODULE__)
  end

  def init(_) do
    Process.flag(:trap_exit, true)

    children = [
      {Task.Supervisor, name: TasksSupervisor},
      Server.SomeMonitor
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end
defmodule Server.SomeMonitor do
  use GenServer

    ... 

    def handle_info(:update, state) do

    ...

    task =
      Task.Supervisor.async(
        TasksSupervisor,
        fn -> PyOperatorSupervisor.launch({[], "foo", "bar"}) end
      )

      ...

      {:noreply, state}
    end

    ...

end

Thanks again for your all your help! :smiley:

1 Like