Elixir Supervisor останавливается с ошибкой: bad_return - PullRequest
0 голосов
/ 15 февраля 2019

Уже 2 недели я пытался провести полный рефакторинг приложения нашей компании Elixir, потому что у нас было слишком много проблем с процессами.

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

Application |- MainSupervisor |- Some workers (Extreme for EventStore, Repo for PostgreSQL, and a stream subscriber for eventstore) |- AccountStreamSupervisor |- AccountStreamDispatcher (Supervisor) |- StreamSubscriber (Worker)

Диспетчер и подписчик имеют функцию start_child (поэтому они будут использоваться во время выполнения позже)

Я инициализирую свое деревос Supervisor.start_link/2 для каждого руководителя.Приложение, MainSupervisor, AccountStreamSupervisor запускаются без проблем, но когда дело доходит до инициализации AccountStreamDispatcher, у меня появляется эта ошибка :bad_return.

Трассировка говорит, что init/1 AccountStreamDispatcher является проблемой, потому что она возвращает{:ok, #PID<0.392.0> (что, согласно документации, хороший ответ).

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

Вот код:

defmodule MainSupervisor do
  use Supervisor
  require Logger

  def start_link(_args) do
    Logger.info("MainSupervisor => Starting...")

    result = Supervisor.start_link(__MODULE__, name: :main_supervisor)

    case result do
      {:ok, _} ->
        nil

      :ignore ->
        Logger.error("Unable start main supervisor because is ignored")

      {:error, {:already_started, _}} ->
        Logger.error("Unable start main supervisor because is already started")

      {:error, {:shutdown, reason}} ->
        Logger.error("Unable start main supervisor because #{IO.inspect(reason)}")

      {:error, reason} ->
        Logger.error("Unable start main supervisor because #{IO.inspect(reason)}")
    end

    result
    end

    def init(_) do
    Logger.info("MainSupervisor => Initializing...")

    event_store_settings = Application.get_env(:extreme, :event_store)

    children = [
      [...]
      %{
        id: ViewBuilder.V2.AccountStreamSupervisor,
        start: {ViewBuilder.V2.AccountStreamSupervisor, :start_link, []},
        type: :supervisor
      }
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

defmodule AccountStreamSupervisor do
  use Supervisor
  require Logger

  def start_link do
    Logger.info("AccountStreamSupervisor => Starting...")

    result = Supervisor.start_link(__MODULE__, name: :account_supervisor)

    case result do
      {:ok, _} ->
        nil

      :ignore ->
        Logger.error("Unable start account stream supervisor because is ignored")

      {:error, {:already_started, _}} ->
        Logger.error("Unable start account stream supervisor because is already started")

      {:error, {:shutdown, reason}} ->
        Logger.error("Unable start account stream supervisor because #{IO.inspect(reason)}")

      {:error, reason} ->
        Logger.error("Unable start account stream supervisor because #{IO.inspect(reason)}")
    end

    result
  end

  def init(_) do
    Logger.info("AccountStreamSupervisor => Initializing...")

    children = [
  %{
    id: AccountStreamDispatcher,
    start: {AccountStreamDispatcher, :start_link, []},
    type: :supervisor
  }
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end

  def start_child(account_stream_name) do
    Logger.debug(
      "AccountStreamSupervisor => Start a new child - AccountStreamDispatcher with the name: #{
        account_stream_name
      }"
    )

    Supervisor.start_child(:account_supervisor, [])
  end
end

defmodule AccountStreamDispatcher do
  use Supervisor
  require Logger

  def start_link do
    Logger.debug("AccountStreamDispatcher => Starting...")

    result = Supervisor.start_link(__MODULE__, name: :account_dispatcher)
    IO.inspect(result)
    case result do
      {:ok, _} ->
        nil

      :ignore ->
        Logger.error("Unable start dispatcher because is ignored")

      {:error, {:already_started, pid}} ->
        Logger.debug("Dispatcher is already started with pid #{pid}")

      {:error, reason} ->
        Logger.error("Unable start dispatcher because #{IO.inspect(reason)}")
    end

    result
  end

  def init(_) do
    Logger.info("AccountStreamDispatcher => Initializing...")

    children = [
      %{
        id: StreamSubscriber,
        start: {StreamSubscriber, :start_link, []},
        type: :supervisor
      }
    ]

    Supervisor.start_link(children, [strategy: :one_for_one])
  end

  def start_child(account_stream_name, type, account_id, sub_keys) do
    Logger.debug(
      "AccountStreamDispatcher => Start a new child - StreamSubscriber with the name: #{
        account_stream_name
      }"
    )

    Supervisor.start_child(
      :account_dispatcher,
      [
        %{
          stream_name: account_stream_name,
          stream_type: type,
          account_id: account_id,
          sub_keys: sub_keys
        }
      ]
    )
  end
end

defmodule StreamSubscriber do
  use GenServer
  require Logger

  alias EventHandler.EventHandlerProvider, as: EventHandlerProvider

   def start_link(
          args = %{
            stream_name: name,
            stream_type: _type,
            account_id: _account_id,
            sub_keys: _sub_keys
          }
      ) do
    Logger.debug("StreamSubscriber => Starting... (#{name})")

    result = GenServer.start_link(__MODULE__, args, name: name)

    case result do
      {:ok, _} ->
        nil

      :ignore ->
        Logger.error("Unable start process #{name} because is ignored")

      {:error, {:already_started, _}} ->
        Logger.error("Unable start process #{name} because is already started")

      {:error, reason} ->
        Logger.error("Unable start process #{name} because #{IO.inspect(reason)}")
    end

    result
  end

  def init(%{stream_name: name, stream_type: type, account_id: account_id, sub_keys: sub_keys}) do
    Logger.debug("StreamSubscriber => Initializing... (#{name})")

    state = %{stream_name: name, stream_type: type, account_id: account_id, sub_keys: sub_keys}

    {:ok, _} = EventHandlerProvider.create_handler(type, name, account_id, sub_keys)

    {:ok, state}
  end
end

Что я делаю не так?

1 Ответ

0 голосов
/ 17 февраля 2019

Я не думаю, что это правильно:

   children = [
      %{
        id: StreamSubscriber,
        start: {StreamSubscriber, :start_link, []},
        type: :supervisor
      }
    ]

Значение ключа start: сообщает супервизору, как запустить ребенка StreamSubscriber, а вы говорите супервизору, чтобы он позвонил *Функция 1006 * start_link() с аргументом [], но вы определили start_link() в StreamSubscriber следующим образом:

  def start_link(
          args = %{
            stream_name: name,
            stream_type: _type,
            account_id: _account_id,
            sub_keys: _sub_keys
          }
      ) do ...

Но [] не может сопоставить шаблон с картой.

Я пробовал очень много вещей, таких как изменение start_link и сигнатуры методов init,

Может быть, вы опубликовали какой-то неисправный код после попытки решить проблему?

Как только вы получите вызовы функций, соответствующие функциям def, вы можете решить проблему bad_return, выполнив следующее:

Супервизоры на основе модулей
В приведенном выше примере супервизор был запущен путем передачи структуры контроля в start_link / 2.Однако супервизоры также могут быть созданы путем явного определения модуля супервизии:

defmodule MySupervisor do

  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      {Stack, [:hello]}
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end

Разница между этими двумя подходами состоит в том, что супервизор на основе модулей дает вам более прямой контроль над инициализацией супервизора.Вместо того чтобы вызывать Supervisor.start_link/2 со списком дочерних элементов, которые автоматически инициализируются, мы вручную инициализируем дочерние элементы, вызывая Supervisor.init/2 внутри его обратного вызова init / 1.

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

~/elixir_programs/app1$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> MainSupervisor.start_link(1)

20:42:11.324 [info]  MainSupervisor => Starting...

20:42:11.324 [info]  MainSupervisor => Initializing...

20:42:11.327 [info]  AccountStreamSupervisor => Starting...

20:42:11.328 [info]  AccountStreamSupervisor => Initializing...

20:42:11.328 [debug] AccountStreamDispatcher => Starting...

20:42:11.328 [info]  AccountStreamDispatcher => Initializing...

20:42:11.328 [debug] StreamSubscriber => Starting... #(Elixir.StreamSubscriber)

20:42:11.329 [debug] StreamSubscriber => Initializing... (Elixir.StreamSubscriber)
{:ok, #PID<0.214.0>}

iex(2)>
...