Erlang error handling

时间:2022-06-01 13:04:16

Erlang error handling

Contents

  1. Preface
  2. try-catch
  3. Process link
  4. Erlang-way error handling
  5. OTP supervisor tree
  6. Restart process

0. Preface

说到容错处理,大概大家都会想到 try-catch 类结构,对于绝大多数传统语言来说,确实是这样。但是对于Erlang来说,容错处理是其一个核心特性,真正涉及到的是整个系统的设计,与 try-catch 无关;其核心是Erlang进程本身的特性以及进程链接。

1. try-catch

本节纯粹为科普

不管怎么说,还是先介绍以下Erlang的 try-catch 结构(但请留意,try-catch 只作为引入,此小节以后的章节才是本文核心)。

Erlang 有三种错误类型:

  • error: 运行时异常,比如零除错误、匹配错误等。一旦Error使某个进程崩溃,Erlang错误日志管理器会进行记录。
  • exit: 进程停止异常,Exit会在迫使进程崩溃的同时将进程退出的原因告诉其他进程,一般不建议对Exit作捕获。同时Exit也可在进程正常终止时使用。Erlang错误日志管理器不会接收到Exit的异常汇报。
  • throw: 和Java的Throwable较类似,大多用于用户自己throw出异常到上层函数。如果没有catch的话就会变成原因为nocatch的error,迫使进程停止并记录异常。这里有个trap,如果你用了尾递归优化的函数,throw后是只有一层stack的。

关于尾递归的 throw,建议测试以下代码:

tail_throw(I) ->
if
I =< 0 ->
throw(erlang:get_stacktrace());
I > 0 ->
io:format("tail~n"),
tail_throw(I - 1)
,io:write("~n") % try delete this line to see the differences
end
.

try_catch

try
do_stuff()
catch
throw: Other -> {get_throw, Other};
error: Reason -> {get_error, Reason};
exit: Reason -> {get_exit, Reason}
end.

try_of_catch

try_of 可以对返回结果进行模式匹配,相当于case func() of的语法糖。

try
do_stuff_return()
of
0 -> pass;
Result -> pass
catch
throw: Other -> {get_throw, Other};
error: Reason -> {get_error, Reason};
exit: Reason -> {get_exit, Reason}
end.

after

after相当于Java的finally。

try
do_stuff()
catch
throw: Other -> {get_throw, Other};
error: Reason -> {get_error, Reason};
exit: Reason -> {get_exit, Reason}
after
clean()
end.

2. Process link

本节为预备知识

Erlang process之间有个重要的关系叫link,请记住,这是一个双向的关系。有时开玩笑会说,不link则已,一link则挂。

当两个进程link起来后,其中一方进程崩溃了产生 exit 信号,这个 exit 会被另一方进程 trap 住,然后一同挂掉。当然,有的进程trap 到了 exit 信号不一定打算同归于尽,他可以去做其他事情比如汇报异常、重启挂掉的进程等,这种进程的角色叫做supervisor,后面会提到。

link 一般有两种方式:

link(PidOrPort) -> true
% Types: PidOrPort = pid() | port()

Creates a link between the calling process and another process (or port) PidOrPort, if there is not such a link already. If a process attempts to create a link to itself, nothing is done. Returns true.

或者:

% Fun : function()
% Node: node()
% Module = module()
% Args = [term()]
spawn_link(Fun) -> pid(),
spawn_link(Node, Fun) -> pid(),
spawn_link(Module, Function, Args) -> pid(),
spawn_link(Node, Module, Function, Args) -> pid().

3. Erlang-way error handling

本节及之后为核心内容

我们先来看一段来自 Erlang Mailing List 2014.11.24 的邮件原文:

The try-catch syntax was deliberately chosen to be reminiscent of Java, the idea being that if you understood this try-catch consequence in Java you'd easily understand the Erlang code.

The problem I see with this is that programmers with previous experience in Java are tempted to blindly convert sequential try-catch code in Java into sequential try-catch code in Erlang, but this is almost always the wrong thing to do.

Beginners should be forbidden to use try-catch - the Erlang "way" is to spawn_link a regular process (ie a process that does not trap exits) and just let that process die if anything goes wrong. There should be no error trapping code in the spawned process. Call exit(Why) in every place where the behaviour is unspecified.

---- By Joe Armstrong

Joe Armstrong何许人也,据说他的简历只需要三个词 —— I wrote Erlang。

Joe 极力劝告大家不要使用try-catch结构来做错误处理,其实文中forbidden一词稍有点极端,在某些譬如做socket或做io的时候可能需要一些失败后的收尾工作,这时还是要借助catch。但是catch后最后还是建议让进程fail掉,因为那才是Erlang-way的容错处理,也就是Actor模式中常讲的“Let it crash”。

crash后的进一步错误处理就依靠上节提到的进程链接。那么这种同归于尽式的link有什么好处呢?通过link我们实际可以构建出一组进程(其实就是构建了一颗双向连通的进程树型结构),该组进程里的任一进程挂掉都会使整组进程挂掉。实际上这就是控制了错误的传播。在很多时候,某个进程计算出错了,相关的进程的状态其实也是有问题的,与其在每一步都去思考出错的可能情况去catch做处理,还不如挂掉重启,反正你跑下去也是有问题的。再者,Erlang的设计本来就是不停地新建进程和销毁进程,每一个小型任务可以的话一般都会分出去新建进程去跑。Erlang进程不同于OS级别的进程线程,调度开销很低。因此,Erlang社区的开发者都会建议你通过link来传播进程的exit信号,从而实现容错处理。

Erlang 里还有个术语叫做 error kernel,指的是整个系统程序里决不能出错的核心部分(一出错整个系统就完蛋,没有挽救机会的那种)。error kernel的要求是规模尽可能小,并且发布后默认是可信任的;其他一些计算类任务都要尽可能从error kernel剥离。因此,error kernel占系统的比例大小是评估整个系统Erlang实现的鲁棒性的重要标准。

有一种形象化的比喻:把整个系统看做一个个方格拼成的广场,error kernel部分的方格是红色的。还有一些比如起顶端supervisor角色作用的模块,或一些可能出严重错误但还未严重到使系统崩溃的模块进程,它们也被标成红色的。其他允许挂掉的进程我们标为白色。Erlang系统的鲁棒性的一个形象体现就在于红色方块越少,系统越健壮。

4. OTP supervisor tree

上文我们反复提到一个词叫做supervisor,中文叫监管者/监督者。我们所创建的Erlang process一般有两种,supervisor和worker。worker很简单,就是实际执行任务的进程。supervisor的职责有:监控子进程状态并在其挂掉后做错误处理、exit信号隔离以及重启策略。

OTP给Erlang带来了很多特性,其中一个非常重要的是supervisor tree监管树。

Erlang error handling

如图所示,在 Process link 一节中曾提到多个进程link成一个树形结构的进程组,便是图中的一棵子树。supervisor下的某个子树挂了,不会影响到其他无关的进程组,同时supervisor还可以帮助重启。supervisor也可以监管supervisor形成多级监管。总而言之有如下特性:

  1. 子进程受父进程监管,其退出信号会传递给父进程。
  2. 父进程收到子进程的退出信号时,可以决定是将信号继续传递还是自行处理。
  3. 父进程若决定自行处理则会按照配置的重启策略去重启子进程。

OTP提供的默认 behaviour 中就有 supervisor,让用户很方便的去实现基本的 supervisor 进程。OTP允许监督者按预设的方式和次序来启动进程。用户还可以告知监督者如何在单个进程故障时重启其他进程、一段时间内尝试重启多少次后放弃重启等。

5. Restart process

我们一直在强调要重启进程,但是重启我们就不得不面对一个问题,旧进程的状态怎么办?正所谓一个计算过程或函数一般都需要输入才能有输出,重启后输入怎么办?

这里我们要将进程内的状态分为三种来考虑:

  1. Internal State,又叫 Stack State。就是一些临时变量或存储在栈上的内容。这部分状态我们实际是不期望保留的,一般最多用来做点错误追踪,因为同样的栈再去跑还是同样的错误。
  2. Static State,或 Global State。这里比较类似Java里的一些常量、静态变量等,比如像TCP的端口地址配置之类的。这部分状态应该是做好配置存储的,比如放到ETS里,一般全局存储后可以轻易拿回。
  3. Dynamic State。比如说一些计算结果或用户输入。计算结果还好,只要可以重计算的问题都不大。比较麻烦的是像用户输入一类的没办法轻易取回的数据,总不能叫用户再输入一次吧。这部分数据状态就需要开发者自己比较小心。在识别出该进程可能挂掉会丢失该类数据后,如果有需要应该将其进行一定的缓存,这样重启时也可以去找回,等到计算结束后再清除缓存。

以上。

Erlang error handling的更多相关文章

  1. MySQL Error Handling in Stored Procedures 2

    Summary: this tutorial shows you how to use MySQL handler to handle exceptions or errors encountered ...

  2. setjmp&lpar;&rpar;、longjmp&lpar;&rpar; Linux Exception Handling&sol;Error Handling、no-local goto

    目录 . 应用场景 . Use Case Code Analysis . 和setjmp.longjmp有关的glibc and eglibc 2.5, 2.7, 2.13 - Buffer Over ...

  3. Error Handling

    Use Exceptions Rather Than Return Codes Back in the distant past there were many languages that didn ...

  4. Error Handling and Exception

    The default error handling in PHP is very simple.An error message with filename, line number and a m ...

  5. Clean Code&ndash&semi;Chapter 7 Error Handling

    Error handling is important, but if it obscures logic, it's wrong. Use Exceptions Rather Than Return ...

  6. &lbrack;RxJS&rsqb; Error handling operator&colon; catch

    Most of the common RxJS operators are about transformation, combination or filtering, but this lesso ...

  7. MySQL Error Handling in Stored Procedures---转载

    This tutorial shows you how to use MySQL handler to handle exceptions or errors encountered in store ...

  8. Error Handling in ASP&period;NET Core

    Error Handling in ASP.NET Core 前言  在程序中,经常需要处理比如 404,500 ,502等错误,如果直接返回错误的调用堆栈的具体信息,显然大部分的用户看到是一脸懵逼的 ...

  9. beam 的异常处理 Error Handling Elements in Apache Beam Pipelines

    Error Handling Elements in Apache Beam Pipelines Vallery LanceyFollow Mar 15 I have noticed a defici ...

随机推荐

  1. Java中如何把一下字符串转换成map

    首先,你先确认你的字符串是否是json格式的,如果是json格式,那你可以使用Gson.jar或json-lib-xx-jdk.jar两个包来自动解析解析. 使用Gson更简单些,只需要导入一个包就可 ...

  2. Google 黑客搜索技巧

    常用的google关键字: foo1 foo2 (也就是关联,比如搜索xx公司 xx美女) operatorfoo filetype123 类型 sitefoo.com 相对直接看网站更有意思,可以得 ...

  3. 使用 Git 管理源代码

    在现代软件开发项目中,要成为一个有效的软件开发人员,我们必须能够与其他项目贡献者并行进行开发.源代码管理(SCM)系统不是什么新思想.为了编写一些能够更快速.简单地开发以后软件项目的软件,已经进行了很 ...

  4. c&num; 定时执行python脚本

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  5. 以O2O为核心的ERP思考

    O2O已经火了一阵子了,很多人都在说O2O,各行各业都想和O2O有所结合,都认为这里面将会有巨大的商机. 在互联网发展到移动互联网的时代,我们的生活的很多方面已经被改变了,很多事情都已经可以在移动端完 ...

  6. POJ 1700 坐船过河问题

    题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82974#problem/E 解题思路:当n>=4,假设n个人单独过河所需 ...

  7. load、save方法、spark sql的几种数据源

    load.save方法的用法          DataFrame usersDF = sqlContext.read().load("hdfs://spark1:9000/users.pa ...

  8. 报错:&OpenCurlyDoubleQuote;不是有效的Win32应用程序”的解决办法

    Win7.Win8下用VS2013编译完的程序,拿到32位WindowsXP虚拟机下运行有时候会报错:

  9. testNG常用方法

    1.常用注释: 注解                  描述 @BeforeSuite                       在该套件的所有测试都运行在注释的方法之前,仅运行一次. @After ...

  10. 摩羯座Capricornus

    Capricornus  摩羯座的人通常会如何拒绝别人. 摩羯座的人做事脚踏实地,比较固执,忍耐力也是出奇的强大,同时也非常勤奋.他们心中总是背负着很多的责任感,但往往又很没有安全感,不会完全地相信别 ...