0
votes

I've been learning elixir for only a few weeks, and I want to set up a proper application with it's Application module, Supervisor, and (only one, for the moment) worker.

Days of going through tutorials, documentation, the Elixir forum, and stackoverflow has led me to this understanding:

  • I need to have a module which has use Application
  • I don't need to have a separate module for the Supervisor, calling Supervisor.start_link/2 and storing the returned pid is enough for a simple application.
  • the Application module has a start/2 function which returns {:ok, pid} (which is the return value of the last call in it, Supervisor.start_link(worker_module, options))
  • I need to have a Worker module, which defines child_spec/2, init/1 and start_link/1
  • Supervisor.start_link/2 calls init/1 and then start_link/1 on the worker with the given options
  • init/1 returns a certain value

Now, I have a problem with the last part. According to the error I get, my worker's init function returns a "bad value", but I can't figure out what should be the return value for supervisor.start_link/2 not to fail.

The values I have already tried, when init is defined as init(opts):

  • nil
  • {:ok, self()}
  • {:ok, __MODULE__}
  • {:ok, opts}
  • Task.start_link(fn() -> function_that_actually_does_the_work() end)
  • :normal

This is the error message I get from the logger after the line {:ok, pid} = Supervisor.start_link(MyAppMain.Worker, []):

** (Mix) Could not start application myapp: exited in: MyAppMain.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:bad_return, {MyAppMain.Worker, :init, {:ok, %{}}}}}
            lib/MyAppMain.ex:15: MyAppMain.start/2
            (kernel) application_master.erl:273: :application_master.start_it_old/4

So, what return values does Supervisor.start_link/2 accept?

EDIT: some actual code

Application module:

defmodule MyAppMain do
  use Application

  def start(_type, _args) do
    {:ok, pid} = Supervisor.start_link(MyAppMain.Worker, [])
  end
end

Worker module:

defmodule MyAppMain.Worker do
  def child_spec(opts) do
    %{
       id: MyAppMain.Worker,
       start: {__MODULE__, :start_link, []},
       restart: :transient,
       type: :worker
     }
  end

  def start_link(state) do
    do_work() # returns a "fn() -> nil end", because "expected a function"
  end

  def init(opts) do
    {:ok, %{}} # Also tried putting the elements of the above list here.
  end

  defp do_work()
    #do some work

    if(prompt_restart()) do
      do_work()
    else
      fn() -> nil end
    end
  end

  defp prompt_restart() do
    # prompt the user whether to repeat the task via IO.gets, return either true or false
  end
end
1
Can you post the complete code as well?Dogbert
@Dogbert The complete code is a bit too long for posting it here, but yes, I'll edit the question to include all the necessary parts.sisisisi
Since my last comment probably didn't tag you properly... @Dogbertsisisisi

1 Answers

3
votes

The issue is the way you're setting up your application. You're treating your Application as a Supervisor (it sort of is, but not like you're using it), and the start_link part of your Worker is being used wrong. The Application is really just used to start some top-level supervisors, which themselves start some workers. Here's a basic example:

Application:

defmodule Chat do
  use Application

  def start(_type, _args) do
    Chat.Supervisor.start_link(name: Chat.Supervisor)
  end
end

Supervisor (that is started by Application)

defmodule Chat.Supervisor do
  def start_link(state) do
    Supervisor.start_link(__MODULE__, nil, named: __MODULE__)
  end

  def init(opts) do
    children = [
      %{
        id: Chat.RoomWorker,
        start: {Chat.RoomWorker, :start_link, []},
        restart: :transient,
        type: :worker
     }
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end

Worker (that is started by the Supervisor)

defmodule Chat.RoomWorker do
  use GenServer
  def start_link() do
    GenServer.start_link(__MODULE__, nil, named: __MODULE__)
  end

  def init(opts) do
    {:ok, []}
  end
end

start_link calls return {:ok, pid} when they are successful. The init function happens in the spawned process, and it returns some {:ok, state} response to let the spawner know that is started successfully. The start_link stuff runs in the caller process, and the init code runs in the newly spawned process.

Try to refactor your code so that the application is starting a top-level supervisor, and that supervisor is starting some worker processes. While you can start workers from Application and forgo a Supervisor, it's better to forgo the Application and only use a Supervisor, but it's even better to see how all 3 work together.