I’m a little confused how Supervisors and Genservers work. I can create an Elixir application which uses a Supervisor and a Genserver and it works great when I execute it in the iex but how would I use that same functionality(Supervisor+Genserver) in a Elixir escript? If I try to run an Elixir escript with a Supervisor and Genserver and have the Supervisor restart the Genserver it crashes the escript…
Crashing? What error are you getting? You are starting your supervision tree manually?
I tried starting the supervisor with a module which implements the Application behavior and without and both times the running code failed when the GenServer had to be restarted. Note, all my attempts worked in the iex.
I’m just looking for an Elixir tutorial or Elixir code which implements a ‘simple’ Supervisor and ‘simple’ GenServer and will run past the the point where the Supervisor restarts the GenServer.
You should not need any application behaviour for an escript, all you should need to do is just start_link
the parent-most supervisor.
Unless of course escripts are reduced in some way, I do not ‘think’ they are, but may need to wait for Jose or so to appear to confirm. ^.^
As for a tutorial, the best for beginners that I can recommend would be Learn You Some Erlang (For Great Good!), also has a book that is awesome to get, but fully available online too. It would be very easy to elixirify the syntax, hmm…
How are you builidng your escript?
The problem is I don’t you how to do this at the Mix level or the Elixir code level for an escript.
Basically I just want to create an escript which has a simple supervisor which monitors a simple genserver and I want the escript code to force the supervisor to restart the genserver and continue executing after the restart.
Are you using mix escript.build ? What does your main routine look like?
While you can “script” elixir, it’s about a 1000 times easier to use mix even for escripts.
The primary difference between iex and an escript is that an escript is just going to stop and exit all the processes if the “main” routine exits. You need to put some kind of “loop” to allow it to continue running. The easiest way to do this is to use a receive statement.
receive do
{ :all_done_boss } ->
System.halt(0)
end
I think my problem is not understood.
- I can write and execute escripts. That’s not the problem.
The problem is writing an escript with a simple supervisor and simple genserver and have the escript force the supervisor to restart the genserver and continuing the execution of the escript after the restart.
All I’m looking for is working Elixir escript code which implements a simple supervisor and a simple genserver that doesn’t fail when the escript code forces the supervisor to restart the genserver.
I do understand what your problem is, but I had never problems running escripts that spin up a supervision tree. Can you please provide an example program that shows the problem?
So you had escript code that implemented a supervisor + genserver and had no problems when the escript code accessed the genserver and forced a restart?
I have used severall mix projects which did use some OTP-features like the ones you mentioned and they all worked fine. I can’t show any of them due to legal reasons.
But you should be able to reproduce a minified example of your problem, which we can use to walk through it and try to find whats going on, or at least confirm if we have the same problem using your code.
OK here’s the modules of my project…
mix.exs
defmodule Main.Mixfile do
use Mix.Project
def project do
[app: :main,
version: "0.1.0",
elixir: "~> 1.3",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
escript: escript,
deps: deps()]
end
def escript do
[main_module: Main]
end
def application do
[applications: [:logger]]
end
defp deps do
[]
end
end
main.ex
defmodule Main do
def main(_args \\ []) do
Super.start_link()
Stack.stack_push(:hello)
IO.inspect Stack.stack_pop
#remove comment below and the escript will get clobbered
#Stack.stack_fail((fn (stk) -> [h|_] = stk; h end))
IO.inspect Stack.stack_push(:World)
IO.inspect Stack.stack_pop
IO.puts "Good-bye"
end
end
stack.ex
defmodule Stack do
use GenServer
#client
def start_link do
GenServer.start_link(__MODULE__, [], [{:name, __MODULE__}])
end
def stop do
GenServer.stop(__MODULE__)
end
def stack_pop do
GenServer.call(__MODULE__, :pop)
end
def stack_push(item) do
GenServer.cast(__MODULE__, {:push, item})
end
def stack_fail(f) do
GenServer.call(__MODULE__, {:fail, f})
end
#callbacks
def handle_call(:pop, _from, [head|stack]) do
{:reply, {:Some, head}, stack}
end
def handle_call(:pop, _from, []) do
{:reply, {:None}, []}
end
def handle_call({:fail, f}, _from, stack) do
{:reply, f.(stack), stack}
end
def handle_cast({:push, item}, stack) do
{:noreply, [item|stack]}
end
end
super.ex
defmodule Super do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init(_) do
children = [
worker(Stack, [])
]
supervise(children, [strategy: :one_for_one])
end
end
Oh I think I see what is happening from looking at your code (not ran it yet, but I hope this is right).
First, your escript main
function is the entrance of an escript, if it dies the escript dies, yes?
Second, you are linking the supervisor to the main process, so if the super dies then main dies, and if main dies the super will die.
Third, on the commented line it is going to throw an error, which will get propagated back to the main process through a `{:fail,blah} message, which then kills your main process, which then kills the supervisor, and everything dies and quits.
You should see the same in in iex too (except iex will not die because the shell process catches death messages and just reports them but otherwise ignores them).
You should probably put the ‘work’ that you are doing in main in another process that would be good to be supervised by the same supervisor as Stack, do not have the main process supervise anything that you would not have a root application supervise.
Give that a try? I hope I am right?
EDIT: You could always trap exits too in the main process, that way it always lives regardless of other processes deaths.
Thanks. I give that a try.
I went ahead and grabbed the code and changed main to become:
defmodule Main do
def main(_args \\ []) do
Super.start_link()
Stack.stack_push(:hello)
IO.inspect Stack.stack_pop
#remove comment below and the escript will get clobbered
try do
Stack.stack_fail((fn (stk) -> [h|_] = stk; h end))
catch
:exit, e -> IO.inspect {:EXIT, e}
end
IO.inspect Stack.stack_push(:World)
IO.inspect Stack.stack_pop
IO.puts "Good-bye"
end
end
And I get this as output:
$ ./main
{:Some, :hello}
{:EXIT,
{{{:badmatch, []},
[{Main, :"-main/1-fun-0-", 1, [file: 'lib/main.ex', line: 13]},
{Stack, :handle_call, 3, [file: 'lib/stack.ex', line: 37]},
{:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 615]},
{:gen_server, :handle_msg, 5, [file: 'gen_server.erl', line: 647]},
{:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]},
{GenServer, :call,
[Stack, {:fail, #Function<0.17120755/1 in Main.main/1>}, 5000]}}}
:ok
{:Some, :World}
Good-bye
11:30:58.342 [error] GenServer Stack terminating
** (MatchError) no match of right hand side value: []
(main) lib/main.ex:13: anonymous fn/1 in Main.main/1
(main) lib/stack.ex:37: Stack.handle_call/3
(stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:647: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:fail, #Function<0.17120755/1 in Main.main/1>}
State: []
So yep, you can just catch the exit. The message that printed out at the end was about the Stack terminating, its message came through ‘later’ and got flushed when main died, that is why it appeared later, but it actually ‘happened’ at the time of the exit.
But doesn’t that defeat the whole purpose of let it fail and supervisors? Isn’t this just turning into defensive programming?
Well, You let your main-process crash. You can’t recover from this, ever!
Try to do your fail asynchroniously (by doing a cast) and sleep for a second or two directly after calling it, you will see, nothing bubbles to main, main survives, genserver crashes, supervisor recognises this and restarts the genserver, @g4143 is happy…
And this is precisely why an ‘Application’ in Erlang/Elixir never ever ever runs code, it only starts supervisors and Supervisors starts other Supervisors and/or GenServer (and/or others). All code is running within a supervision tree. Putting code in a main
in an escript is like putting it in an Application, and if an application goes down, so does everything it manages. All code that does stuff should be in a supervision tree, not outside or above it.
But how do you keep main alive long enough for the supervisor’s code to complete? IO.gets?
Note. The point about code running in an application is a revelation to me. I think this was one of the hurdles that keep me from grasping the fundamentals of OTP.
Usually in escripts I have a receive-loop in the main-process which waits for some kind of quit message.