GenServer.call Error - :bad_return_value

defmodule Supervisor.PyOperatorManager 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 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

  @timeout 2_000

  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(:arbit), "python")
    {:ok, py_pid} = Python.start_link(python_path: priv_path)
    {:ok, Map.put(state, :py_pid, py_pid)}
  end

  @impl true
  def handle_call(
        {[] = args, "python_script" = py_module, "main" = py_lambda},
        _from,
        %{py_pid: py_pid} = state
      ) do
    parent = self()
    ref = make_ref()
    Process.send_after(self(), {:work_timeout, ref}, @timeout)

    python_script_results = Python.call(py_pid, py_module, py_lambda, [args])
    send(parent, {:work_complete, ref, python_script_results})

    {:noreply, state}
  end

  @impl true
  def handle_call({args, py_module, py_lambda}, _from, %{py_pid: py_pid} = state) do
    python_script_results = Python.call(py_pid, py_module, py_lambda, [args])
    {:reply, python_script_results, state}
  end

  @impl true
  def handle_info({:work_complete, _ref, python_script_results}, state) do
    {:reply, python_script_results, state}
  end

  def handle_info({:work_timeout, _ref}, state) do
    LogBook.main(
      "Still waiting . . . . Elapsed time: #{@timeout / 1000} second(s).",
      __MODULE__,
      90,
      :warning
    )

    {:noreply, 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_pid: py_pid}) do
    Python.stop(py_pid)
    :ok
  end
end




defmodule Server.AccountMonitor do
  use GenServer

  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_update, state)

    {:ok, state}
  end

  @impl true
  def handle_info(
        :kickoff_update,
        state
      ) do
    RunAncillaryJob.launch()

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

  @impl true
  def handle_info(:run_update, state) do
    script_response = Supervisor.PyOperatorManager.launch([], "python_script", "main")

    {:noreply, state}
  end

  def handle_info({:reply, script_response}, state) do
    {:ok, _} = DoStuff.main(script_response)

    Process.send(self(), :run_update, state)

    {: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(
      {:glitch, critical_error: critical_error, state: state},
      __MODULE__,
      66,
      :error
    )
end

Error Message:

{{:bad_return_value,
   {:reply,
    {:ok,
     #response_data},
    %{py_pid: #PID<0.440.0>}}},
  {GenServer, :call, [#PID<0.439.0>, {[], #python_function, "main"}, 60000]}}

What particularly does this error message refer to, and how should it be handled?

Not entirely sure if this is the cause, but at a minimum this is a bug, you can’t return :reply from a handle info. If ref is the from from a previous call though you can do:

  def handle_info({:work_complete, ref, python_script_results}, state) do
    GenServer.reply(ref, python_script_results)
    {:noreply, state}
  end
2 Likes

That worked, thanks!

The issue I’m now having is that even when python script results is finished far before the timeout, {:work timeout, ref} is still emitted. How do I prevent this from occurring?