【问题标题】:Elixir Supervisor stops with :bad_return errorElixir Supervisor 因 :bad_return 错误而停止
【发布时间】:2019-07-09 21:26:43
【问题描述】:

两周以来,我试图对我们公司的 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 是问题所在,因为它返回 {: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

我做错了什么?

【问题讨论】:

    标签: elixir erlang-supervisor


    【解决方案1】:

    我不认为这是正确的:

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

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

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

    [] 无法与 Map 进行模式匹配。

    我尝试了很多东西,比如更改 start_link 和 init 方法签名,

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

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

    Module-based supervisors
    在上面的示例中,通过将监督结构传递给 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)>
    

    【讨论】:

    • 感谢您的回复!对于参数部分,使用地图,我知道这是不正确的(我尝试过不使用模式匹配(空列表)但它也没有工作)。对于您的基于模块的解释,我现在将尝试此操作,并在获得结果后回复您。
    • 我终于成功了,我的问题是start_linkdefinition 有多个参数。您必须先使用数组。
    • 我还使用DynamicSupervisor 类型而不是Supervisor 来初始化我的子主管。
    猜你喜欢
    • 2017-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-26
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多