在 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
Posted by: Cloud | (9) July 12, 2012 10:08 AM
Posted by: dwing | (8) July 12, 2012 09:47 AM
Posted by: Cloud | (7) July 11, 2012 11:20 AM
Posted by: dwing | (6) July 10, 2012 05:49 PM
Posted by: lite3 | (5) July 10, 2012 01:03 PM
Posted by: 技术爱好者 | (4) July 10, 2012 10:14 AM
Posted by: bbp | (3) July 9, 2012 10:42 PM
Posted by: spracle | (2) July 9, 2012 10:33 PM
Posted by: gf | (1) July 9, 2012 09:18 PM