Как правильно вернуться из инициализации рабочего процесса?

Я изучаю эликсир всего несколько недель и хочу создать подходящее приложение с его модулем приложения, супервизором и (на данный момент только одним) работником.

Дни изучения руководств, документации, форума Elixir и stackoverflow привели меня к такому пониманию:

  • Мне нужен модуль с use Application
  • Мне не нужен отдельный модуль для супервизора, для простого приложения достаточно вызова Supervisor.start_link/2 и сохранения возвращенного pid.
  • модуль приложения имеет функцию start/2, которая возвращает {:ok, pid} (которое является возвращаемым значением последнего вызова в нем, Supervisor.start_link(worker_module, options))
  • Мне нужен рабочий модуль, который определяет child_spec/2, init/1 и start_link/1
  • Supervisor.start_link/2 вызывает init/1, а затем start_link/1 рабочего с заданными параметрами
  • init/1 возвращает определенное значение

Теперь у меня проблема с последней частью. Согласно полученной мной ошибке, функция init моего рабочего возвращает "неверное значение", но я не могу понять, каким должно быть возвращаемое значение, чтобы supervisor.start_link/2 не завершился ошибкой.

Значения, которые я уже пробовал, когда init определен как init(opts):

  • ноль
  • {: хорошо, сам ()}
  • {: хорошо, __MODULE__}
  • {: ok, opts}
  • Task.start_link (fn () -> function_that_actually_does_the_work () конец)
  • :нормальный

Это сообщение об ошибке, которое я получаю от регистратора после строки {: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

Итак, какие возвращаемые значения принимает Supervisor.start_link/2?

РЕДАКТИРОВАТЬ: какой-то фактический код

Модуль приложения:

defmodule MyAppMain do
  use Application

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

Рабочий модуль:

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

person sisisisi    schedule 28.10.2017    source источник
comment
Можете ли вы опубликовать и полный код?   -  person Dogbert    schedule 29.10.2017
comment
@Dogbert Полный код слишком длинный, чтобы размещать его здесь, но да, я отредактирую вопрос, чтобы включить все необходимые части.   -  person sisisisi    schedule 29.10.2017
comment
Поскольку мой последний комментарий, вероятно, не пометил вас должным образом ... @Dogbert   -  person sisisisi    schedule 29.10.2017


Ответы (1)


Проблема в том, как вы настраиваете свое приложение. Вы относитесь к своему приложению как к супервизору (вроде как, но не так, как вы его используете), и часть start_link вашего Worker используется неправильно. Приложение действительно просто используется для запуска некоторых супервизоров верхнего уровня, которые сами запускают некоторых рабочих. Вот простой пример:

Применение:

defmodule Chat do
  use Application

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

Супервизор (запускается приложением)

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

Рабочий (запускается Супервайзером)

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 вызовы возвращают {:ok, pid}, когда они успешны. Функция init выполняется в порожденном процессе и возвращает некоторый ответ {:ok, state}, чтобы средство создания узнало, что он запущен успешно. Материал start_link выполняется в вызывающем процессе, а код init выполняется во вновь созданном процессе.

Попробуйте реорганизовать свой код так, чтобы приложение запускало супервизор верхнего уровня, а этот супервизор запускал некоторые рабочие процессы. Хотя вы можете запускать воркеры из приложения и отказаться от супервизора, лучше отказаться от приложения и использовать только супервизора, но еще лучше увидеть, как все 3 работают вместе.

person Peter R    schedule 29.10.2017