Escript+Supervisor+Genserver

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?

1 Like

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. :slight_smile:

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.

  1. 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.

1 Like

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? :slight_smile:

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. :slight_smile:

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.

1 Like