skynet的callback

  • 创建新的 userdata
  • 创建一个新 thread 对象,把 callback function 至于这个 thread 中
  • 把新 thread 绑定 userdata 的 uservalue 上(防止 gc)
  • userdata 的 C 指针放到 C 端,用于 callback 调用
  • callback function 放到新 thead 中而不是注册表中是因为这样做比从注册表中读会 callback function 要高效
  122 static int
  123 lcallback(lua_State *L) {
  124 >   struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
  125 >   int forward = lua_toboolean(L, 2);  // 获取第二个参数
  126 >   luaL_checktype(L,1,LUA_TFUNCTION);  // 检查第一个参数是否是 lua function
  127 >   lua_settop(L,1); // 弹出除第一个参数外的所有参数,即 lua function 在 L 的栈底
  128 >   struct callback_context * cb_ctx = (struct callback_context *)lua_newuserdatauv(L, sizeof(*cb_ctx), 2); // 创建 userdata callback_context 并压入到 L 的栈中
  129 >   cb_ctx->L = lua_newthread(L);  // 创建一个新线程并压入 L 的栈中
  130 >   lua_pushcfunction(cb_ctx->L, traceback); // 将 c 函数 traceback 压入新线程的栈中
  131 >   lua_setiuservalue(L, -2, 1);  // 将新线程弹出并设置为 128 创建的 userdata 的 uservalue
  132 >   lua_getfield(L, LUA_REGISTRYINDEX, "callback_context"); // 将注册表下标为 "callback_context" 的值压入 L 的栈中
  133 >   lua_setiuservalue(L, -2, 2); // 将上面压入的值设置为 128 创建的 userdata 的 uservalue
  134 >   lua_setfield(L, LUA_REGISTRYINDEX, "callback_context"); // 将 128 创建的 userdata 设置为注册表下标为 "callback_context" 的值
  135 >   lua_xmove(L, cb_ctx->L, 1); // 将 userdata 从 L 中弹出并压入新线程中
  136
  137 >   skynet_callback(context, cb_ctx, (forward)?(_forward_pre):(_cb_pre)); // 将 _cb_pre/_forward_pre 设置为 context 的 callback,两种区别为是否自动释放 skynet msg 的空间
  138 >   return 0;
  139 }
  • 第132~133行把旧的 userdata 设置为新 userdata 的 uservalue,是因为在重新设置 callback 时,如果直接替换掉旧的 userdata 时,这只旧 userdata 就处于可被 gc 的状态,如果此时触发 gc 行为,将会导致当前正在被执行的 coroutine 和 cb_ctx 被回收掉,从而导致crash。具体如下:skynet callback导致的crash问题
  • Lua binding 中正确的 callback