查看源码 appup
应用升级文件
描述
应用升级文件 定义了如何在运行的系统中升级或降级应用程序。
此文件由 systools
中的函数在生成发布升级文件 relup
时使用。
文件语法
应用升级文件应命名为 Application.appup
,其中 Application
是应用程序的名称。该文件应位于应用程序的 ebin
目录中。
.appup
文件包含一个单独的 Erlang 项,该项定义了用于升级或降级应用程序的指令。该文件具有以下语法:
{Vsn,
[{UpFromVsn, Instructions}, ...],
[{DownToVsn, Instructions}, ...]}.
Vsn = string()
- 当前应用程序版本。UpFromVsn = string() | binary()
- 要升级的早期应用程序版本。如果它是字符串,则将其解释为特定的版本号。如果它是二进制文件,则将其解释为可以匹配多个版本号的正则表达式。DownToVsn = string() | binary()
- 要降级的早期应用程序版本。如果它是字符串,则将其解释为特定的版本号。如果它是二进制文件,则将其解释为可以匹配多个版本号的正则表达式。Instructions
- 发布升级指令列表,请参阅 发布升级指令。建议仅使用高级指令。在创建relup
文件时,systools
会自动将这些指令转换为低级指令。
为了避免重复升级指令,允许使用正则表达式来指定 UpFromVsn
和 DownToVsn
。要被视为正则表达式,版本标识符必须指定为二进制文件。例如,以下内容匹配所有版本 2.1.x
,其中 x
是任意数字
<<"2\\.1\\.[0-9]+">>
请注意,正则表达式必须匹配完整的版本字符串,因此此示例适用于例如 2.1.1
,但不适用于 2.1.1.1
。
发布升级指令
发布升级指令由发布处理程序在进行升级或降级时解释。有关发布处理的更多信息,请参阅 系统文档 中的 OTP 设计原则。
如果 Mod
列在用于启动进程的子规范的 Modules
部分中,则称进程使用模块 Mod
,请参阅 supervisor
。在 gen_event
的情况下,如果 Mod
是已安装的事件处理程序,则称事件管理器进程使用 Mod
。
高级指令
{update, Mod}
{update, Mod, supervisor}
{update, Mod, Change}
{update, Mod, DepMods}
{update, Mod, Change, DepMods}
{update, Mod, Change, PrePurge, PostPurge, DepMods}
{update, Mod, Timeout, Change, PrePurge, PostPurge, DepMods}
{update, Mod, ModType, Timeout, Change, PrePurge, PostPurge, DepMods}
Mod = atom()
ModType = static | dynamic
Timeout = int()>0 | default | infinity
Change = soft | {advanced,Extra}
Extra = term()
PrePurge = PostPurge = soft_purge | brutal_purge
DepMods = [Mod]
使用模块 Mod
的进程的同步代码替换。
使用 sys:suspend
暂停所有这些进程,加载新模块版本,然后使用 sys:resume
恢复这些进程。
Change
- 默认为soft
,并定义代码更改的类型。如果设置为{advanced,Extra}
,则使用gen_server
、gen_fsm
、gen_statem
或gen_event
实现的进程将通过调用回调函数code_change
来转换其内部状态。特殊进程调用回调函数system_code_change/4
。在这两种情况下,术语Extra
都作为参数传递给回调函数。PrePurge
- 默认为brutal_purge
。它控制在加载新模块版本之前对执行旧代码的进程执行的操作。如果值为brutal_purge
,则会杀死进程。如果该值为soft_purge
,则release_handler:install_release/1
返回{error,{old_processes,Mod}}
。PostPurge
- 默认为brutal_purge
。它控制在加载新模块版本时对执行旧代码的进程执行的操作。如果值为brutal_purge
,则在发布永久时清除代码并杀死进程。如果该值为soft_purge
,则当没有剩余的进程执行代码时,发布处理程序会清除旧代码。DepMods
- 默认为[]
,并定义Mod
依赖的其他模块。在relup
文件中,升级时,使用Mod
暂停进程的指令在升级时出现在使用DepMods
中模块暂停进程的指令之前,反之,在降级时相反。在循环依赖的情况下,保留appup
文件中指令的顺序。Timeout
- 定义暂停进程时的超时时间。如果未指定值或指定default
,则使用sys:suspend
的默认值。ModType
- 默认为dynamic
。它指定代码是否为“动态”,也就是说,如果使用该模块的进程自发地切换到新代码,或者是否为“静态”。在进行高级更新和升级时,动态模块的新版本在要求进程更改代码之前加载。降级时,在加载新版本之前,会要求进程更改代码。对于静态模块,在升级和降级的情况下,都会在新版本加载后,才要求进程更改代码。回调模块是动态的。
更改 Supervisor 的启动规范时,使用带有参数 supervisor
的 update
。
{load_module, Mod}
{load_module, Mod, DepMods}
{load_module, Mod, PrePurge, PostPurge, DepMods}
Mod = atom()
PrePurge = PostPurge = soft_purge | brutal_purge
DepMods = [Mod]
模块 Mod
的简单代码替换。
有关 PrePurge
和 PostPurge
的说明,请参阅上面的 update
。
DepMods
默认为 []
,并定义 Mod
所依赖的其他模块。在 relup
文件中,升级时,加载这些模块的指令出现在加载 Mod
的指令之前,反之,在降级时相反。
{add_module, Mod}
{add_module, Mod, DepMods}
Mod = atom()
DepMods = [Mod]
加载新模块 Mod
。
DepMods
默认为 []
,并定义 Mod
所依赖的其他模块。在 relup
文件中,升级时,与这些模块相关的指令出现在加载 Mod
的指令之前,反之,在降级时相反。
{delete_module, Mod}
{delete_module, Mod, DepMods}
Mod = atom()
使用低级指令 remove
和 purge
删除模块 Mod
。
DepMods
默认为 []
,并定义 Mod
所依赖的其他模块。在 relup
文件中,升级时,与这些模块相关的指令出现在删除 Mod
的指令之前,反之,在降级时相反。
{add_application, Application}
{add_application, Application, Type}
Application = atom()
Type = permanent | transient | temporary | load | none
添加应用程序意味着使用 add_module
加载 .app
文件中 modules
键定义的模块。
Type
默认为 permanent
,并指定应用程序的启动类型。如果 Type = permanent | transient | temporary
,则以相应的方式加载并启动应用程序,请参阅 application
。如果 Type = load
,则仅加载应用程序。如果 Type = none
,则不加载也不启动应用程序,尽管会加载其模块的代码。
{remove_application, Application}
Application = atom()
删除应用程序意味着停止该应用程序,使用 delete_module
卸载模块,然后从应用程序控制器卸载应用程序规范。
{restart_application, Application}
Application = atom()
重新启动应用程序意味着停止该应用程序,然后再启动它,类似于依次使用指令 remove_application
和 add_application
。请注意,即使应用程序在执行发布升级之前已启动,restart_application
也可能仅 load
它而不是 start
它,具体取决于应用程序的 start type
:如果 Type = load
,则仅加载应用程序。如果 Type = none
,则不加载也不启动应用程序,尽管会加载其模块的代码。
低级指令
{load_object_code, {App, Vsn, [Mod]}}
App = Mod = atom()
Vsn = string()
以二进制形式从目录 App-Vsn/ebin
读取每个 Mod
。它不会加载模块。该指令应放置在脚本的最前面,以便从文件中读取所有新代码,从而缩短暂停-加载-恢复周期的时间。
point_of_no_return
如果在此指令之后发生崩溃,则系统无法恢复,并从旧版本重新启动。该指令在脚本中只能出现一次。它应放置在所有 load_object_code
指令之后。
{load, {Mod, PrePurge, PostPurge}}
Mod = atom()
PrePurge = PostPurge = soft_purge | brutal_purge
在此指令发生之前,必须已使用 load_object_code
加载 Mod
。此指令加载模块。PrePurge
被忽略。有关 PostPurge
的说明,请参阅之前的高级指令 update
。
{remove, {Mod, PrePurge, PostPurge}}
Mod = atom()
PrePurge = PostPurge = soft_purge | brutal_purge
使 Mod
的当前版本成为旧版本。PrePurge
被忽略。有关 PostPurge
的说明,请参阅之前的高级指令 update
。
{purge, [Mod]}
Mod = atom()
清除每个模块 Mod
,即删除旧代码。请注意,执行清除代码的任何进程都将被终止。
{suspend, [Mod | {Mod, Timeout}]}
Mod = atom()
Timeout = int()>0 | default | infinity
尝试暂停使用模块 Mod
的所有进程。如果进程没有响应,则将其忽略。这可能会导致进程死亡,原因可能是当它自发地切换到新代码时崩溃,或者由于清除操作而导致。如果未指定 Timeout
或指定了 default
,则使用 sys:suspend
的默认值。
{resume, [Mod]}
Mod = atom()
恢复使用模块 Mod
的所有暂停进程。
{code_change, [{Mod, Extra}]}
{code_change, Mode, [{Mod, Extra}]}
Mod = atom()
Mode = up | down
Extra = term()
Mode
默认为 up
,并指定是升级还是降级。此指令通过调用函数 sys:change_code
,并传递术语 Extra
作为参数,向使用模块 Mod
的所有进程发送 code_change
系统消息。
{stop, [Mod]}
Mod = atom()
通过调用 supervisor:terminate_child/2
停止使用模块 Mod
的所有进程。当更改代码的最简单方法是停止并重新启动运行代码的进程时,此指令很有用。
{start, [Mod]}
Mod = atom()
通过调用 supervisor:restart_child/2
,使用模块 Mod
启动所有已停止的进程。
{sync_nodes, Id, [Node]}
{sync_nodes, Id, {M, F, A}}
Id = term()
Node = node()
M = F = atom()
A = [term()]
apply(M, F, A)
必须返回一个节点列表。
此指令将发布安装与其他节点同步。每个 Node
必须使用相同的 Id
来评估此命令。本地节点在执行继续之前等待所有其他节点评估该指令。如果某个节点关闭,则将其视为不可恢复的错误,并且本地节点将从旧版本重新启动。此指令没有超时时间,这意味着它可能会永远挂起。
{apply, {M, F, A}}
M = F = atom()
A = [term()]
评估 apply(M, F, A)
。
如果指令出现在指令 point_of_no_return
之前,则会捕获失败。release_handler:install_release/1
然后返回 {error,{'EXIT',Reason}}
,除非抛出或返回 {error,Error}
。然后它返回 {error,Error}
。
如果指令出现在指令 point_of_no_return
之后且函数调用失败,则系统将重新启动。
restart_new_emulator
当升级应用程序 ERTS、Kernel、STDLIB 或 SASL 时,将使用此指令。它会关闭当前虚拟机并启动一个新的虚拟机。所有进程都将正常终止,并且虚拟机重新启动时将使用新版本的 ERTS、Kernel、STDLIB 和 SASL。在 relup
文件中只允许一个 restart_new_emulator
指令,并且必须将其放置在最前面。systools:make_relup/3,4
在生成 relup
文件时会确保这一点。relup
文件中的其余指令在重新启动后作为启动脚本的一部分执行。
升级完成后,将写入一个信息报告。要以编程方式确定升级是否完成,请调用 release_handler:which_releases/0,1
并检查预期的版本是否具有状态 current
。
新版本必须在升级完成后保持永久有效,否则,如果虚拟机重新启动,将启动旧的虚拟机。
警告
如前所述,指令
restart_new_emulator
会导致虚拟机使用新版本的 ERTS、Kernel、STDLIB 和 SASL 重新启动。但是,所有其他应用程序在启动时都会在此新虚拟机中运行其旧版本。这通常不是问题,但有时核心应用程序会发生不兼容的更改,这可能会在此设置中导致问题。此类不兼容的更改(当函数被删除时)通常会在两个主要版本中被弃用。为确保您的应用程序不会因不兼容的更改而崩溃,请始终尽快删除对已弃用函数的任何调用。
restart_emulator
此指令类似于 restart_new_emulator
,但必须将其放置在 relup
文件的末尾。它与虚拟机或核心应用程序的升级无关,但当需要完全重启系统时,任何应用程序都可以使用它。
在生成 relup
文件时,systools:make_relup/3,4
确保只有一个 restart_emulator
指令,并且它是 relup
文件中的最后一个指令。