事故背景

由于误操作在erlcron设置了一个超过3个月后的定时任务。然后第二天之后发现每天的daily reset没有被执行,一些定时任务也没有被执行。瞬间感觉整个人都不好了,怎么无端端就不执行了呢。

通过排查日志,发现了以下报错:

2016-03-22 16:54:32.014 [error] gen_server ecrn_control terminated with reason: no case clause matching {ok,[<0.14123.1577>,<0.13079.1576>,<0.25254.1569>,<0.13402.1577>,...]} in ecrn_control:internal_cancel/1 line 111
2016-03-22 16:54:32.015 [error] CRASH REPORT Process ecrn_control with 0 neighbours exited with reason: no case clause matching {ok,[<0.14123.1577>,<0.13079.1576>,<0.25254.1569>,<0.13402.1577>,...]} in ecrn_control:internal_cancel/1 line 111 in gen_server:terminate/6 line 744

我擦,ecrn_control都崩了,怎么回事。

找到具体出错的代码:

internal_cancel(AlarmRef) ->
case ecrn_reg:get(AlarmRef) of
undefined ->
undefined;
{ok, [Pid]} ->
ecrn_agent:cancel(Pid)
end.

发现调用ecrn_reg:get(AlarmRef)被返回了{ok, List},而且这个List的数据远不止一个。明显在设置那个超过3个月的定时任务的时候,ecrn_reg被注册进了脏数据。

事故重现

先设置几个正常的定时任务

erlcron:cron({{once, 1000}, {io, fwrite, ["Hello, world!~n"]}}).
erlcron:cron({{once, 1000}, {io, fwrite, ["Hello, world!~n"]}}).
erlcron:cron({{once, 1000}, {io, fwrite, ["Hello, world!~n"]}}).

查看observer:start() 可以看到进程树如下:

再设置一个4294968秒之后的定时任务

erlcron:cron({{once, 4294968}, {io, fwrite, ["Hello, world!~n"]}}).

结果就gg了,好多崩溃信息是不是:

22:49:16.818 [error] CRASH REPORT Process <0.5822.64> with 0 neighbours crashed with reason: timeout_value in gen_server:loop/6 line 358
22:49:16.818 [error] Supervisor ecrn_cron_sup had child ecrn_agent started with ecrn_agent:start_link(#Ref<0.0.11.11209>, {{once,4294968},{io,fwrite,["Hello, world!~n"]}}) at <0.5822.64> exit with reason timeout_value in context child_terminated
22:49:16.819 [error] CRASH REPORT Process <0.5701.64> with 0 neighbours crashed with reason: timeout_value in gen_server:loop/6 line 358
22:49:16.821 [error] Supervisor ecrn_cron_sup had child ecrn_agent started with ecrn_agent:start_link(#Ref<0.0.11.11209>, {{once,4294968},{io,fwrite,["Hello, world!~n"]}}) at <0.5701.64> exit with reason timeout_value in context child_terminated
22:49:16.821 [error] CRASH REPORT Process <0.6237.64> with 0 neighbours crashed with reason: timeout_value in gen_server:loop/6 line 358
22:49:16.821 [error] Supervisor ecrn_cron_sup had child ecrn_agent started with ecrn_agent:start_link(#Ref<0.0.11.11209>, {{once,4294968},{io,fwrite,["Hello, world!~n"]}}) at <0.6237.64> exit with reason timeout_value in context child_terminated
22:49:16.821 [error] CRASH REPORT Process <0.5862.64> with 0 neighbours crashed with reason: timeout_value in gen_server:loop/6 line 358
22:49:16.821 [error] Supervisor ecrn_cron_sup had child ecrn_agent started with ecrn_agent:start_link(#Ref<0.0.11.11209>, {{once,4294968},{io,fwrite,["Hello, world!~n"]}}) at <0.5862.64> exit with reason timeout_value in context child_terminated ...(总共有25条)

再看一下进程数:

我擦,为毛原来的 scrn_agent 进程也没有了。

可以发现,erlcron 在尝试了25次设置 这个定时任务之后,也就是 scrn_agent 崩溃了25次之后,原来设置的三个正常的定时任务的scrn_agent 进程也没有掉了。 也就是说,不但我新设置的定时任务没有成功,而且我原来正常的定时任务也没有掉了。

再看一下崩溃日志里面的崩掉的进程号,每一个都是不一样的。可以推算其实原来的报错ecrn_reg:get(AlarmRef)获取到了多个Pid,其实就是这里插入失败的定时任务产生的25个Pid。也就是说,虽然ecrn_agent进程崩溃了,但是ecrn_reg还是保存了这些Pid。所以在取消这些定时任务的时候,ecrn_reg:get(AlarmRef)返回的内容在internal_cancel(AlarmRef)没有被匹配到。

为什么是4294968,其实是2^32

为什么设置了4294968秒后的定时任务就崩溃了。这个数估计很多人很熟悉,2^32=4294967296,而4294968000也就是刚好大于2^32。即,如果设置的定时任务超过了2^32毫秒,在erlcron里面就不支持了。

查看gen_server:loop的源码,找到引起崩溃的代码:

loop(Parent, Name, State, Mod, hibernate, Debug) ->
proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
loop(Parent, Name, State, Mod, Time, Debug) ->
Msg = receive
Input ->
Input
after Time ->
timeout
end,
decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false).

可以发现引起崩溃的,358行是一段receive代码。也就是说receive是不支持超过2^32大小的。

自测了一下,的确如果receiveafter后面如果是大于等于2^32的数值就会出现bad receive timeout value的报错。查看官方解释,已经明确说明不能大于32位大小。

ExprT is to evaluate to an integer. The highest allowed value is 16#FFFFFFFF, that is, the value must fit in 32 bits. receive..after works exactly as receive, except that if no matching message has arrived within ExprT milliseconds, then BodyT is evaluated instead. The return value of BodyT then becomes the return value of the receive..after expression.

引用自:http://erlang.org/doc/reference_manual/expressions.html

再回到erlcron, 在 ecrn_agent:start_link的时候,ecrn_agent:init执行完ecrn_reg:register(JobRef, self())返回{ok, NewState, Millis}gen_server之后,Millis如果超过2^32gen_server:loop就会引起gen_servertimeout_value异常退出。

%% @private
init([JobRef, Job]) ->
State = #state{job=Job,
alarm_ref=JobRef},
{DateTime, Actual} = ecrn_control:datetime(),
NewState = set_internal_time(State, DateTime, Actual),
case until_next_milliseconds(NewState, Job) of
{ok, Millis} when is_integer(Millis) ->
ecrn_reg:register(JobRef, self()),
{ok, NewState, Millis};
{error, _} ->
{stop, normal}
end.

最后

这坑踩的,有点郁闷。其实这跟erlcron也没关系,也不是gen_server的问题。而是erlang自身receive不支持2^32引起的。继续往下查其实可以发现,再往下是其它语言写的了。

-module(prim_eval).

%% This module is simply a stub which abstract code gets included in the result
%% of compilation of prim_eval.S, to keep Dialyzer happy. -export(['receive'/2]). -spec 'receive'(fun((term()) -> nomatch | T), timeout()) -> T.
'receive'(_, _) ->
erlang:nif_error(stub).

与君共勉

*:first-child {
margin-top: 0 !important;
}

body>*:last-child {
margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}

h1 {
font-size: 28px;
color: #000;
}

h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}

h3 {
font-size: 18px;
}

h4 {
font-size: 16px;
}

h5 {
font-size: 14px;
}

h6 {
color: #777;
font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
color: #4183C4;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
padding-left: 30px;
}

ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}

dl {
padding: 0;
}

dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}

dl dt:first-child {
padding: 0;
}

dl dt>:first-child {
margin-top: 0px;
}

dl dt>:last-child {
margin-bottom: 0px;
}

dl dd {
margin: 0 0 15px;
padding: 0 15px;
}

dl dd>:first-child {
margin-top: 0px;
}

dl dd>:last-child {
margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}

pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}

pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}

pre code, pre tt {
background-color: transparent;
border: none;
}

kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}

blockquote>:first-child {
margin-top: 0px;
}

blockquote>:last-child {
margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}

/* TABLES
=============================================================================*/

table th {
font-weight: bold;
}

table th, table td {
border: 1px solid #ccc;
padding: 6px 13px;
}

table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}

table tr:nth-child(2n) {
background-color: #f8f8f8;
}

/* IMAGES
=============================================================================*/

img {
max-width: 100%
}
-->

[erlang]一次erlcron崩溃引起的事故分析的更多相关文章

  1. 记一次 .NET 某供应链WEB网站 CPU 爆高事故分析

    一:背景 1. 讲故事 年前有位朋友加微信求助,说他的程序出现了偶发性CPU爆高,寻求如何解决,截图如下: 我建议朋友用 procdump 在 cpu 高的时候连抓两个dump,这样分析起来比较稳健, ...

  2. 记一次某制造业ERP系统 CPU打爆事故分析

    一:背景 1.讲故事 前些天有位朋友微信找到我,说他的程序出现了CPU阶段性爆高,过了一会就下去了,咨询下这个爆高阶段程序内部到底发生了什么? 画个图大概是下面这样,你懂的. 按经验来说,这种情况一般 ...

  3. mmzb游戏事故分析

    最近一次线上更新,老项目挂了,遍地哀嚎,日活跃掉了好多,心痛... 这次维护时,SA为了缩减硬件资源,做了一次数据库迁移.给到开发手上的player db,只有一些索引数据,不带有任一玩家数据.玩家上 ...

  4. 再记一次 应用服务器 CPU 暴高事故分析

    一:背景 1. 前言 大概有2个月没写博客了,不是不想写哈

  5. iserver频繁崩溃、内存溢出事故解决小记

    一.事故分析 在生产项目中,频繁遇到iserver隔一段时间就会出现崩溃的情况. 将iserver错误日志发给技术客服后,说是内存溢出的问题. 查看服务器的配置是32g内存,按理说不该出现此类问题. ...

  6. iOS-----App闪退,程序崩溃---解决方案

    1.iOS-中app启动闪退的原因 2.iOS开发-闪退问题-解决之前上架的 App 在 iOS 9 会闪退问题 3.iOS-应用闪退总结 4.iOS开发-捕获程序崩溃日志 5.iOS开发-应用崩溃日 ...

  7. IIS崩溃时自动抓取Dump

    背景:在客户现场,IIS有时会崩溃,开发环境没法重现这个bug,唯有抓取IIS的崩溃是的Dump文件分析. IIS崩溃时自动抓取Dump,需要满足下面几个条件 1.启动 Windows Error R ...

  8. jvm 之 国际酒店 8 月 19 一次full GC 导致的事故

    事故经过: 1  15:18收到短信报警:国际酒店调用OMS queryGorderOrderList方法失败:成单接口调用OMS获取token失败. 2  查看checkList发现15:18开始发 ...

  9. android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地

    http://blog.csdn.net/jason0539/article/details/45602655 应用发生crash之后要查看log,判断问题出在什么地方,可是一旦应用发布出去,就要想办 ...

随机推荐

  1. 【转】Mac系统中安装homebrew(类似redhat|Centos中的yum;类似Ubuntu中的apt-get)

    Homebrew,Homebrew简称brew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件,可以说Homebrew就是mac下的apt-get.yum神器 Homebr ...

  2. caffe源码阅读(一)convert_imageset.cpp注释

    PS:本系列为本人初步学习caffe所记,由于理解尚浅,其中多有不足之处和错误之处,有待改正. 一.实现方法 首先,将文件名与它对应的标签用 std::pair 存储起来,其中first存储文件名,s ...

  3. Mac Aria2 使用Privoxy将socks代理转化为http代理

    安装Privoxy 打开终端安装privoxy来实现这里我是通过brew来进行的安装 brew install privoxy 看到这行已经安装成功 ==> Caveats To have la ...

  4. php中的魔术方法

    __construct 构造器是一个魔术方法,当对象被实例化时它会被调用.在一个类声明时它常常是第一件做的事但是没得必要他也像其他任何方法在类中任何地方都可以声明,构造器也能像其他方法样继承.如果我们 ...

  5. 《CoffeeScript应用开发》学习:第五章 CoffeeScript中的类

    在CoffeeScript中定义类 在CoffeeScript中,使用class定义类,使用关键字new实例化对象. 给类绑定方法 class Airplane takeOff: -> cons ...

  6. 9.openssl ca

    用于签名证书请求.生成CRL.维护一个记录已颁发证书和这些证书状态的数据库. 证书请求私用CA的私钥签名之后就是证书. [root@xuexi tmp]# man ca SYNOPSIS openss ...

  7. Android Studio中清单文件改versionCode和versionName没效果的原因

    在Android Studio中,项目的versionCode 和versionName 的控制不是在AndroidManifest.xml清单文件中更改的,而是在项目的build.gradle中更改 ...

  8. BulkSqlCopy 批量导入数据(Ef支持)

    Ado.net对批量数据的支持相信大家都已经非常熟悉.再此就不在多说,就当是给自己备个份,没办法,这个方法太好用了. public static void BulkCreate( string tab ...

  9. Jenkins+Jmeter+Ant接口持续集成

    修改时间 修改内容 修改人 2016.5.22 创建 刘永志 2016.6.15 完成 刘永志 前言: 为什么要用Jmeter做接口测试: 当选择这套方案的时候,很多人会问,为什么选择Jmeter做C ...

  10. CentOS上安装spark standalone mode(转载)

    原文链接 http://blog.csdn.net/chenxingzhen001/article/details/11072765 参考: http://spark.incubator.apache ...