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 are your thoughts on this strategy? Oh, and what particularly does this error message refer to, and how should it be handled?