« Lua 5.2 如何实现 C 调用中的 Continuation | 返回首页 | Lua 5.2.1 的一处改变 »

在 C 中设置 Lua 回调函数引起的一处 bug

我们的服务器框架提供了一个 C 接口, 在 RPC 调用时, 回调一个事先注册的函数.

C 中标准的回调函数的接口设计, 标准方法是设置一个 C 函数指针加一个 void * 类型的数据指针.

由于我们的游戏逻辑使用 Lua 来实现, 所以这里只需要实现一个 C 函数去调 Lua 机里的函数, 而对应的 void * 自然就是 lua_State *

今天,同事在实现服务的热更新功能。发现多次热更新 lua 写的服务会导致一处 core dump ,一直没有找到原因。通过阅读代码,我仔细思考后,确定了 bug 所在。

我们的热更新是通过重新初始化 lua 中的各种状态,且重置 skynet 框架的 callback 函数实现的。

热更新并不会重新打开一个新的 lua state 而是在原有的 state 中进行的,这样才能继承一些不需要更新的数据和状态。

热更新的触发也是通过响应一个 RPC 调用来触发的。准确的说,是在前一次注册的 callback 函数中进行的。

问题在于,触发热更新机制是由一个 lua rpc 函数启动。而每个 lua rpc 调用都被包装成了一个独立的 coroutine 。

lua 的 coroutine 在 lua 的术语中被称为 thread 。每个 thread 都有一个独立的 lua state 。所以,重置 skynet 的 callback 函数时,C 上下文中的 state L 是一个 thread 的 L ,而不是 lua 创建时 main thread 的 L 。

当代码热更新几次后,先前的那些临时的 rpc 调用的 thread 被垃圾回收。L 指针变得无效了。而在当初设置 skynet 的 callback 函数时却传入了它。这导致了 callback 函数工作时,访问了一个已经被释放的 L 指针。

正确的方法是,当你在 C 里想保存 L 供以后 callback 函数使用时,不要轻易使用当前上下文的 L 。因为调用你的 C 函数的 lua 函数,不一定是在主线程中。lua 5.2 获取主线程的方法是:

  lua_rawgeti(L,  LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
  lua_State *mL = lua_tothread(L,-1);
  lua_pop(L,1);

Comments

@dwing
问题在于即使在同一进程内开多个 luajit 的 state 它们加起来也只能用 1G 内存, 这才是问题.

@Cloud

没错,我也知道luajit的内存限制,但根本原因在于gc算法,即使用到1G内存,gc一轮的时间也很长,这是当前官方lua也没办法解决的效率问题.
只能期待luajit2.1的分代gc了.

@dwing

等明年的 luajit 2.1
2.0 现在有 1G 内存限制.

去年云风还说“我比较在意性能,暂时先投靠 luajit 了。反正和 5.2 区别也不大。”
今年貌似专攻lua 5.2了, 再没提到luajit, 有什么原因么?

哈哈,虽然不做lua和c,但是还是来围观下,不知道时候会用到的,多学无害,哈哈

别用coroutine了,这东西不靠谱,不好热更新,占资源,不好调试

我们使用的luajit2b10,首先可以排除是如云风这里所说的协程回收引起的,因为发现每次创建协程执行一个函数再立刻释放会有比较大的cpu损耗(lua5.1),而且decoda会跟踪不了,后来改用协程池来实现,不出错的情况下是不会释放的。当然也有可能是协程出错了导致释放引起core dump,这个再仔细分析一下。

我们游戏服务器老是GC引发崩溃,一直找不出什么原因。。

我们要是每次core dump都写个blog,都能出书了

Post a comment

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