ZastrixArundell

ZastrixArundell

How do I supervise an erlport GenServer?

So I have this erlport connection module:

defmodule PheedThePi.PythonConnection do
  @moduledoc """
  Module responsible for the Python connection and IO.
  """

  def start() do
    path = [
      :code.priv_dir(:pheed_the_pi), "python"
    ] |> Path.join() |> IO.inspect(label: "Python priv path")

    {:ok, pid} = :python.start([{:python_path, to_charlist(path)}])

    pid
  end

  def call(pid, module, function, arguments \\ []), do:
    :python.call(pid, module, function, arguments)

  def cast(pid, message), do:
    :python.cast(pid, message)

  def stop(pid), do:
    :python.stop(pid)

end

And my GenServer for the connection:

defmodule PheedThePi.PythonServer do
  @moduledoc """
  GenServer which manages the Python erlang port.
  """

  use GenServer
  alias PheedThePi.PythonConnection, as: Python

  def start_link(_), do:
    GenServer.start_link(__MODULE__, nil, name: __MODULE__)

  def init(_) do
    # Get the pid of the python session.
    python_session = Python.start()
    # Start the connection to the python session.
    Python.call(python_session, :api, :register_handler, [self()])
    {:ok, python_session}
  end

  # Call a specific function given an atom as the function name
  @spec cast_function(atom(), list(any())) :: :ok
  def cast_function(function, arguments), do:
    GenServer.cast(__MODULE__, {function, arguments})

  # Send a general message to Python
  @spec send_message(any) :: :ok
  def send_message(message), do:
    GenServer.cast(__MODULE__, {:python_message, message})

  def handle_cast({:python_message, message}, python_session) do
    Python.cast(python_session, message)
    {:noreply, python_session}
  end

  def handle_cast({function, arguments}, python_session) do
    Python.call(python_session, :api, function, arguments)
    {:noreply, python_session}
  end

  def handle_info({:python, message}, python_session) do
    IO.write "Got message from Python: #{message}\n"
    {:noreply, python_session}
  end

  def terminate(_reason, python_session) do
    Python.stop(python_session)
  end

end

And my Python code:

from erlport.erlang import set_message_handler, cast
from erlport.erlterms import Atom

message_handler = None #reference to the elixir process to send result to

def message(message):
    message = message.decode("utf-8") 
    print('Hey, python has gotten the message: ' + message)
    return 'test'

def cast_message(pid, message):
    cast(pid, (Atom(b'python'), message))

def register_handler(pid):
    #save message handler pid
    global message_handler
    message_handler = pid

def handle_message(message):
    message = message.decode("utf-8")
    print("Received message from Elixir: " + message) 
    cast_message(message_handler, 'Here you go: ' + message)


set_message_handler(handle_message)

Now this works when you run expected code:

PheedThePi.PythonServer.send_message "this is a message"

:ok
iex(12)> Received message from Elixir: this is a message
Got message from Python: Here you go: this is a message

But if I were to do something stupid like:

iex(12)> PheedThePi.PythonServer.send_message 1+1                
:ok
iex(13)> [error] GenServer #PID<0.420.0> terminating
** (stop) {:message_handler_error, {:python, :"builtins.AttributeError", '\'int\' object has no attribute \'decode\'', ['  File "/home/zastrix/Documents/Personal/Phoenix/pheed_the_pi/_build/dev/lib/pheed_the_pi/priv/python/api.py", line 20, in handle_message\n    message = message.decode("utf-8")\n', '  File "/home/zastrix/Documents/Personal/Phoenix/pheed_the_pi/_build/dev/lib/erlport/priv/python3/erlport/erlang.py", line 233, in _call_with_error_handler\n    function(*args)\n']}}
Last message: {#Port<0.10>, {:data, <<131, 104, 2, 100, 0, 1, 101, 104, 4, 100, 0, 6, 112, 121, 116, 104, 111, 110, 100, 0, 23, 98, 117, 105, 108, 116, 105, 110, 115, 46, 65, 116, 116, 114, 105, 98, 117, 116, 101, 69, 114, 114, 111, 114, 107, 0, ...>>}}
State: {:state, :infinity, 0, #Port<0.10>, [], []}

I get an error message, and my erlport process dies, not my GenServer so it doesn’t restart. I couldn’t find a way but is it possible to perhaps add a Supervisor to my GenServer which supercises the erlport process?

Marked As Solved

kokolegorille

kokolegorille

Nice You find a solution.

I would trap exit from this GenServer, and restart the python session upon receiving a DOWN message… avoiding the GenSever restart, but it’s just cosmetic.

BTW You could use the following line, as it is atomic.

python_session = Python.start_link()

instead of…

# Get the pid of the python session.
python_session = Python.start()

# Link the python pid to the GenServer
Process.link(python_session)

Also Liked

kokolegorille

kokolegorille

As it is just a process, You could monitor it, catch DOWN message… then take appropriate mesure.

BTW there is also a :python.start_link() so it is easy to link it to another process.

ZastrixArundell

ZastrixArundell

Thanks. I didn’t know that the difference between start and start_link is that start_link basically links the process to the caller (I mean, now it makes sense when I wrote it). I’ll give you a solution as it’s basically what I’ve done but better ahahah!

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: &lt;h1&gt;Create Post&lt;/h1&gt; &lt;%= ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement