在使用skynet开发时,你也许会碰到类似这样的警告:A message from [ :0100000f ] to [ :0100000a ] maybe in an endless loop (version = 137)
    它表示你的代码在某处陷入了死循环。但是如何找到死循环的点呢?可以这样做:
    1)本机登陆skynet控制台:nc 127.0.0.1 8000 ==> telnet登录skynet
    2)输入命令:signal addr 0                 ==> 向目标服务地址addr发送信号0,在本例中应为:signal :0100000a 0
    此时目标地址addr所指服务会退出死循环并打印错误堆栈。关于进一步使用skynet console的教程,请参考skynet在git上的wiki:https://github.com/cloudwu/skynet/wiki/DebugConsole
    死循环检测的问题解决了,让我们趁着这个机会,探索下这背后的原理吧。
    skynet console是通过debug_console.lua来实现的,可以看到signal函数:

 function COMMAND.signal(address, sig)
address = skynet.address(adjust_address(address))
if sig then
core.command("SIGNAL", string.format("%s %d",address,sig))
else
core.command("SIGNAL", address)
end
end

顺藤摸瓜,我们来到skynet_server.c中的cmd_signal函数:

 static const char *
cmd_signal(struct skynet_context * context, const char * param) {
uint32_t handle = tohandle(context, param);
if (handle == )
return NULL;
struct skynet_context * ctx = skynet_handle_grab(handle);
if (ctx == NULL)
return NULL;
param = strchr(param, ' ');
int sig = ;
if (param) {
sig = strtol(param, NULL, );
}
// NOTICE: the signal function should be thread safe.
skynet_module_instance_signal(ctx->mod, ctx->instance, sig); skynet_context_release(ctx);
return NULL;
}

解析出signal参数后调用skynet_module_instance_signal:

 void
skynet_module_instance_signal(struct skynet_module *m, void *inst, int signal) {
if (m->signal) {
m->signal(inst, signal);
}
}

其调用通过动态库加载的API:m->signal,它是在加载skynet module时动态加载的,Lua服务对应的module是snlua,我们看下service_snlua.c中的snlua_signal函数做了什么:

 void
snlua_signal(struct snlua *l, int signal) {
skynet_error(l->ctx, "recv a signal %d", signal);
if (signal == ) {
#ifdef lua_checksig
// If our lua support signal (modified lua version by skynet), trigger it.
skynet_sig_L = l->L;
#endif
} else if (signal == ) {
skynet_error(l->ctx, "Current Memory %.3fK", (float)l->mem / );
}
}

可以看到,signal为0时,将skynet_sig_L置空。那么skynet_sig_L是干嘛用的呢?在lua的lvm.c中有如下定义:

 /* Add by skynet */
lua_State * skynet_sig_L = NULL; LUA_API void
lua_checksig_(lua_State *L) {
if (skynet_sig_L == G(L)->mainthread) {
skynet_sig_L = NULL;
lua_pushnil(L);
lua_error(L);
}
}

即如果skynet_sig_L为lua主线程G(L)->mainthread的话,那么将其置空并主动报错,在lua_error中会展开堆栈信息。

在lua.h中定义了lua_checksig宏:

 #define lua_checksig(L) if (skynet_sig_L) { lua_checksig_(L); }

查找此宏的引用点:

在虚拟机指令:跳转OP_JMP、尾调用OP_TAILCALL、for循环OP_FORLOOP、OP_TFORLOOP处都会做checksig检查。除了for循环之外的其它循环,比如while或repeat,在LUA中都是通过条件判断结合JMP跳转来实现的,因此也是可以被检查报错的。对于无限递归的情况,如果递归函数可以被优化成尾调用的话,那么会在TAILCALL中被检查并报错。至此,如何打断死循环并报错跳出的处理我们已经清楚了,可是新的问题又来了,skynet中是如何检测到死循环发生的呢?

回想skynet启动时,会创建monitor线程,监视各个线程对应的skynet_monitor参数的情况:

 static void *
thread_monitor(void *p) {
struct monitor * m = p;
int i;
int n = m->count;
skynet_initthread(THREAD_MONITOR);
for (;;) {
CHECK_ABORT
for (i=;i<n;i++) {
skynet_monitor_check(m->m[i]);
}
for (i=;i<;i++) {
CHECK_ABORT
sleep();
}
} return NULL;
}

skynet_monitor_check监视的内容:

 void
skynet_monitor_check(struct skynet_monitor *sm) {
if (sm->version == sm->check_version) {
if (sm->destination) {
skynet_context_endless(sm->destination);
skynet_error(NULL, "A message from [ :%08x ] to [ :%08x ] maybe in an endless loop (version = %d)", sm->source , sm->destination, sm->version);
}
} else {
sm->check_version = sm->version;
}
}

skynet_monitor有个version参数,上节中我们讨论过消息分发,每次消息分发时,分发前置monitor->destination并自增version,分发后置monitor->destination为空并自增version(为简化,以下代码做了裁剪):

 struct message_queue *
skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {
uint32_t handle = skynet_mq_handle(q);
struct skynet_context * ctx = skynet_handle_grab(handle); int i,n=;
struct skynet_message msg; for (i=;i<n;i++) {
if (skynet_mq_pop(q,&msg)) {
skynet_context_release(ctx);
return skynet_globalmq_pop();
} skynet_monitor_trigger(sm, msg.source , handle);
dispatch_message(ctx, &msg);
skynet_monitor_trigger(sm, ,);
} assert(q == ctx->queue);
struct message_queue *nq = skynet_globalmq_pop();
if (nq) {
skynet_globalmq_push(q);
q = nq;
}
skynet_context_release(ctx); return q;
}

这样,当线程X处理消息陷入长久的阻滞时,monitor线程便会检测到X正在处理消息(skynet_monitor->destination不为空)并且version未改变,给出警告。

skynet源码阅读<7>--死循环检测的更多相关文章

  1. skynet源码阅读<1>--lua与c的基本交互

    阅读skynet的lua-c交互部分代码时,可以看到如下处理: struct skynet_context * context = lua_touserdata(L, lua_upvalueindex ...

  2. skynet源码阅读<3>--网关分析

    继上一篇介绍了skynet的网络部分之后,这一篇以网关gate.lua为例,简单分析下其串接和处理流程. 在官方给出的范例中,是以examples/main.lua作为启动脚本的,在此过程中会创建wa ...

  3. skynet 源码阅读笔记 bootstrap.lua

    最近几周粗略看了 skynet 代码的 C 部分.遇到很多知识点以前只是知道,但并不十分了解,所以这是一个学习的过程. 从 main 函数开始,闷头一阵看下来,着实蛋疼. 当看了 skynet_mq. ...

  4. skynet源码阅读<5>--协程调度模型

    注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入.    作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...

  5. skynet源码阅读<6>--线程调度

    相比于上节我们提到的协程调度,skynet的线程调度从逻辑流程上来看要简单很多.下面我们就来具体做一分析.首先自然是以skynet_start.c为入口: static void start(int ...

  6. skynet源码阅读<4>--定时器实现

    昨天和三石公聊天,他提到timer的实现原理,我当时迟疑了一下,心想timer不是系统底层时钟中断驱动上层进程/线程,累积计时实现的么?他简述了timer的实现,什么堆排序,优先级队列等,与我想象的不 ...

  7. skynet源码阅读<2>--网络部分

    先来看下socket_server的数据结构,这里简称为ss: struct socket_server { int recvctrl_fd; int sendctrl_fd; int checkct ...

  8. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  9. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

随机推荐

  1. treetable 用法小例

    插件地址:http://pan.baidu.com/s/1kVf0Kcfcript src="/plugins/jQuery/jQuery-2.1.4.min.js">< ...

  2. raspi集成库及安装

    原文:http://blog.csdn.net/xukai871105/article/details/12684617   树莓派来自国外,国外嵌入式开源领域具有良好的分享精神,树莓派各种集成库也层 ...

  3. Zabbix监控Mongo

    安装Zabbix-agent # groupadd zabbix # useradd -g zabbix zabbix # yum -y install gcc mysql-community-dev ...

  4. Android 支付宝快捷支付集成及ALI64错误的有效解决

    支付宝开放平台採用了RSA安全签名机制,开发人员能够通过支付宝公钥验证消息来源.同一时候可使用自己的私钥对信息进行加密. RSA算法及数字签名机制是支付宝开放平台与开发人员网关安全通信的基础.若开发人 ...

  5. Xcode 技巧充电篇

    作为project师,我们最重要的事情就是熟悉我们每天使用的日常工具,但不能仅限于此.仅仅要有可能,我们应该试着掌握和定制能使我们更快.更轻松地实现终于目标的工具.以下是一些小提示和技巧,都是我在 X ...

  6. 从Nginx源代码谈大写和小写字符转化的最高效代码以及ASCII码表的科学

    说起大写和小写字母转换.大家非常easy想起系统函数是不是,差点儿全部的编程语言都提供了这样的转换函数,可是你有没有想过这背后是怎么实现的? 让你写怎么实现? 我们都知道Nginx是眼下用的最多的Ht ...

  7. JSP简明教程:汇总

    原创JSP教程,简洁明了,不含废话. JSP简明教程(一):JSP简单介绍 JSP简明教程(二):JSP基本的语法 JSP简明教程(三):JSP隐含对象 JSP简明教程(四):EL表达式语言.Java ...

  8. Android设置系统开机自己主动永不休眠

    android4.2系统增永不休眠同一时候隐藏休眠选项 1. android\frameworks\base\packages\SettingsProvider\res\values\defaults ...

  9. PostgreSQL 源码解读 node的模拟实现

      node的实现是PostgreSQL的查询解析的基础,实现的关键是两个宏,makeNode和newNode.其他节点继承自Node节点,如果增加新的结构体,需要添加NodeTag中添加对应的枚举值 ...

  10. iOS开发 - App程序启动原理

    Info.plist和pch文件的作用 建立一个project后,会在Supporting files目录下看到一个"project名-Info.plist"的文件,该文件对pro ...