多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > php教程 > 淺析lua異常捕獲處理機制

淺析lua異常捕獲處理機制

來源:程序員人生   發布時間:2016-03-29 09:39:27 閱讀次數:7645次

異常捕獲是高級語言的1大特性,通過對異常的捕獲和處理,可以有效提高系統的穩定性和硬朗性。由于不管再怎樣改進代碼,都不可避免出現1些異常,例如文件io毛病、網絡毛病、內存毛病等等,就要求編碼對毛病進行捕獲,同時打印日志以便開發人員跟進問題的處理。固然,lua也提供了接口用于捕獲運行時異常。

lua異常捕獲函數

lua有兩個函數可用于捕獲異常: pcall 和 xpcall,這兩個函數很類似,都會在保護模式下履行函數,效果類似try-catch,可捕獲并處理異常。
兩個函數的原型以下:
pcall (func [, arg1, ???]) xpcall (func, errfunc [, arg1, ???])
對照兩個函數,xpcall多了1個異常處理函數參數 errfunc。對pcall,異常處理完時只簡單記錄毛病信息,然后釋放調用棧空間,而對xpcall,這個參數可用于在調用棧釋放前跟蹤到這些數據。效果以下:
> f=function(...) error(...) end > pcall(f, 123) false stdin:1: 123 > xpcall(f, function(e) print(debug.traceback()) return e end, 123) stack traceback: stdin:1: in function[C]: in function error stdin:1: in function f [C]: in function xpcall stdin:1: in main chunk [C]: in ? false stdin:1: 123
值得注意的是,errfunc的傳入參數是異常數據,函數結束時必須將這個數據返回,才能實現和 pcall 1樣的返回值

lua異常捕獲處理機制

下面,以 pcall 來講明lua異常捕獲是怎樣實現的。這里,lua代碼版本取5.3.1

pcall的實現

pcall的c實現函數為luaB_pcall,以下:
// lbaselib.c static int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); lua_pushboolean(L, 1); // 如果沒毛病產生,返回true lua_insert(L, 1); /* put it in place */ status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); return finishpcall(L, status, 0); }
看下lua_pcallk,這是pcall的預處理函數:
// lapi.c LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k) { struct CallS c; int status; ptrdiff_t func; lua_lock(L); api_check(L, k == NULL || !isLua(L->ci), "cannot use continuations inside hooks"); api_checknelems(L, nargs+1); api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); checkresults(L, nargs, nresults); if (errfunc == 0) func = 0; else { StkId o = index2addr(L, errfunc); api_checkstackindex(L, errfunc, o); func = savestack(L, o); } c.func = L->top - (nargs+1); // 取到pcall要履行的函數 if (k == NULL || L->nny > 0) { /* no continuation or no yieldable? */ c.nresults = nresults; /* do a conventional protected call */ /* 處理pcall(非協程走這里,詳見拓展瀏覽)*/ status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); } else { //當resume協程時履行,已在保護模式下 CallInfo *ci = L->ci; ci->u.c.k = k; /* save continuation */ ci->u.c.ctx = ctx; /* save context */ /* save information for error recovery */ ci->extra = savestack(L, c.func); ci->u.c.old_errfunc = L->errfunc; L->errfunc = func; // 記錄異常處理函數 setoah(ci->callstatus, L->allowhook); /* save value of allowhook */ ci->callstatus |= CIST_YPCALL; // 打標記設置協程恢復點 luaD_call(L, c.func, nresults, 1); /* do the call */ ci->callstatus &= ~CIST_YPCALL; // 履行結束去掉標記 L->errfunc = ci->u.c.old_errfunc; status = LUA_OK; /* if it is here, there were no errors */ } adjustresults(L, nresults); lua_unlock(L); return status; }
這里重點看下luaD_pcall 的實現,這是pcall的核心處理函數:
// ldo.c int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) { int status; CallInfo *old_ci = L->ci; lu_byte old_allowhooks = L->allowhook; unsigned short old_nny = L->nny; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef; // 記錄異常處理函數 status = luaD_rawrunprotected(L, func, u); // 以保護模式運行 if (status != LUA_OK) { // 當異常產生時 StkId oldtop = restorestack(L, old_top); // ‘釋放’調用棧 luaF_close(L, oldtop); /* close possible pending closures */ seterrorobj(L, status, oldtop); L->ci = old_ci; L->allowhook = old_allowhooks; L->nny = old_nny; luaD_shrinkstack(L); } L->errfunc = old_errfunc; return status; }
看下 luaD_rawrunprotected 函數的實現:
// ldo.c int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { unsigned short oldnCcalls = L->nCcalls; struct lua_longjmp lj; lj.status = LUA_OK; lj.previous = L->errorJmp; /* chain new error handler */ L->errorJmp = &lj; LUAI_TRY(L, &lj, (*f)(L, ud); ); // 異常宏處理 L->errorJmp = lj.previous; // 設置毛病跳轉點 L->nCcalls = oldnCcalls; return lj.status; }
LUAI_TRY 和文章后面出現的LUAI_THROW 都是宏實現的,目的是兼容主流c或c++版本的異常處理語法,表現為try-catch的語法結構。簡單說就是,履行代碼前先try,履行進程出錯就throw,然后在catch的地方處理異常。
/* ** LUAI_THROW/LUAI_TRY define how Lua does exception handling. By ** default, Lua handles errors with exceptions when compiling as ** C++ code, with _longjmp/_setjmp when asked to use them, and with ** longjmp/setjmp otherwise. */ #if !defined(LUAI_THROW) /* { */ #if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* { */ /* C++ exceptions */ #define LUAI_THROW(L,c) throw(c) #define LUAI_TRY(L,c,a) try { a } catch(...) { if ((c)->status == 0) (c)->status = ⑴; } #define luai_jmpbuf int /* dummy variable */ #elif defined(LUA_USE_POSIX) /* }{ */ /* in POSIX, try _longjmp/_setjmp (more efficient) */ #define LUAI_THROW(L,c) _longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf #else /* }{ */ /* ISO C handling with long jumps */ #define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf #endif /* } */ #endif /* } */
當異常出現時,status 就會賦值為⑴,即不等于 LUA_OK



xpcall和pcall的區分

對照下xpcall對照pcall,有甚么區分?

// lbaselib.c static int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); lua_pushboolean(L, 1); /* first result if no errors */ lua_insert(L, 1); /* put it in place */ status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); return finishpcall(L, status, 0); } static int luaB_xpcall (lua_State *L) { int status; int n = lua_gettop(L); luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ lua_pushboolean(L, 1); /* first result */ lua_pushvalue(L, 1); // 將異常處理函數 errfunc 寫入調用棧 lua_rotate(L, 3, 2); /* move them below functions arguments */ status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall); return finishpcall(L, status, 2); }
再回頭看下lua_pcallk 
// lapi.c LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k) { struct CallS c; int status; ptrdiff_t func; lua_lock(L); api_check(L, k == NULL || !isLua(L->ci), "cannot use continuations inside hooks"); api_checknelems(L, nargs+1); api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); checkresults(L, nargs, nresults); if (errfunc == 0) func = 0; // pcall時 func為0 else { StkId o = index2addr(L, errfunc); api_checkstackindex(L, errfunc, o); func = savestack(L, o); // xpcall取到異常處理函數 errfunc }
然后, func 賦值給 L->errfunc,在異常產生時,就檢查1下這個函數。

甚么地方履行這個毛病處理函數?
// ldebug.c l_noret luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { // 如果有異常處理函數 errfunc StkId errfunc = restorestack(L, L->errfunc); setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ L->top++; /* assume EXTRA_STACK */ luaD_call(L, L->top - 2, 1, 0); /* call it */ } luaD_throw(L, LUA_ERRRUN); }
luaD_throw則是調用 LUAI_THROW,跳到前面catch的位置
// ldo.c l_noret luaD_throw (lua_State *L, int errcode) { if (L->errorJmp) { /* thread has an error handler? */ L->errorJmp->status = errcode; /* set status */ LUAI_THROW(L, L->errorJmp); /* jump to it */ } else { /* thread has no error handler */ global_State *g = G(L); L->status = cast_byte(errcode); /* mark it as dead */ if (g->mainthread->errorJmp) { /* main thread has a handler? */ setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ } else { /* no handler at all; abort */ if (g->panic) { /* panic function? */ seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ if (L->ci->top < L->top) L->ci->top = L->top; /* pushing msg. can break this invariant */ lua_unlock(L); g->panic(L); /* call panic function (last chance to jump out) */ } abort(); } } }


兩種情況會觸發這個函數
1、主動調用error時:
// lapi.c LUA_API int lua_error (lua_State *L) { lua_lock(L); api_checknelems(L, 1); luaG_errormsg(L); /* code unreachable; will unlock when control actually leaves the kernel */ return 0; /* to avoid warnings */ }
2、出現運行時毛病時:
// ldebug.c l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { CallInfo *ci = L->ci; const char *msg; va_list argp; va_start(argp, fmt); msg = luaO_pushvfstring(L, fmt, argp); /* format message */ va_end(argp); if (isLua(ci)) /* if Lua function, add source:line information */ luaG_addinfo(L, msg, ci_func(ci)->p->source, currentline(ci)); luaG_errormsg(L); }


拓展瀏覽

lua state之nny 


在lua state中,nny記錄了調用棧上不能被掛起的次數,定義在 lua_State結構:
struct lua_State { CommonHeader; 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) */ 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 */ lu_byte hookmask; lu_byte allowhook; };
為何lua state需要這個字段?
這是由于pcall調用函數中還可以有pcall,支持多層嵌套,所以需要記錄1個次數值,在pcall履行前+1,結束后⑴
/* ** Call a function (C or Lua). The function to be called is at *func. ** The arguments are on the stack, right after the function. ** When returns, all the results are on the stack, starting at the original ** function position. */ void luaD_call (lua_State *L, StkId func, int nResults, int allowyield) { fprintf(stderr, "luaD_call %d ", allowyield); if (++L->nCcalls >= LUAI_MAXCCALLS) { if (L->nCcalls == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } if (!allowyield) L->nny++; if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ luaV_execute(L); /* call it */ if (!allowyield) L->nny--; L->nCcalls--; }
這樣,通過判斷 nny的值,就能夠知道當前進程能否掛起。
仔細的同學會發現,luaD_call是在pcall的履行進程調用到的,為何 L->nny會大于0?
這是由于lua 虛擬機(lua state)啟動時 L->nny 就賦值1了
// lstate.c LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; L = &l->l.l; g = &l->g; L->next = NULL; L->tt = LUA_TTHREAD; g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); preinit_thread(L, g); // 預處理協程時將 L->nny 賦值1 g->frealloc = f; g->ud = ud; g->mainthread = L; g->seed = makeseed(L); g->gcrunning = 0; /* no GC while building state */ g->GCestimate = 0; g->strt.size = g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(&g->l_registry); luaZ_initbuffer(L, &g->buff); g->panic = NULL; g->version = NULL; g->gcstate = GCSpause; g->gckind = KGC_NORMAL; g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL; g->sweepgc = NULL; g->gray = g->grayagain = NULL; g->weak = g->ephemeron = g->allweak = NULL; g->twups = NULL; g->totalbytes = sizeof(LG); g->GCdebt = 0; g->gcfinnum = 0; g->gcpause = LUAI_GCPAUSE; g->gcstepmul = LUAI_GCMUL; for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { /* memory allocation error: free partial state */ close_state(L); L = NULL; } return L; }
看下preinit_thread 函數:
// lstate.c /* ** preinitialize a thread with consistent values without allocating ** any memory (to avoid errors) */ static void preinit_thread (lua_State *L, global_State *g) { G(L) = g; L->stack = NULL; L->ci = NULL; L->stacksize = 0; L->twups = L; /* thread has no upvalues */ L->errorJmp = NULL; L->nCcalls = 0; L->hook = NULL; L->hookmask = 0; L->basehookcount = 0; L->allowhook = 1; resethookcount(L); L->openupval = NULL; L->nny = 1; //將 L->nny 賦值1 L->status = LUA_OK; L->errfunc = 0; }
那協程 lua_newthread時也會調用 preinit_thread ,但是二者對pcall的實現卻有很多的差異。
以下,協程創建時調用 preinit_thread 預處理協程:
// lstate.c LUA_API lua_State *lua_newthread (lua_State *L) { global_State *g = G(L); lua_State *L1; lua_lock(L); luaC_checkGC(L); /* create new thread */ L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l; L1->marked = luaC_white(g); L1->tt = LUA_TTHREAD; /* link it on list allgc */ L1->next = g->allgc; g->allgc = obj2gco(L1); /* anchor it on L stack */ setthvalue(L, L->top, L1); api_incr_top(L); preinit_thread(L1, g); // 預處理協程時將 L->nny 賦值1 L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; resethookcount(L1); /* initialize L1 extra space */ memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), LUA_EXTRASPACE); luai_userstatethread(L, L1); stack_init(L1, L); /* init stack */ lua_unlock(L); return L1; }
但是,協程在lua中是這樣調用的:
local co = coroutine.create(func) coroutine.resume(co)
再看下 resume的實現:
LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { int status; int oldnny = L->nny; /* save "number of non-yieldable" calls */ lua_lock(L); luai_userstateresume(L, nargs); L->nCcalls = (from) ? from->nCcalls + 1 : 1; L->nny = 0; // 將 nny賦值 0,允許掛起 api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); status = luaD_rawrunprotected(L, resume, &nargs); // 在保護模式啟動協程 if (status == ⑴) /* error calling lua_resume? */ status = LUA_ERRRUN; else { /* continue running after recoverable errors */ while (errorstatus(status) && recover(L, status)) { // 檢查是不是有可恢復點 /* unroll continuation */ status = luaD_rawrunprotected(L, unroll, &status); } if (errorstatus(status)) { /* unrecoverable error? */ L->status = cast_byte(status); /* mark thread as dead */ seterrorobj(L, status, L->top); /* push error message */ L->ci->top = L->top; } else lua_assert(status == L->status); /* normal end or yield */ } L->nny = oldnny; // 恢復協程的nny值 L->nCcalls--; lua_assert(L->nCcalls == ((from) ? from->nCcalls : 0)); lua_unlock(L); return status; }
可以看出協程的pcall處理有些不同,為何會這樣?
這是由于協程可掛起(yield),可恢復上下文(resume),就會有pcall履行進程有協程掛起的情況,所以需要記錄還原點,然后從上下文恢復。
所以到這里也能夠發現,除創建的協程,lua state原生的協程是沒法被掛起,通常只有在lua代碼履行完時才會退出。

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 色亚洲成人 | 免费上床视频 | 久久久久久久久久久观看 | 99heicom视频| 亚洲视频你懂的 | 国产zzzwww在线观看 | 成人免费体验区福利云点播 | 国内精品伊人久久大香线焦 | 亚洲欧美日韩综合久久久久 | 欧美性黑人极品 hd 欧美性黑人极品hd 欧美性活生活视频 | 亚洲国产精久久久久久久 | 另类ts人妖一区二区三区 | 美国免费高清一级毛片 | 中文字幕在线视频在线看 | 性欧美video 性欧美videofreel另类 | 欧美18一14sex性处hd | 日本成人性视频 | 亚洲欧美日韩在线观看播放 | 精品国产一区二区三区在线观看 | 亚洲第一视频区 | 免费网站看v片在线观看 | 一级特黄aa大片免费播放视频 | 网友自拍视频悠悠在线 | 亚洲在线免费免费观看视频 | 国产在线精品一区二区高清不卡 | 久久久久亚洲精品一区二区三区 | 老司机一二三区福利视频 | 日本特黄色大片 | 最近2019中文字幕免费大全8 | 精品久久成人 | a级片在线免费看 | 九九精品免费观看在线 | 日本不卡在线视频 | 亚洲成a人片 | 小说区 综合区 都市激情 | 日韩精品观看 | 欧美亚洲国产色综合 | 一级毛片在线免费视频 | 免费澳门一级毛片 | 日本免费xxxx色视频 | 亚洲精品天堂在线 |