umbrella 应用程序中的监督树冲突
Supervision tree conflict in an umbrella app
背景
我有一个综合应用,里面有很多小应用。其中一个名为 A
的应用程序需要能够旋转和监督另一个名为 B
的应用程序。
B
,本身就是一个应用程序,公开了一个 public API 并有一个 GenServer,负责接收请求,然后将其重定向到逻辑模块等.
问题
所以,我有两个要求:
- 我必须能够独立启动
B
并将其作为普通的独立应用程序运行。
A
必须能够在其子代中具有 B
和 restart/manage 它,如果出现这种需要。
我在这里遇到的问题是,使用我的代码我可以实现 1 或 2,但不能同时实现。
代码
所以,下面是应用B
的重要代码:
application.ex
defmodule B.Application do
@moduledoc false
use Application
alias B.Server
alias Plug.Cowboy
@test_port 8082
@spec start(any, nil | maybe_improper_list | map) :: {:error, any} | {:ok, pid}
def start(_type, args) do
# B.Server is a module containing GenServer logic and callbacks
children = children([Server])
opts = [strategy: :one_for_one, name: B.Supervisor]
Supervisor.start_link(children, opts)
end
end
server.ex(简体)
defmodule B.Server do
use GenServer
alias B.HTTPClient
#############
# Callbacks #
#############
@spec start_link(any) :: :ignore | {:error, any} | {:ok, pid}
def start_link(_args), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
@impl GenServer
@spec init(nil) :: {:ok, %{}}
def init(nil), do: {:ok, %{}}
@impl GenServer
def handle_call({:place_order, order}, _from, _state), do:
{:reply, HTTPClient.place_order(order), %{}}
@impl GenServer
def handle_call({:delete_order, order_id}, _from, _state), do:
{:reply, HTTPClient.delete_order(order_id), %{}}
@impl GenServer
def handle_call({:get_all_orders, item_name}, _from, _state), do:
{:reply, HTTPClient.get_all_orders(item_name), %{}}
##############
# Public API #
##############
def get_all_orders(item_name), do:
GenServer.call(__MODULE__, {:get_all_orders, item_name})
def place_order(order), do:
GenServer.call(__MODULE__, {:place_order, order})
def delete_order(order_id), do:
GenServer.call(__MODULE__, {:delete_order, order_id})
end
这里是 B
的入口点
b.ex
defmodule B do
@moduledoc """
Port for http client.
"""
alias B.Server
defdelegate place_order(order), to: Server
defdelegate delete_order(order_id), to: Server
defdelegate get_all_orders(item_name), to: Server
@doc false
defdelegate child_spec(args), to: Server
end
b.ex
基本上是服务器的外观,带有一些额外的上下文信息,例如规范、类型定义等(为简洁起见,此处省略)。
A
如何管理生命周期?
据我了解,监督树是在应用程序的 application.ex
文件中指定的。因此,根据我的理解,我为 A
:
创建了这个应用程序文件
defmodule A.Application do
@moduledoc false
use Application
alias B
def start(_type, _args) do
children = [B]
opts = [strategy: :one_for_one, name: A.Supervisor]
Supervisor.start_link(children, opts)
end
end
应该有效,但无效。
在 A
的文件夹中时,如果我 运行 iex -S mix
,我没有顺利启动,而是收到以下错误:
** (Mix) Could not start application a: A.Application.start(:normal, []) returned an error: shutdown: failed to start child: B.Server
** (EXIT) already started: #PID<0.329.0>
我目前对该问题的理解是 A
的 application.ex 文件与 B
的应用程序文件冲突。
问题
- 如何解决此冲突?
如果我对要求的理解正确,A
希望最终停止 B
应用程序并重新生成 B
受监督的进程,
Application.stop/1
正是这样做的。
defmodule A.Application do
...
def start(_type, _args) do
Application.stop(:b) # ⇐ THIS
children = [B]
opts = [strategy: :one_for_one, name: A.Supervisor]
Supervisor.start_link(children, opts)
end
end
请注意,它需要应用程序的名称,如 mix.exs
文件中所示(以及稍后在编译后的 ×××.app
文件中。)
背景
我有一个综合应用,里面有很多小应用。其中一个名为 A
的应用程序需要能够旋转和监督另一个名为 B
的应用程序。
B
,本身就是一个应用程序,公开了一个 public API 并有一个 GenServer,负责接收请求,然后将其重定向到逻辑模块等.
问题
所以,我有两个要求:
- 我必须能够独立启动
B
并将其作为普通的独立应用程序运行。 A
必须能够在其子代中具有B
和 restart/manage 它,如果出现这种需要。
我在这里遇到的问题是,使用我的代码我可以实现 1 或 2,但不能同时实现。
代码
所以,下面是应用B
的重要代码:
application.ex
defmodule B.Application do
@moduledoc false
use Application
alias B.Server
alias Plug.Cowboy
@test_port 8082
@spec start(any, nil | maybe_improper_list | map) :: {:error, any} | {:ok, pid}
def start(_type, args) do
# B.Server is a module containing GenServer logic and callbacks
children = children([Server])
opts = [strategy: :one_for_one, name: B.Supervisor]
Supervisor.start_link(children, opts)
end
end
server.ex(简体)
defmodule B.Server do
use GenServer
alias B.HTTPClient
#############
# Callbacks #
#############
@spec start_link(any) :: :ignore | {:error, any} | {:ok, pid}
def start_link(_args), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
@impl GenServer
@spec init(nil) :: {:ok, %{}}
def init(nil), do: {:ok, %{}}
@impl GenServer
def handle_call({:place_order, order}, _from, _state), do:
{:reply, HTTPClient.place_order(order), %{}}
@impl GenServer
def handle_call({:delete_order, order_id}, _from, _state), do:
{:reply, HTTPClient.delete_order(order_id), %{}}
@impl GenServer
def handle_call({:get_all_orders, item_name}, _from, _state), do:
{:reply, HTTPClient.get_all_orders(item_name), %{}}
##############
# Public API #
##############
def get_all_orders(item_name), do:
GenServer.call(__MODULE__, {:get_all_orders, item_name})
def place_order(order), do:
GenServer.call(__MODULE__, {:place_order, order})
def delete_order(order_id), do:
GenServer.call(__MODULE__, {:delete_order, order_id})
end
这里是 B
b.ex
defmodule B do
@moduledoc """
Port for http client.
"""
alias B.Server
defdelegate place_order(order), to: Server
defdelegate delete_order(order_id), to: Server
defdelegate get_all_orders(item_name), to: Server
@doc false
defdelegate child_spec(args), to: Server
end
b.ex
基本上是服务器的外观,带有一些额外的上下文信息,例如规范、类型定义等(为简洁起见,此处省略)。
A
如何管理生命周期?
据我了解,监督树是在应用程序的 application.ex
文件中指定的。因此,根据我的理解,我为 A
:
defmodule A.Application do
@moduledoc false
use Application
alias B
def start(_type, _args) do
children = [B]
opts = [strategy: :one_for_one, name: A.Supervisor]
Supervisor.start_link(children, opts)
end
end
应该有效,但无效。
在 A
的文件夹中时,如果我 运行 iex -S mix
,我没有顺利启动,而是收到以下错误:
** (Mix) Could not start application a: A.Application.start(:normal, []) returned an error: shutdown: failed to start child: B.Server
** (EXIT) already started: #PID<0.329.0>
我目前对该问题的理解是 A
的 application.ex 文件与 B
的应用程序文件冲突。
问题
- 如何解决此冲突?
如果我对要求的理解正确,A
希望最终停止 B
应用程序并重新生成 B
受监督的进程,
Application.stop/1
正是这样做的。
defmodule A.Application do
...
def start(_type, _args) do
Application.stop(:b) # ⇐ THIS
children = [B]
opts = [strategy: :one_for_one, name: A.Supervisor]
Supervisor.start_link(children, opts)
end
end
请注意,它需要应用程序的名称,如 mix.exs
文件中所示(以及稍后在编译后的 ×××.app
文件中。)