Elixir Supervisor 因 :bad_return 错误而停止

Elixir Supervisor stops with :bad_return error

两周以来,我尝试对我们公司的 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)

dispatcher和subscriber有start_child函数(所以后面会在运行时用到)

我用 Supervisor.start_link/2 为每个主管初始化我的树。应用程序、MainSupervisor、AccountStreamSupervisor 启动时没有问题,但是当涉及到要初始化的 AccountStreamDispatcher 时,我有这个 :bad_return 错误。

跟踪表明 AccountStreamDispatcher 的 init/1 是问题所在,因为它 return {:ok, #PID<0.392.0>(根据文档,这是一个很好的响应)。

我尝试了很多东西,比如更改 start_linkinit 方法签名,更改子声明,总是一样。我知道没有我的调度员,一切都正常启动...

这是一些代码:

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

我做错了什么?

我认为这是不正确的:

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

start:键的值告诉Supervisor如何启动子StreamSubscriber,而你是在告诉Supervisor调用StreamSubscriberstart_link()函数使用参数 [],但您在 StreamSubscriber 中定义了 start_link(),如下所示:

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

但是[] 不能模式匹配地图。

I've tried so many things, like change start_link and init method signatures,

也许您在尝试解决问题后发布了一些错误代码?

一旦获得与函数 defs 匹配的函数调用,就可以通过执行以下操作来解决 bad_return 问题:

Module-based supervisors
In the example above, a supervisor was started by passing the supervision structure to start_link/2. However, supervisors can also be created by explicitly defining a supervision module:

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

The difference between the two approaches is that a module-based supervisor gives you more direct control over how the supervisor is initialized. Instead of calling Supervisor.start_link/2 with a list of children that are automatically initialized, we manually initialized the children by calling Supervisor.init/2 inside its init/1 callback.

在你主管的所有 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)>