查看源码 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 会自动将这些指令转换为低级指令。

为了避免重复升级指令,允许使用正则表达式来指定 UpFromVsnDownToVsn。要被视为正则表达式,版本标识符必须指定为二进制文件。例如,以下内容匹配所有版本 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_servergen_fsmgen_statemgen_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 的启动规范时,使用带有参数 supervisorupdate

{load_module, Mod}
{load_module, Mod, DepMods}
{load_module, Mod, PrePurge, PostPurge, DepMods}
  Mod = atom()
  PrePurge = PostPurge = soft_purge | brutal_purge
  DepMods = [Mod]

模块 Mod 的简单代码替换。

有关 PrePurgePostPurge 的说明,请参阅上面的 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()

使用低级指令 removepurge 删除模块 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_applicationadd_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 文件中的最后一个指令。

另请参阅

release_handler, relup(4), supervisor, systools