在使用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. 一点点VIM

    VIM 当你喜欢它时,你会发现真的不错,不过配置真是麻烦, 不过万事开头难,当你熟练时真的会发现她的美. syntax on set nu colo evening set mouse=a set c ...

  2. Xcode not building app with changes incorporated

    Did you clean the build folder by pressing command while the cursor is on the clean option? Are you ...

  3. Android ZXing 二维码、条形码扫描介绍

    本帖最后由 Shims 于 2013-11-9 12:39 编辑 最近公司的Android项目需要用到摄像头做条码或二维码的扫描,Google一下,发现一个开源的 ZXing项目.它提供二维码和条形码 ...

  4. HeatMap(热图)的原理和实现

    先来看两张图: (1)10年世界杯决赛,冠军西班牙队中门将.后卫.中场及前锋的跑位热图 通过热图,我们可以很清楚的看出四个球员在比赛中跑动位置的差异. (2)历史地震震源位置的热图 也可以很清楚的看出 ...

  5. 【Todo】Java的JIT机制

    先是参考了这篇说的不怎么详细的文章<Java的JIT机制>(Link) JIT是just in time,即时编译技术.使用该技术,能够加速java程序的执行速度. 通常javac将程序源 ...

  6. Leetcode第1题至第10题 思路分析及C++实现

    笔者按照目录刷题,对于每一道题,力争使用效率最高(时间复杂度最低)的算法,并全部通过C++代码实现AC.(文中计算的复杂度都是最坏情况复杂度) 因为考虑到大部分读者已经在Leetcode浏览过题目了, ...

  7. C语言之基本算法34—分解质因数(方法一)

    //矩阵基础 /* ================================================================== 题目:输入一个正整数.将其分解为质因式,如:6 ...

  8. centos下的hadoop服务器的配置

    是我安装CentOS服务器的过程,记录下来,与大家一起分享. 安装操作系统 CentOS 6.2 ,CentOS-6.2-i386-bin-DVD1.iso(32位) ,CentOS-6.2-x86_ ...

  9. HDU 3305 Ice-sugar Gourd

    Ice-sugar Gourd Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)T ...

  10. C# 通过window消息控制指定控件的scroll滚动

     [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]         private st ...