- 编程语言
- 语言运行时
- 标准库
- 编译器
- 并发编程
【免费下载链接】otp
Erlang/OTP
导读
在 Erlang/OTP 中,SASL 应用提供的 release handling 框架允许系统在运行时(runtime)对应用进行升级与降级,其核心是每个应用目录下的.appup文件——它描述"从旧版本升级到新版本、以及从新版本降级到旧版本"需要执行哪些指令。本文以官方文档 Appup Cookbook 为骨架,结合仓库中的 release handling 文档 与 SASL 源码(systools、release_handler),逐一讲解功能模块替换、内部状态变更、supervisor 属性与子进程规格变更、特殊进程、被包含应用(included application)以及非 Erlang 代码升级等典型场景,并给出可直接复制使用的.appup文件示例。读完本文,你将能够为自己的应用编写正确的.appup文件、理解relup的生成原理,并掌握release_handler在目标系统上的安装流程。
.appup文件的基础结构
在深入各种场景之前,先明确.appup(application upgrade file)的语法。它定义如何在新版本与旧版本之间升级/降级,文件名为Application.appup,其中Application是应用名,通常位于应用的ebin目录:
{Vsn, [{UpFromVsn1, InstructionsU1}, ..., {UpFromVsnK, InstructionsUK}], [{DownToVsn1, InstructionsD1}, ..., {DownToVsnK, InstructionsDK}]}.Vsn(字符串)是当前版本,与.app文件中的{vsn, ...}一致;- 每个
UpFromVsn是"要从其升级"的旧版本;每个DownToVsn是"要降级到"的旧版本; - 每个
Instructions是一组 release handling 指令(instruction); UpFromVsn与DownToVsn也支持正则表达式,例如 sasl.appup.src 中就用{<<"^4\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}匹配 OTP 27/28/29 一系列历史版本。
指令分为两类:高层指令(high-level)由systools:make_relup/3,4翻译为低层指令(low-level),后者才是release_handler直接理解的指令集合(详见 release_handling.md)。.appup文件中书写的是高层指令,而生成的relup文件全部是低层指令。systools:make_relup/3,4的实现位于 lib/sasl/src/systools.erl#L343-L435,它会读取新老版本的.rel、.app与.appup文件,推导出需要新增、删除、升级或降级的应用,然后把所有指令合并为一条有序的低层指令列表。
先记住两个关键概念:
- 驻留模块(residence module):进程尾递归循环函数所在的模块。对于按 OTP behaviour 实现的进程,behaviour 模块(如
gen_server)就是驻留模块,回调模块则属于功能模块。 - 功能模块(functional module):不是任何进程驻留模块的模块。
更换功能模块:简单的代码替换
当一个功能模块被修改(例如新增函数或修复 bug)时,简单代码替换(simple code replacement)就够了,即把新版本模块加载进系统并移除旧版本。.appup文件只需一条load_module指令:
{"2", [{"1", [{load_module, m}]}], [{"1", [{load_module, m}]}] }.升级与降级的指令相同,因为降级时"加载旧版本"同样只是简单的模块加载。
更换回调模块:也是简单的代码替换
回调模块(callback module)本质上是功能模块,因此扩展代码同样用简单代码替换。文档中的经典例子是 Release Handling 里给ch3增加available/0函数:新版本ch_app从"1"升级到"2",只需加载新版本的ch3回调模块:
{"2", [{"1", [{load_module, ch3}]}], [{"1", [{load_module, ch3}]}] }.注意,load_module只加载模块,不会让已经运行中的 behaviour 进程切换到新代码;进程只有在下次函数调用时才会自然进入新版本代码。如果新版本代码改变了进程内部状态格式,就必须使用下一节讲的同步代码替换。
变更内部状态:同步代码替换与 code_change/3
如果新版本改变了 behaviour 进程的内部状态格式,简单代码替换就不够了。进程必须在切换到新代码之前,通过回调函数code_change/3显式完成状态转换,这称为同步代码替换(synchronized code replacement),对应指令:
{update, Module, {advanced, Extra}}它让 release handler 依次完成:挂起(suspend)使用该模块的进程 → 让进程调用code_change/3转换状态并切换到新代码 → 移除旧版本代码 → 恢复(resume)进程。这一过程在 release_handling.md 中有详细描述,其底层调用链是sys:suspend/1,2、sys:change_code/4,5与sys:resume/1,2。
示例:给 ch3 增加计数器
考虑 gen_server Behaviour 中的ch3模块,其内部状态Chs表示可用频道集合。假设要为alloc请求增加计数器N,状态格式要变为{Chs,N}:
{"2", [{"1", [{update, ch3, {advanced, []}}]}], [{"1", [{update, ch3, {advanced, []}}]}] }.update指令的第三个元素{advanced,Extra}表示:受影响的进程在加载新模块版本之前要做状态转换——即调用回调函数code_change/3。Extra(此处为[])会被原样传给该函数:
-module(ch3). ... -export([code_change/3]). ... code_change({down, _Vsn}, {Chs, N}, _Extra) -> {ok, Chs}; code_change(_Vsn, Chs, _Extra) -> {ok, {Chs, 0}}.- 第一个参数在降级时是
{down,Vsn},在升级时是Vsn;Vsn取自"原始"版本(即你正在升级的旧版本或正在降级到的新版本)的模块。 - 版本由模块属性
vsn决定;ch3没有该属性,因此版本就是 beam 文件的校验和(一个大整数),实践中通常不关心这个值。 - 其余回调函数也需要相应修改(比如新增接口函数),这里不再展开。
模块依赖:DepMods
假设m1模块调用新加的ch3:available/0。如果升级时先加载新版本m1,而新版本ch3尚未加载,m1调用ch3:available/0就会在运行时出错。因此ch3必须先于m1加载(升级方向),降级方向则相反。这就是模块依赖,通过指令中的DepMods元素表达:
{load_module, Module, DepMods} {update, Module, {advanced, Extra}, DepMods}DepMods是Module所依赖的模块列表。例如应用myapp的m1依赖ch3:
myapp.appup: {"2", [{"1", [{load_module, m1, [ch3]}]}], [{"1", [{load_module, m1, [ch3]}]}] }. ch_app.appup: {"2", [{"1", [{load_module, ch3}]}], [{"1", [{load_module, ch3}]}] }.如果m1和ch3属于同一个应用,可以写在一个.appup里:
{"2", [{"1", [{load_module, ch3}, {load_module, m1, [ch3]}]}], [{"1", [{load_module, ch3}, {load_module, m1, [ch3]}]}] }.m1在降级时也依赖ch3。systools懂得升级与降级的方向差异,会自动生成正确的relup:升级时先加载ch3再加载m1,降级时先加载m1再加载ch3。
更换特殊进程(Special Process)的代码
特殊进程是使用proc_lib和sys手动实现、不完全遵循 behaviour 的进程。当特殊进程的驻留模块换新版本时,进程必须对循环函数做一次全限定调用(fully qualified call)才能切入新代码,因此必须使用同步代码替换:
{update, ch4, {advanced, []}}注意:特殊进程的驻留模块名必须列在子进程规格(child specification)的
Modules部分,否则 release handler 无法找到该进程。
以文档 sys and proc_lib 中的ch4为例,由 supervisor 启动时的子进程规格:
{ch4, {ch4, start_link, []}, permanent, brutal_kill, worker, [ch4]}ch4属于应用sp_app,升级"1"→"2"时sp_app.appup可写为:
{"2", [{"1", [{update, ch4, {advanced, []}}]}], [{"1", [{update, ch4, {advanced, []}}]}] }.update指令必须包含{advanced,Extra},它让特殊进程调用用户实现的system_code_change/4回调函数,Extra原样传入:
-module(ch4). ... -export([system_code_change/4]). ... system_code_change(Chs, _Module, _OldVsn, _Extra) -> {ok, Chs}.各参数含义:
- 第一个参数是内部状态
State,来自特殊进程收到系统消息时调用的sys:handle_system_msg/6;在ch4中即可用频道集合Chs。 - 第二个参数是模块名(
ch4)。 - 第三个参数是
Vsn或{down,Vsn},规则与前面code_change/3相同。
如果代码只是扩展,返回原状态即可;如果内部状态格式也变了(类似"变更内部状态"一节的例子),就在这个函数里完成转换并返回{ok,Chs2}。
更换 Supervisor:属性与子进程规格
supervisor behaviour 支持修改内部状态,即修改重启策略、最大重启频率以及现有子进程规格。子进程可以新增或删除,但不会自动处理,必须在.appup中显式给出指令。
修改属性(Properties)
修改 supervisor 属性属于内部状态变更,需要同步代码替换,但必须使用专用的update指令形式:
{update, Module, supervisor}其执行逻辑是:先加载新版本回调模块(升级、降级都要加载),然后检查新的init/1返回值,并据此变更内部状态。
示例:把ch_sup(来自 Supervisor Behaviour)的重启策略从one_for_one改为one_for_all,先修改ch_sup.erl中的init/1:
-module(ch_sup). ... init(_Args) -> {ok, {#{strategy => one_for_all, ...}, ...}}.ch_app.appup:
{"2", [{"1", [{update, ch_sup, supervisor}]}], [{"1", [{update, ch_sup, supervisor}]}] }.修改子进程规格(Child Specifications)
修改现有子进程规格时,指令与.appup文件与修改属性完全相同:
{"2", [{"1", [{update, ch_sup, supervisor}]}], [{"1", [{update, ch_sup, supervisor}]}] }.需要注意:
- 修改不影响已经存在的子进程。例如修改 start 函数只决定该子进程以后如果需要重启时如何启动。
- 子进程规格的
id不能被修改。 - 修改
Modules字段会影响 release handling 过程本身,因为 release handler 正是靠Modules字段识别哪些进程参与同步代码替换(在 release_handling.md 中,进程使用某模块即表示该模块名列在其子进程规格的Modules中;若Modules=dynamic,如事件管理器,则由gen_event进程向 release handler 上报当前安装的事件处理器列表)。
新增与删除子进程
新子进程规格会被自动添加,但不会自动删除;子进程也不会被自动启动或终止,必须借助apply指令显式调用supervisor的函数。
示例:升级ch_app从"1"到"2"时给ch_sup新增子进程m1(降级时删除它):
{"2", [{"1", [{update, ch_sup, supervisor}, {apply, {supervisor, restart_child, [ch_sup, m1]}} ]}], [{"1", [{apply, {supervisor, terminate_child, [ch_sup, m1]}}, {apply, {supervisor, delete_child, [ch_sup, m1]}}, {update, ch_sup, supervisor} ]}] }.指令的顺序很重要:升级时先更新 supervisor 的子进程规格,再启动新子进程;降级时先终止子进程、删除规格,再更新 supervisor。
apply指令的形式是{apply, {M, F, A}},由 release handler 直接求值apply(M, F, A)(见 release_handling.md)。这里要求 supervisor 已注册为ch_sup;如果 supervisor 未注册,脚本无法直接访问它,就必须写一个辅助函数先找到 supervisor 的 pid 再调用supervisor:restart_child等,并通过apply调用这个辅助函数。
如果模块m1是在版本"2"中才引入的,升级时还要加载、降级时还要删除:
{"2", [{"1", [{add_module, m1}, {update, ch_sup, supervisor}, {apply, {supervisor, restart_child, [ch_sup, m1]}} ]}], [{"1", [{apply, {supervisor, terminate_child, [ch_sup, m1]}}, {apply, {supervisor, delete_child, [ch_sup, m1]}}, {update, ch_sup, supervisor}, {delete_module, m1} ]}] }.升级方向:先add_module加载m1、更新子进程规格,再启动子进程;降级方向:先终止并删除子进程,再更新规格、最后delete_module卸载模块。delete_module会杀死任何以该模块为驻留模块的进程,所以删除前必须确保这类进程已全部终止(见 release_handling.md)。
新增或删除模块
新增一个功能模块m到ch_app:
{"2", [{"1", [{add_module, m}]}], [{"1", [{delete_module, m}]}] }.add_module加载模块。在嵌入式模式(embedded mode)下必须显式加载;交互模式(interactive mode)下代码服务器会自动查找并加载未加载的模块,所以并非严格必需。delete_module卸载模块。
启动或终止进程
在按 OTP 设计原则组织的系统中,任何进程都是某个 supervisor 的子进程,所以"启动/终止进程"本质上就是上一节"新增与删除子进程"的操作,直接复用那里的指令即可。
新增或移除应用
新增或移除一个**主应用(primary application)**时不需要.appup文件。生成relup时,systools会对比新旧.rel文件,自动加入add_application与remove_application指令:
add_application:先按.app文件中的modules键用多条add_module加载模块,然后启动应用;remove_application:停止应用、用多条delete_module卸载模块、再从 application controller 卸载应用规格。
重启一个应用
当变更过于复杂、无法不重启进程就完成时(例如 supervisor 层级被重构),restart_application指令非常有用,它等价于依次执行"移除应用 + 添加应用"(见 release_handling.md)。
示例:上一节"新增子进程m1"的场景,也可以不更新 supervisor,而是直接重启整个应用:
{"2", [{"1", [{restart_application, ch_app}]}], [{"1", [{restart_application, ch_app}]}] }.变更应用规格与应用配置
安装 release 时,应用规格会自动更新,发生在relup脚本求值之前,因此.appup中不需要任何指令:
{"2", [{"1", []}], [{"1", []}] }.更新.app文件中的env键从而修改应用配置,属于"变更应用规格"的一种。除此之外,也可以在sys.config中新增或更新应用配置参数。
值得补充的是应用配置的更新优先级(见 release_handling.md):安装新 release 后,应用配置参数按以下优先级递增的顺序自动更新:
- 启动脚本(boot script)中的数据(来自新版本的
App.app); - 新的
sys.config; - 命令行参数
-App Par Val。
这意味着其他系统配置文件里设置的值以及通过application:set_env/3设置的值会被忽略。安装完成后,application controller 会比较新旧配置并调用可选回调Module:config_change(Changed, New, Removed),其中Changed/New是变更/新增的{Par,Val}列表,Removed是被移除的参数列表。
变更被包含应用(Included Applications)
关于新增、移除、重启应用的指令只适用于主应用,没有针对被包含应用(included application)的对应指令。但由于被包含应用本质上是一棵以顶层 supervisor 为根的监督树、作为包含应用某 supervisor 的子进程启动,因此可以手工创建relup来实现。
示例:release 中有应用prim_app(监督树里有 supervisorprim_sup)。新版本要把ch_app包含进prim_app,即把ch_app的顶层 supervisorch_sup作为prim_sup的子进程启动。
Step 1)修改prim_sup的代码:
init(...) -> {ok, {...supervisor flags..., [..., {ch_sup, {ch_sup,start_link,[]}, permanent,infinity,supervisor,[ch_sup]}, ...]}}.Step 2)修改prim_app的.app文件:
{application, prim_app, [..., {vsn, "2"}, ..., {included_applications, [ch_app]}, ... ]}.Step 3)创建新的.rel文件,把ch_app加进去:
{release, ..., [..., {prim_app, "2"}, {ch_app, "1"}]}.被包含应用可以用两种方式启动。
方式一:应用重启(Application Restart)
Step 4a)一种方式是重启整个prim_app,通常使用prim_app.appup中的restart_application指令。
但如果这样做了再生成relup,它不但包含重启(移除并添加)prim_app的指令,还会因为新.rel里有ch_app而自动加入启动/停止ch_app的指令——这正是我们不想要的(被包含应用应随包含应用启动)。正确做法是手工创建relup(从头写或在生成版本上修改),把启动/停止ch_app的指令替换为加载/卸载应用的指令:
{"B", [{"A", [], [{load_object_code,{ch_app,"1",[ch_sup,ch3]}}, {load_object_code,{prim_app,"2",[prim_app,prim_sup]}}, point_of_no_return, {apply,{application,stop,[prim_app]}}, {remove,{prim_app,brutal_purge,brutal_purge}}, {remove,{prim_sup,brutal_purge,brutal_purge}}, {purge,[prim_app,prim_sup]}, {load,{prim_app,brutal_purge,brutal_purge}}, {load,{prim_sup,brutal_purge,brutal_purge}}, {load,{ch_sup,brutal_purge,brutal_purge}}, {load,{ch3,brutal_purge,brutal_purge}}, {apply,{application,load,[ch_app]}}, {apply,{application,start,[prim_app,permanent]}}]}], [{"A", [], [{load_object_code,{prim_app,"1",[prim_app,prim_sup]}}, point_of_no_return, {apply,{application,stop,[prim_app]}}, {apply,{application,unload,[ch_app]}}, {remove,{ch_sup,brutal_purge,brutal_purge}}, {remove,{ch3,brutal_purge,brutal_purge}}, {purge,[ch_sup,ch3]}, {remove,{prim_app,brutal_purge,brutal_purge}}, {remove,{prim_sup,brutal_purge,brutal_purge}}, {purge,[prim_app,prim_sup]}, {load,{prim_app,brutal_purge,brutal_purge}}, {load,{prim_sup,brutal_purge,brutal_purge}}, {apply,{application,start,[prim_app,permanent]}}]}] }.这段脚本展示了低层指令的组合:load_object_code(把模块代码读入系统但不立即切换)、point_of_no_return(提交点,其前的指令失败可回滚、其后失败则不可回滚,见 release_handler.erl)、remove/load(带brutal_purge的卸载/加载)、purge、apply调用application:stop/1、application:load/1、application:unload/1、application:start/2等。
方式二:Supervisor 变更(Supervisor Change)
Step 4b)另一种方式是:对prim_sup做"新增/删除子进程"的指令,同时配合加载/卸载ch_app的全部代码及其应用规格。同样手工创建relup:升级时先加载ch_app全部代码与应用规格,再更新prim_sup;降级时先更新prim_sup,再卸载ch_app的代码与应用规格。
{"B", [{"A", [], [{load_object_code,{ch_app,"1",[ch_sup,ch3]}}, {load_object_code,{prim_app,"2",[prim_sup]}}, point_of_no_return, {load,{ch_sup,brutal_purge,brutal_purge}}, {load,{ch3,brutal_purge,brutal_purge}}, {apply,{application,load,[ch_app]}}, {suspend,[prim_sup]}, {load,{prim_sup,brutal_purge,brutal_purge}}, {code_change,up,[{prim_sup,[]}]}, {resume,[prim_sup]}, {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}], [{"A", [], [{load_object_code,{prim_app,"1",[prim_sup]}}, point_of_no_return, {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}}, {apply,{supervisor,delete_child,[prim_sup,ch_sup]}}, {suspend,[prim_sup]}, {load,{prim_sup,brutal_purge,brutal_purge}}, {code_change,down,[{prim_sup,[]}]}, {resume,[prim_sup]}, {remove,{ch_sup,brutal_purge,brutal_purge}}, {remove,{ch3,brutal_purge,brutal_purge}}, {purge,[ch_sup,ch3]}, {apply,{application,unload,[ch_app]}}]}] }.注意升级方向的顺序:先load_object_code加载ch_app与prim_sup新代码 →point_of_no_return→ 加载ch_sup、ch3→application:load(ch_app)→suspend(prim_sup)→ 加载新prim_sup→code_change,up→resume(prim_sup)→supervisor:restart_child(prim_sup, ch_sup)启动被包含应用。降级方向则完全镜像:先终止/删除子进程,再更新prim_sup,最后卸载ch_app代码并application:unload(ch_app)。
变更非 Erlang 代码(如 Port 程序)
OTP 对非 Erlang 语言编写的程序(例如 port 程序)的代码升级没有专门支持,属于应用相关的问题。典型做法是让控制 port 的 Erlang 进程在code_change/3中关闭旧 port 并打开新 port。
示例:假设控制 port 的进程是gen_serverportc,port 在init/1中打开:
init(...) -> ..., PortPrg = filename:join(code:priv_dir(App), "portc"), Port = open_port({spawn,PortPrg}, [...]), ..., {ok, #state{port=Port, ...}}.为portc增加code_change/3,关闭旧 port、打开新 port(必要时可先从旧 port 请求需保存的数据再传给新 port):
code_change(_OldVsn, State, port) -> State#state.port ! close, receive {Port,close} -> true end, PortPrg = filename:join(code:priv_dir(App), "portc"), Port = open_port({spawn,PortPrg}, [...]), {ok, #state{port=Port, ...}}.更新.app中的应用版本号并编写.appup文件:
["2", [{"1", [{update, portc, {advanced,port}}]}], [{"1", [{update, portc, {advanced,port}}]}] ].{advanced,port}中的port会作为Extra传给code_change/3。还要确保 C 程序所在的priv目录被打进新 release 包:
1> systools:make_tar("my_release", [{dirs,[priv]}]). ...运行时系统重启与升级
两条升级指令会重启运行时系统:
restart_new_emulator:用于 ERTS、Kernel、STDLIB 或 SASL 升级。由systools:make_relup/3,4生成relup时自动加入,且必须是relup的第一条指令。遇到该指令时,release handler 先生成临时启动脚本(新版本运行时系统与核心应用、旧版本的其他应用),调用init:reboot/0优雅终止所有进程,由heart程序按临时脚本重启,重启后继续执行relup剩余指令。它要求系统以心跳监控(heartbeat)方式启动,即嵌入式系统 +heart(见 release_handling.md)。在 release_handler.erl 中可以看到,如果脚本里遇到restart_new_emulator,upgrade_app/2会返回{error,restart_new_emulator},因为该指令需要启动新版本模拟器。警告:该机制会让"新版本运行时系统与核心应用"在启动时与"旧版本的其他应用"共存,必须格外小心兼容性。核心应用如确有破坏性变更,通常会先在两个大版本中走弃用(deprecation)流程;同时要尽快移除对已弃用函数的调用,避免应用被不兼容变更弄崩。
restart_emulator:与 ERTS/核心应用升级无关,用于在任何应用需要时、在所有其他升级指令执行完之后强制重启运行时系统。relup中只能有一条,且必须放在末尾(systools:make_relup生成时自动满足)。遇到它时 release handler 调用init:reboot/0优雅关闭系统,由heart用新release 版本重启,重启后不再执行任何升级指令。
如果只需要重启运行时系统、不需要任何升级指令(即重启本身足以让新版本应用生效),可以手工写一个极简的relup:
{"B", [{"A", [], [restart_emulator]}], [{"A", [], [restart_emulator]}] }.这种情况下,可以只使用 release handler 框架的自动打包/解包、自动路径更新等能力,而完全不需要编写.appup文件。
从.appup到relup再到在线安装:完整工作流
把上面的知识串起来,一个完整的升级周期是(详见 release_handling.md):
- 按 Releases 创建 release,安装到目标环境(嵌入式系统,配
heart)。 - 在开发环境修改代码,更新相关
.app文件版本号并编写新.rel文件。 - 为每个被修改的应用编写
.appup文件(本文全部场景)。 - 用
systools:make_relup/3,4基于新旧.rel、.app、.appup生成relup:
1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]). ok如果新旧版本的.app/.rel文件不在同一代码路径,用path选项扩展:
1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"], [{path,["../ch_rel-1", "../ch_rel-1/lib/ch_app-1/ebin"]}]). ok- 生成启动脚本与 release 包(记得包含
relup、sys.config,必要时用{dirs,[priv]}包含 priv 目录),拷到目标机$ROOT/releases:
1> systools:make_script("ch_rel-2"). ok 2> systools:make_tar("ch_rel-2"). ok- 在运行中的目标系统里解包并安装:
1> release_handler:unpack_release("ch_rel-2"). {ok,"B"} 2> release_handler:install_release("B"). {ok,"A",[]}安装会逐条执行relup中的指令;失败则用旧版本重启,成功则新版本成为当前版本但尚未成为默认版本。 7. 让新版本成为默认版本(permanent),否则系统重启后仍会回到旧版本:
3> release_handler:make_permanent("B"). ok系统在$ROOT/releases/RELEASES与$ROOT/releases/start_erl.data中记录各版本的 old/permanent 状态。release_handler:remove_release(Vsn)可移除已安装但未 permanent 的 release。
常见注意事项小结
- 指令顺序至关重要:新增模块要先加载再使用,删除模块前要先终止相关进程;升级与降级方向完全相反。
- 简单 vs 同步代码替换:只加函数用
load_module;改内部状态/驻留模块用update+{advanced,Extra}(behaviour 进程回调code_change/3,特殊进程回调system_code_change/4);改 supervisor 用update+supervisor。 DepMods表达模块依赖:防止升级瞬间新旧代码混用导致的undef运行时错误;systools会根据升级/降级方向自动排好加载顺序。- 被包含应用没有专属指令:要么重启包含应用,要么手工编写
relup组合加载/卸载与 supervisor 子进程操作。 relup只需低层指令:手工编写时只使用load_object_code、point_of_no_return、suspend/resume、code_change、remove/load/purge、apply、restart_new_emulator/restart_emulator等低层指令。- 保持向后兼容、小步改动:release handling 期间未受影响的进程照常运行,新进程可能短暂执行旧代码(release_handling.md),因此代码改动尽量小且向后兼容。
以上所有指令的完整清单与语法可查阅 SASL 应用手册中的appup、relup页面(本仓库对应源码在 lib/sasl/src 的systools*.erl与release_handler*.erl),而.appup的官方示例合集正是本文依据的 appup_cookbook.md。
- 编程语言
- 语言运行时
- 标准库
- 编译器
- 并发编程
【免费下载链接】otp
Erlang/OTP
相关推荐
Erlang/OTP 应用升级文件 appup 完全指南:语法、指令详解与实战示例
Erlang/OTP 应用升级文件 appup 完全指南:语法、指令详解与实战示例 导读 本文深入讲解 Erlang/OTP 中 应用升级文件(Applicat
编程语言语言运行时标准库编译器并发编程Erlang/OTP relup 文件完全指南:运行时热升级指令的生成、语法与执行原理
Erlang/OTP relup 文件完全指南:运行时热升级指令的生成、语法与执行原理 导读 relup (Release Upgrade File,发布升级文
编程语言语言运行时标准库编译器并发编程Erlang/OTP Release Handling 实战指南:基于 SASL 的运行时升级与回滚
Erlang/OTP Release Handling 实战指南:基于 SASL 的运行时升级与回滚 Erlang 语言最突出的能力之一是在运行时替换模块代码(
编程语言语言运行时标准库编译器并发编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考