« 开发笔记 (9) :近期工作小结 | 返回首页 | libuv 初窥 »

一个链接 lua 引起的 bug , 事不过三

今天花了将近 3 个小时帮同事看一个崩在 lua VM 中的 bug 结果打乱了进度,没有在年前把预想的东西做完。其实说起来这不是个大问题,以前也碰到过。我检讨自己没有在看到出错时的调用栈时去看一眼 lua 相关的代码。如果是那样,因为以前遇到过同样的问题,所以就可以条件反射出问题原因,而不用荒废宝贵了数小时时间了。

唉,这下整合的进度没接上,过年不能自己一个人接着做下面的活了。

下面记录一下这个 bug ,提醒自己第三次遇到时不用再花时间找问题:

问题的缘由是因为的不同的 lua 扩展库链接的时候多链接了一份 lua 库,导致进程内有超过一份 lua 库。这是在写 Makefile 时不小心造成的。知道原因后自然很容易解决。要么把 lua 编译成 .so ;要么只有嵌入的主框架使用 -E 参数静态链接 lua 的静态库。这里就不展开说了。

用了 lua 十年,绝对不是第一次帮其他用 lua 的同学指出这个问题。下面要讨论的是错误产生后,程序是怎么崩溃的。

lua 的代码中几乎没有使用全局变量。就是说,lua 的 api 的相关状态仅存在于参数 L 中,而和库无关。这点 lua 实现的是很漂亮的。对于一个无外部状态的 api ,理论上无论代码段在进程内被链接再多次,都不会有副作用的。但副作用的确又发生了,为何?

问题出在 ltable.c 中引用了一个静态全局变量 dummynode_ 。这个东西是做什么用的呢?

在 lua table 实现中,为了不让 hash part 为空的时候不引用 NULL 指针(一种优化),而引用了这个 dummynode ,这样就可以减少操作表时的判断操作。

由于这个 dummynode 是静态分配出来的特殊节点,所以是不能调用内存管理函数去释放它的。lua 另外用一个宏来判断一个节点是否为 dummynode 。

#define isdummy(n)      ((n) == dummynode)

当进程中链接了多份 lua 库时,就出现了多份 dummynode 对象,自然 isdummy 的行为就可能不正确了。那么接下来的释放工作则会崩掉,看起来段错误会发生在 lua_Alloc 中。

问题多出现在运行过程中,向空 table 插入第一个 hash 值时,也就是 luaH_resize 函数的最后一行:

 if (!isdummy(nold))
    luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old array */

或是关闭 lua state 时,释放 table 时,的 luaH_free 函数最前面:

  if (!isdummy(t->node))
    luaM_freearray(L, t->node, cast(size_t, sizenode(t)));

之所以我把代码和出错位置列出来,是因为我今天不是第一次专门分析这个点了。如果我下午在刚出问题时,能够在看到 stack traceback 信息的函数名)luaH_resize 打开 lua 的源代码看一眼的话,我相信我能立刻回想起上次分析这个问题的情景,从而节省掉后来浪费在查别的地方上的无尽时间。

希望经过这次,下次有人向我反应类似问题时,我能直接对应到问题。


4 月 19 日补充:

如何在出现这个问题时立刻检查出问题呢?

可以在每个为 lua 写的 C 扩展库的第一行调用一下 luaL_checkversion , 它可以检查有没有多次链接 lua 库, 以及其它一些版本问题.

Comments

谢谢这个帖子,帮我解决了很多疑惑,哈哈!
@damon luaL_checkversion 只是为了检查,并不解决问题.
那么在lua5.1上没有lua_version,又不能够立即升级,似乎是不容易解这个问题了。
这几乎是每一个在项目中用到lua的人都会碰到的问题。。我第一次碰到是4年前,2年前又帮别人指出过一次。。
向你学习!
我感觉到了云风的懊恼,对自己的懊恼
一直在关注云风,期待早点看到你们的新作品!
这个问题原来在Windows里也碰到过,总是dummynode的bug, 没太关注.Linux下估计会严重的多
云风大哥对bug很执着,不能让自己在同个河掉很多次。向你学习!
不知道为什么不实现成在lua_State中保存这个节点,而要用个static

Post a comment

非这个主题相关的留言请到:留言本