查看源代码 supervisor 行为 (stdlib v6.2)
通用 supervisor 行为。
此行为模块提供了一个 supervisor,这是一个监控其他进程(称为子进程)的进程。子进程可以是另一个 supervisor 或工作进程。工作进程通常使用 gen_event
、 gen_server
或 gen_statem
行为之一来实现。使用此模块实现的 supervisor 具有一组标准的接口函数,并包含用于跟踪和错误报告的功能。Supervisor 用于构建一个称为监管树的层次进程结构,这是一种构建容错应用程序的好方法。有关详细信息,请参阅 OTP 设计原则中的 Supervisor 行为。
supervisor 希望在导出预定义函数集的回调模块中指定要监管的子进程的定义。
除非另有说明,否则如果指定的 supervisor 不存在或指定了错误的参数,则此模块中的所有函数都将失败。
监管原则
supervisor 负责启动、停止和监视其子进程。supervisor 的基本思想是,它必须通过在必要时重新启动子进程来保持子进程的活动状态。
supervisor 的子进程被定义为子进程规范的列表。当 supervisor 启动时,子进程会按照此列表从左到右的顺序启动。当 supervisor 即将终止时,它首先按照相反的启动顺序(从右到左)终止其子进程。
Supervisor 标志
supervisor 的属性由 supervisor 标志定义。supervisor 标志的类型定义如下
sup_flags() = #{strategy => strategy(), % optional
intensity => non_neg_integer(), % optional
period => pos_integer(), % optional
auto_shutdown => auto_shutdown()} % optional
重启策略
supervisor 可以使用上面映射中的 strategy
键指定以下重启策略之一
one_for_one
- 如果一个子进程终止并要重新启动,则只会影响该子进程。这是默认的重启策略。one_for_all
- 如果一个子进程终止并要重新启动,则所有其他子进程都会终止,然后重新启动所有子进程。rest_for_one
- 如果一个子进程终止并要重新启动,则终止“其余”的子进程(即,启动顺序中终止的子进程之后的子进程)。然后重新启动终止的子进程及其后的所有子进程。simple_one_for_one
- 简化的one_for_one
supervisor,其中所有子进程都是相同进程类型的动态添加实例,即运行相同的代码。函数
delete_child/2
和restart_child/2
对于simple_one_for_one
supervisor 无效,如果指定的 supervisor 使用此重启策略,则返回{error,simple_one_for_one}
。可以通过指定子进程的
pid/0
作为第二个参数来使用函数terminate_child/2
处理simple_one_for_one
supervisor 下的子进程。如果改为使用子进程规范标识符,则terminate_child/2
返回{error,simple_one_for_one}
。由于
simple_one_for_one
supervisor 可以有多个子进程,因此它会异步关闭所有子进程。这意味着子进程并行执行清理,因此停止它们的顺序未定义。
重启强度和周期
为了防止 supervisor 进入子进程终止和重启的无限循环,使用上面映射中的键 intensity
和 period
指定两个整数值来定义最大重启强度。假设 intensity
的值为 MaxR
,period
的值为 MaxT
,那么如果在 MaxT
秒内发生超过 MaxR
次重启,则 supervisor 会终止所有子进程,然后终止自身。在这种情况下,supervisor 自身的终止原因是 shutdown
。intensity
默认为 1
,period
默认为 5
。
自动关闭
可以配置 supervisor 在 重要子进程 以 shutdown
的退出原因终止时,使用上面映射中的 auto_shutdown
键自动关闭自身
never
- 禁用自动关闭。这是默认设置。当
auto_shutdown
设置为never
时,将significant
标志设置为true
的子进程规范被认为是无效的,并且会被拒绝。any_significant
- 当任何重要子进程终止时,supervisor 将关闭自身,即当transient
重要子进程正常终止,或者当temporary
重要子进程正常或异常终止时。all_significant
- 当所有重要子进程都终止时,supervisor 将关闭自身,即当最后一个活动重要子进程终止时。与any_significant
相同的规则适用。
有关详细信息,请参阅 OTP 设计原则中 Supervisor 行为部分的 自动关闭。
警告
自动关闭功能出现在 OTP 24.0 中,但使用此功能的应用程序也可以使用较旧的 OTP 版本进行编译和运行。
但是,当使用早于自动关闭功能出现的 OTP 版本编译此类应用程序时,会发生进程泄漏,因为它们所依赖的自动关闭不会发生。
如果实现者预计他们的应用程序可能会使用较旧的 OTP 版本进行编译,则由他们采取适当的预防措施。
子进程规范
子进程规范的类型定义如下
child_spec() = #{id => child_id(), % mandatory
start => mfargs(), % mandatory
restart => restart(), % optional
significant => significant(), % optional
shutdown => shutdown(), % optional
type => worker(), % optional
modules => modules()} % optional
旧的元组格式保留用于向后兼容,请参阅 child_spec/0
,但首选映射。
id
由 supervisor 在内部用于标识子进程规范。id
键是强制性的。请注意,此标识符有时被称为“name”。在可能的情况下,现在使用术语“identifier”或“id”,但为了保持向后兼容性,仍然可以找到一些“name”的出现,例如在错误消息中。
start
定义用于启动子进程的函数调用。它必须是一个模块-函数-参数元组{M,F,A}
,用作apply(M,F,A)
。启动函数必须创建并链接到子进程,并且必须返回
{ok,Child}
或{ok,Child,Info}
,其中Child
是子进程的 pid,Info
是任何被 supervisor 忽略的项。如果由于某种原因无法启动子进程,则启动函数也可以返回
ignore
,在这种情况下,supervisor 会保留子进程规范(除非它是临时子进程),但会忽略不存在的子进程。如果出现问题,该函数还可以返回错误元组
{error,Error}
。请注意,不同行为模块的
start_link
函数满足上述要求。start
键是强制性的。restart
定义何时必须重新启动已终止的子进程。permanent
子进程始终重新启动。temporary
子进程永远不会重新启动(即使 supervisor 的重启策略是rest_for_one
或one_for_all
并且同级的死亡导致临时进程终止)。仅当transient
子进程异常终止时,才重新启动它,即,使用normal
、shutdown
或{shutdown,Term}
之外的其他退出原因。restart
键是可选的。如果未指定,则默认为permanent
。significant
定义对于 supervisor 的 自动自关闭,是否认为子进程是重要的。当 重启类型 为
permanent
时,将此选项设置为true
无效。此外,当auto_shutdown
supervisor 标志设置为never
时,在此 supervisor 中启动将此选项设置为true
的子进程也被认为是无效的。significant
键是可选的。如果未指定,则默认为false
。shutdown
定义如何终止子进程。brutal_kill
表示使用exit(Child,kill)
无条件终止子进程。整数超时值表示 supervisor 通过调用exit(Child,shutdown)
来告诉子进程终止,然后等待子进程返回带有shutdown
原因的退出信号。如果在指定的毫秒数内未收到退出信号,则使用exit(Child,kill)
无条件终止子进程。如果子进程是另一个 supervisor,则必须将关闭时间设置为
infinity
,以给子树充分的时间关闭。警告
对于类型为
supervisor
的子进程,如果将其关闭时间设置为除infinity
以外的任何值,都可能导致竞态条件:该子进程会取消链接自己的子进程,但在被终止前未能将其终止。如果子进程是 worker,则允许将其关闭时间设置为
infinity
。警告
当子进程是 worker 时,设置关闭时间为
infinity
时要小心。因为在这种情况下,监督树的终止取决于子进程,因此必须以安全的方式实现,并且其清理过程必须始终返回。请注意,所有使用标准 OTP 行为模块实现的子进程都会自动遵循关闭协议。
shutdown
键是可选的。如果未指定,则当子进程类型为worker
时,默认为5000
;当子进程类型为supervisor
时,默认为infinity
。type
指定子进程是 supervisor 还是 worker。type
键是可选的。如果未指定,则默认为worker
。modules
在代码替换期间被 release 处理程序用来确定哪些进程正在使用特定的模块。一般而言,如果子进程是supervisor
、gen_server
或gen_statem
,则此值应为一个包含一个元素的列表[Module]
,其中Module
是回调模块。如果子进程是一个事件管理器 (gen_event
),并且具有动态的回调模块集,则必须使用值dynamic
。有关 release 处理的更多信息,请参阅 OTP 设计原则中的 Release Handling。modules
键是可选的。如果未指定,则默认为[M]
,其中M
来自子进程的启动{M,F,A}
。在内部,supervisor 还会跟踪子进程的 pid
Child
,如果没有 pid,则为undefined
。
另请参阅
摘要
类型
不是 pid/0
。
保留元组格式仅为了向后兼容。建议使用映射;请参阅上方的详细信息。
对于 A
(参数列表),值 undefined
仅在 supervisor
内部使用。如果子进程的重启类型为 temporary
,则该进程永远不会重启,因此无需存储真实的参数列表。此时存储值 undefined
。
保留元组格式仅为了向后兼容。建议使用映射;请参阅上方的详细信息。
启动 supervisor
时要使用的名称规范。请参阅函数 start_link/2,3
和类型 sup_ref/0
。
用于寻址 supervisor
时要使用的 supervisor 规范。请参阅 count_children/1
, delete_child/2
, get_childspec/2
, restart_child/2
, start_child/2
, terminate_child/2
, which_children/1
和类型 sup_name/0
。
回调
每当使用 start_link/2,3
启动 supervisor 时,新进程都会调用此函数来了解重启策略、最大重启强度和子规范。
函数
接受子规范列表作为参数,如果所有子规范在语法上都正确,则返回 ok
,否则返回 {error,Error}
。
返回一个 属性列表,其中包含 supervisor 的子规范和托管进程的以下每个元素的计数。
告诉 supervisor SupRef
删除由 Id
标识的子规范。相应的子进程不得正在运行。使用 terminate_child/2
来终止它。
返回 supervisor SupRef
下由 Id
标识的子规范的映射。返回的映射包含所有键,包括强制键和可选键。
告诉 supervisor SupRef
重启与由 Id
标识的子规范相对应的子进程。子规范必须存在,并且相应的子进程不得正在运行。
将子规范动态添加到 supervisor SupRef
,这将启动相应的子进程。
创建作为监督树一部分的无名称 supervisor 进程。
创建作为监督树一部分的 supervisor 进程。
告诉 supervisor SupRef
终止指定的子进程。
返回一个新创建的列表,其中包含有关 supervisor SupRef
的所有子规范和子进程的信息。
类型
-type auto_shutdown() :: never | any_significant | all_significant.
-type child() :: undefined | pid().
-type child_id() :: term().
不是 pid/0
。
-type child_spec() :: #{id := child_id(), start := mfargs(), restart => restart(), significant => significant(), shutdown => shutdown(), type => worker(), modules => modules()} | {Id :: child_id(), StartFunc :: mfargs(), Restart :: restart(), Shutdown :: shutdown(), Type :: worker(), Modules :: modules()}.
保留元组格式仅为了向后兼容。建议使用映射;请参阅上方的详细信息。
对于 A
(参数列表),值 undefined
仅在 supervisor
内部使用。如果子进程的重启类型为 temporary
,则该进程永远不会重启,因此无需存储真实的参数列表。此时存储值 undefined
。
-type modules() :: [module()] | dynamic.
-type restart() :: permanent | transient | temporary.
-type shutdown() :: brutal_kill | timeout().
-type significant() :: boolean().
-type startchild_ret() :: {ok, Child :: child()} | {ok, Child :: child(), Info :: term()} | {error, startchild_err()}.
-type startlink_ret() :: {ok, pid()} | ignore | {error, startlink_err()}.
-type strategy() :: one_for_all | one_for_one | rest_for_one | simple_one_for_one.
-type sup_flags() :: #{strategy => strategy(), intensity => non_neg_integer(), period => pos_integer(), auto_shutdown => auto_shutdown()} | {RestartStrategy :: strategy(), Intensity :: non_neg_integer(), Period :: pos_integer()}.
保留元组格式仅为了向后兼容。建议使用映射;请参阅上方的详细信息。
-type sup_name() :: {local, Name :: atom()} | {global, Name :: term()} | {via, Module :: module(), Name :: any()}.
启动 supervisor
时要使用的名称规范。请参阅函数 start_link/2,3
和类型 sup_ref/0
。
{local,LocalName}
- 使用register/2
将supervisor
在本地注册为LocalName
。{global,GlobalName}
- 使用global:register_name/2
将supervisor
进程 ID 全局注册为GlobalName
。{via,RegMod,ViaName}
- 在由RegMod
表示的注册表中注册supervisor
进程。RegMod
回调应导出函数register_name/2
、unregister_name/1
、whereis_name/1
和send/2
,这些函数的行为应与global
中的相应函数类似。因此,{via,global,GlobalName}
是与{global,GlobalName}
等效的有效引用。
-type sup_ref() :: (Name :: atom()) | {Name :: atom(), Node :: node()} | {global, Name :: term()} | {via, Module :: module(), Name :: any()} | pid().
用于寻址 supervisor
时要使用的 supervisor 规范。请参阅 count_children/1
, delete_child/2
, get_childspec/2
, restart_child/2
, start_child/2
, terminate_child/2
, which_children/1
和类型 sup_name/0
。
可以是
pid/0
-supervisor
的进程标识符。LocalName
-supervisor
在本地使用register/2
注册为LocalName
。{Name,Node}
-supervisor
在另一个节点上本地注册。{global,GlobalName}
-supervisor
在global
中全局注册。{via,RegMod,ViaName}
-supervisor
在替代进程注册表中注册。注册表回调模块RegMod
应导出函数register_name/2
、unregister_name/1
、whereis_name/1
和send/2
,这些函数的行为应与global
中的相应函数类似。因此,{via,global,GlobalName}
与{global,GlobalName}
相同。
-type worker() :: worker | supervisor.
回调
-callback init(Args :: term()) -> {ok, {SupFlags :: sup_flags(), [ChildSpec :: child_spec()]}} | ignore.
每当使用 start_link/2,3
启动 supervisor 时,新进程都会调用此函数来了解重启策略、最大重启强度和子规范。
Args
是提供给启动函数的 Args
参数。
SupFlags
是主管标志,定义了主管的重启策略和最大重启强度。[ChildSpec]
是有效子进程规范的列表,定义了主管必须启动和监控的子进程。请参阅前面 监管原则
部分的讨论。
请注意,当重启策略为 simple_one_for_one
时,子进程规范列表必须是只包含一个子进程规范的列表。(子进程规范标识符将被忽略。)在初始化阶段不会启动任何子进程,而是假定所有子进程都使用 start_child/2
动态启动。
该函数还可以返回 ignore
。
请注意,此函数也可以作为代码升级过程的一部分被调用。因此,该函数不应有任何副作用。有关主管的代码升级的更多信息,请参阅 OTP 设计原则中的 更改主管 部分。
函数
-spec check_childspecs(ChildSpecs) -> Result when ChildSpecs :: [child_spec()], Result :: ok | {error, Error :: term()}.
-spec check_childspecs(ChildSpecs, AutoShutdown) -> Result when ChildSpecs :: [child_spec()], AutoShutdown :: undefined | auto_shutdown(), Result :: ok | {error, Error :: term()}.
接受子规范列表作为参数,如果所有子规范在语法上都正确,则返回 ok
,否则返回 {error,Error}
。
如果 AutoShutdown
参数不是 undefined
,还会检查给定的 auto_shutdown 选项是否允许子进程规范。
-spec count_children(SupRef) -> PropListOfCounts when SupRef :: sup_ref(), PropListOfCounts :: [Count], Count :: {specs, ChildSpecCount :: non_neg_integer()} | {active, ActiveProcessCount :: non_neg_integer()} | {supervisors, ChildSupervisorCount :: non_neg_integer()} | {workers, ChildWorkerCount :: non_neg_integer()}.
返回一个 属性列表,其中包含 supervisor 的子规范和托管进程的以下每个元素的计数。
specs
- 子进程的总数,包括已死亡的和存活的。active
- 此主管管理的所有正在运行的子进程的计数。对于simple_one_for_one
主管,不会检查以确保每个子进程仍然存活,尽管此处提供的结果很可能非常准确,除非主管负载过重。supervisors
- 在规范列表中标记为child_type = supervisor
的所有子进程的计数,无论子进程是否仍然存活。workers
- 在规范列表中标记为child_type = worker
的所有子进程的计数,无论子进程是否仍然存活。
-spec delete_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), Result :: ok | {error, Error}, Error :: running | restarting | not_found | simple_one_for_one.
告诉 supervisor SupRef
删除由 Id
标识的子规范。相应的子进程不得正在运行。使用 terminate_child/2
来终止它。
如果成功,该函数返回 ok
。如果由 Id
标识的子进程规范存在,但相应的子进程正在运行或即将重启,则该函数分别返回 {error,running}
或 {error,restarting}
。如果由 Id
标识的子进程规范不存在,则该函数返回 {error,not_found}
。
-spec get_childspec(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: pid() | child_id(), Result :: {ok, child_spec()} | {error, Error}, Error :: not_found.
返回 supervisor SupRef
下由 Id
标识的子规范的映射。返回的映射包含所有键,包括强制键和可选键。
-spec restart_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), Result :: {ok, Child :: child()} | {ok, Child :: child(), Info :: term()} | {error, Error}, Error :: running | restarting | not_found | simple_one_for_one | term().
告诉 supervisor SupRef
重启与由 Id
标识的子规范相对应的子进程。子规范必须存在,并且相应的子进程不得正在运行。
请注意,对于临时子进程,当子进程终止时,子进程规范会自动删除;因此,无法重启此类子进程。
如果由 Id
标识的子进程规范不存在,则该函数返回 {error,not_found}
。如果子进程规范存在但相应的进程已在运行,则该函数返回 {error,running}
。
如果子进程启动函数返回 {ok,Child}
或 {ok,Child,Info}
,则 pid 将添加到主管,并且该函数返回相同的值。
如果子进程启动函数返回 ignore
,则 pid 仍设置为 undefined
,并且该函数返回 {ok,undefined}
。
如果子进程启动函数返回错误元组或错误值,或者如果启动失败,则该函数返回 {error,Error}
,其中 Error
是包含有关错误的 Term。
-spec start_child(SupRef, ChildSpec) -> startchild_ret() when SupRef :: sup_ref(), ChildSpec :: child_spec(); (SupRef, ExtraArgs) -> startchild_ret() when SupRef :: sup_ref(), ExtraArgs :: [term()].
将子规范动态添加到 supervisor SupRef
,这将启动相应的子进程。
对于 one_for_one
、one_for_all
和 rest_for_one
主管,第二个参数必须是有效的子进程规范 ChildSpec
。子进程通过使用子进程规范中定义的启动函数启动。
对于 simple_one_for_one
主管,使用 Module:init/1
中定义的子进程规范,并且第二个参数必须是任意的 Term 列表 ExtraArgs
。然后通过将 ExtraArgs
附加到现有的启动函数参数来启动子进程,即通过调用 apply(M, F, A++ExtraArgs)
,其中 {M,F,A}
是子进程规范中定义的启动函数。
- 如果已存在具有指定标识符的子进程规范,则会丢弃
ChildSpec
,并且该函数返回{error,already_present}
或{error,{already_started,Child}}
,具体取决于相应的子进程是否正在运行。 - 如果子进程启动函数返回
{ok,Child}
或{ok,Child,Info}
,则子进程规范和 pid 将添加到主管,并且该函数返回相同的值。 - 如果子进程启动函数返回
ignore
,则如果它是one_for_one
、one_for_all
或rest_for_one
主管,则子进程规范ChildSpec
将添加到主管,并且 pid 设置为undefined
。对于simple_one_for_one
主管,不会向主管添加任何子进程。该函数返回{ok,undefined}
。
如果子进程启动函数返回错误元组或错误值,或者如果启动失败,则会丢弃子进程规范,并且该函数返回 {error,Error}
,其中 Error
是包含有关错误和子进程规范的 Term。
-spec start_link(Module, Args) -> startlink_ret() when Module :: module(), Args :: term().
创建作为监督树一部分的无名称 supervisor 进程。
等效于 start_link/3
,除了主管进程未进行 register
。
-spec start_link(SupName, Module, Args) -> startlink_ret() when SupName :: sup_name(), Module :: module(), Args :: term().
创建作为监督树一部分的 supervisor 进程。
例如,该函数确保主管链接到调用进程(其主管)。
创建的主管进程调用 Module:init/1
以了解重启策略、最大重启强度和子进程。为了确保同步启动过程,在 Module:init/1
返回并且所有子进程都已启动之前,start_link/2,3
不会返回。
- 如果
SupName={local,Name}
,则使用register/2
将主管本地注册为Name
。 - 如果
SupName={global,Name}
,则使用global:register_name/2
将主管全局注册为Name
。 - 如果
SupName={via,Module,Name}
,则使用Module
表示的注册表将主管注册为Name
。Module
回调必须导出函数register_name/2
、unregister_name/1
和send/2
,这些函数的行为必须类似于global
中的相应函数。因此,{via,global,Name}
是有效的引用。
Module
是回调模块的名称。
Args
是作为参数传递给 Module:init/1
的任何 Term。
- 如果成功创建了主管及其子进程(即,如果所有子进程启动函数都返回
{ok,Child}
、{ok,Child,Info}
或ignore
),则该函数返回{ok,Pid}
,其中Pid
是主管的 pid。 - 如果已存在具有指定的
SupName
的进程,则该函数返回{error,{already_started,Pid}}
,其中Pid
是该进程的 pid。 - 如果
Module:init/1
返回ignore
,则此函数也会返回ignore
,并且主管以原因normal
终止。 - 如果
Module:init/1
失败或返回不正确的值,则此函数返回{error,Term}
,其中Term
是包含有关错误的 Term,并且主管以原因Term
终止。 - 如果任何子进程启动函数失败或返回错误元组或错误值,则主管首先以原因
shutdown
终止所有已启动的子进程,然后终止自身并返回{error, {shutdown, Reason}}
。
-spec terminate_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: pid() | child_id(), Result :: ok | {error, Error}, Error :: not_found | simple_one_for_one.
告诉 supervisor SupRef
终止指定的子进程。
如果主管不是 simple_one_for_one
,则 Id
必须是子进程规范标识符。如果有进程,则会终止该进程,除非它是临时子进程,否则主管会保留子进程规范。子进程稍后可以由主管重启。也可以通过调用 restart_child/2
显式重启子进程。使用 delete_child/2
删除子进程规范。
如果子进程是临时的,则一旦进程终止,就会删除子进程规范。这意味着 delete_child/2
没有意义,并且 restart_child/2
不能用于这些子进程。
如果主管是 simple_one_for_one
,则 Id
必须是子进程的 pid/0
。如果指定的进程处于活动状态,但不是指定主管的子进程,则该函数返回 {error,not_found}
。如果指定了子进程规范标识符而不是 pid/0
,则该函数返回 {error,simple_one_for_one}
。
如果成功,则该函数返回 ok
。如果没有具有指定 Id
的子进程规范,则该函数返回 {error,not_found}
。
-spec which_children(SupRef) -> [{Id, Child, Type, Modules}] when SupRef :: sup_ref(), Id :: child_id() | undefined, Child :: child() | restarting, Type :: worker(), Modules :: modules().
返回一个新创建的列表,其中包含有关 supervisor SupRef
的所有子规范和子进程的信息。
请注意,在低内存条件下监督许多子进程时调用此函数可能会导致内存不足异常。
为每个子进程规范/进程提供以下信息
Id
- 如子进程规范中定义,或者对于simple_one_for_one
主管为undefined
。Child
- 相应子进程的 pid,如果进程即将重启,则为原子restarting
,如果没有此类进程,则为undefined
。Type
- 如子进程规范中定义。Modules
- 如子进程规范中定义。