Lua_API lua_atpanic

开篇

今天继续往下解析第 4 个 API :lua_atpanic 。

解析

释义

panic 有崩溃的意思。望文生义,我们可以猜测 lua_atpanic 是处置 Lua 程序崩溃时的方法。且看官方参考文档对 lua_atpanic 的解释:

Sets a new panic function and returns the old one (see §4.6).
设置一个新的 panic 函数,并返回之前设置的那个。

§4.6 – Error Handling in C

在内部实现中,Lua 使用了 C 的 longjmp 机制来处理错误。(如果你使用 C++ 编译,Lua 将换成异常;细节请在源代码中搜索 LUAI_THROW。) 当 Lua 碰到任何错误 (比如内存分配错误、类型错误、语法错误、还有运行时错误)它都会抛出一个错误出去; 也就是调用一次长跳转。在保护环境下,Lua 使用 setjmp 来设置一个恢复点;任何发生的错误都会跳转到最近的一个恢复点。

如果错误发生在保护环境之外, Lua 会先调用 panic 函数 (参见 lua_atpanic) 然后调用 abort 来退出宿主程序。 你的 panic 函数只要不返回 (例如:长跳转到你在 Lua 外你自己设置的恢复点) 就可以不退出程序。panic 函数以错误消息处理器(参见 §2.3)的方式运行;错误消息在栈顶。 不同的是,它不保证栈空间。 做任何压栈操作前,panic 函数都必须先检查是否有足够的空间 (参见 §4.2)。

大多数 API 函数都有可能抛出错误,例如在内存分配错误时就会抛出。每个函数的文档都会注明它是否可能抛出错误。

在 C 函数内部,你可以通过调用 lua_error 来抛出错误。

实现

LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) {
  lua_CFunction old;
  lua_lock(L);
  old = G(L)->panic;
  G(L)->panic = panicf;
  lua_unlock(L);
  return old;
}

其中,lua_CFunction 是个函数指针,定义在 lua.h 105:

/*
** Type for C functions registered with Lua
*/
typedef int (*lua_CFunction) (lua_State *L);

G(L) 是个宏定义,意在取得 Ll_G 域中的数据:

// lstate.h 186
#define G(L)    (L->l_G)

我们之前一直没有分析过 lua_State 的结构,原因是因为它太复杂了,一时半会讲不清楚,只好一层一层去剥开,去理解。现在我们找到它的定义:

/*
** 'per thread' state
*/
struct lua_State {
  CommonHeader;
  unsigned short nci;  /* number of items in 'ci' list */
  lu_byte status;
  StkId top;  /* first free slot in the stack */
  global_State *l_G;
  CallInfo *ci;  /* call info for current function */
  const Instruction *oldpc;  /* last pc traced */
  StkId stack_last;  /* last free slot in the stack */
  StkId stack;  /* stack base */
  UpVal *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist;
  struct lua_State *twups;  /* list of threads with open upvalues */
  struct lua_longjmp *errorJmp;  /* current error recover point */
  CallInfo base_ci;  /* CallInfo for first level (C calling Lua) */
  volatile lua_Hook hook;
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
  int stacksize;
  int basehookcount;
  int hookcount;
  unsigned short nny;  /* number of non-yieldable calls in stack */
  unsigned short nCcalls;  /* number of nested C calls */
  l_signalT hookmask;
  lu_byte allowhook;
};

看到 l_Gglobal_State * 类型,global_State 是个结构体:

/*
** 'global state', shared by all threads of this state
*/
typedef struct global_State {
  lua_Alloc frealloc;  /* function to reallocate memory */
  void *ud;         /* auxiliary data to 'frealloc' */
  l_mem totalbytes;  /* number of bytes currently allocated - GCdebt */
  l_mem GCdebt;  /* bytes allocated not yet compensated by the collector */
  lu_mem GCmemtrav;  /* memory traversed by the GC */
  lu_mem GCestimate;  /* an estimate of the non-garbage memory in use */
  stringtable strt;  /* hash table for strings */
  TValue l_registry;
  unsigned int seed;  /* randomized seed for hashes */
  lu_byte currentwhite;
  lu_byte gcstate;  /* state of garbage collector */
  lu_byte gckind;  /* kind of GC running */
  lu_byte gcrunning;  /* true if GC is running */
  GCObject *allgc;  /* list of all collectable objects */
  GCObject **sweepgc;  /* current position of sweep in list */
  GCObject *finobj;  /* list of collectable objects with finalizers */
  GCObject *gray;  /* list of gray objects */
  GCObject *grayagain;  /* list of objects to be traversed atomically */
  GCObject *weak;  /* list of tables with weak values */
  GCObject *ephemeron;  /* list of ephemeron tables (weak keys) */
  GCObject *allweak;  /* list of all-weak tables */
  GCObject *tobefnz;  /* list of userdata to be GC */
  GCObject *fixedgc;  /* list of objects not to be collected */
  struct lua_State *twups;  /* list of threads with open upvalues */
  unsigned int gcfinnum;  /* number of finalizers to call in each GC step */
  int gcpause;  /* size of pause between successive GCs */
  int gcstepmul;  /* GC 'granularity' */
  lua_CFunction panic;  /* to be called in unprotected errors */
  struct lua_State *mainthread;
  const lua_Number *version;  /* pointer to version number */
  TString *memerrmsg;  /* memory-error message */
  TString *tmname[TM_N];  /* array with tag-method names */
  struct Table *mt[LUA_NUMTAGS];  /* metatables for basic types */
  TString *strcache[STRCACHE_N][STRCACHE_M];  /* cache for strings in API */
} global_State;

可以看出,panicglobal_State 的一个成员,它在发生未保护的错误时会被调用。
因此,lua_atpanic 的作用就是将 global_State 的成员 panic 替换成 panicf,最后返回 panic 给调用者。

我们搜索下源码,看看 lua_atpanic 是如何调用的:

// lauxlib.h 1011
static int panic (lua_State *L) {
  lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n",
                        lua_tostring(L, -1));
  return 0;  /* return to Lua to abort */
}


LUALIB_API lua_State *luaL_newstate (void) {
  lua_State *L = lua_newstate(l_alloc, NULL);
  if (L) lua_atpanic(L, &panic);
  return L;
}

原来 Lua 默认定义了一个 panic 函数,并且在创建新的 lua_State 线程状态的时候都会将默认的 panic 注册到 global_State 下。

其实不太懂在注册新的 panic 函数时为什么要返回之前的 panic 函数,可能是为了暂时替换而后还需要恢复吧。现在说不清楚,因为没有可应用的场景或者想象不出那样的应用场景,且留待之后回答。

终了

  • 过于复杂的结构可以先放过,不求甚解,但脑袋里一定得有点概念。

你可能感兴趣的:(Lua_API lua_atpanic)