Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run Elixir Supervisor in escript

I have a mix project with as simple as possible a Supervisor and GenServer. When I call from iex:

EchoCmd.Supervisor.start_link([:Hello])
GenServer.call(:echoserver, :echo)
GenServer.call(:echoserver, :mumble)
GenServer.call(:echoserver, :echo)

The :mumble call raises an exception, then the GenServer is restarted and the second :echo call works ok.

If I run the code in any other way the Supervisor fails to restart the GenServer. For example, I create an escript of the project with the main module as follows:

defmodule EchoCmd.Echo do
    def main(args) do
        EchoCmd.Supervisor.start_link([:Hello])
        GenServer.call(:echoserver, :echo)
        GenServer.call(:echoserver, :mumble)
        GenServer.call(:echoserver, :echo)
    end
end

The :mumble call raises an exception and the escript terminates without the Supervisor restarting the GenServer.

I've not included the Supervisor and Server modules code because they work fine when called from iex, so I'm guessing they're not needed here.

Do I have a conceptual misunderstanding? Is this not possible, or am I doing something wrong?

like image 267
10 cls Avatar asked Nov 17 '14 00:11

10 cls


2 Answers

Escript behaviour is correct. You just missing how iex shell "helps you".

What you are doing in your code is starting a linked process, and than crashing it. And since it is a linked process, when it goes down, it suppose to bring down all linked processes. There could be some "exceptions", but that what's happening to your escript process.

Both shell and prcess supervisor can handle such "I died, so should you" message. They do it by changing the way process (linked process, not the dying one) processes such messages. It allows them to receive they as normal messages (that you could receive in receive clause if you would like to) rather than special, internal ones. To change this bahaviour they use Process.flag( :trap_exit, :true) (elixir doc pointing to eralng's one). It allows shell to just print death of killed processes, rather that dying every time you do something bad.

So you could do same thing. Change this flag, and if you wan't pattern match in receive on such messages. But I don't think that's what you looking for. Since your process is singleton, and supervisor does all restarting, you don't really have any reason to link to it in first place. There is no need for updates on deaths and restarts, just let supervisor worry about that. It's just like Joe Armstrong said (might be paraphrasing)

You don't need to know how to fix vending machine to use it.

So just, start, rather than start_link.

That said, you might consider creating link with supervisor, which also might die after too many restarts (he might be told to behave in such way). And it allows you to take him (and supervised process) when you die. Or he could be linked to your supervisor, or application supervisor, or in some other manner. It depends on your domain, and there is no bad decision, you just have to check what's working for you. It is design decision, and you either have to experiment or read more about it:

http://elixir-lang.org/getting_started/mix_otp/5.html

http://www.erlang.org/doc/design_principles/des_princ.html

http://learnyousomeerlang.com/supervisors

like image 39
mpm Avatar answered Sep 30 '22 08:09

mpm


The problem lies not in your server and supervisor, but in the way you're calling them. If the server exits while another process is waiting for a reply to GenServer.call, the calling process exits too, so the last call never happens. The reason for this is the process couldn't possibly continue in an invalid state if a synchronous call failed (GenServer.call is synchronous as opposed to GenServer.cast). If you're doing this just to test the supervisor, then you can try:

defmodule EchoCmd.Echo do
    def main(args) do
        EchoCmd.Supervisor.start_link([:Hello])
        GenServer.cast(:echoserver, :echo)
        GenServer.cast(:echoserver, :mumble)
        GenServer.cast(:echoserver, :echo)
    end
end

The reason it works in iex is that iex traps the exit and allows you to type in another line.

like image 120
Paweł Obrok Avatar answered Sep 30 '22 08:09

Paweł Obrok