lua-nginx-module中,在Log,Header_filter,Body_filter幾個階段直接調用lua_pcall執行Lua腳本,而在Access,Rewrite,Content等階段Lua腳本的執行是在ngx_http_lua_run_thread函數中調用lua_resume實現的。再根據lua_resume的返回值進行處理。nginx
int lua_resume (lua_State *L, int narg);
這是lua_resume的聲明,按照返回值可分爲幾種狀況異步
致使協程yield的API主要分如下幾種socket
這裏仍是能夠拿C語言來比較下,C語言中return從當前的函數返回,而exit直接返回到頂層,致使進程的結束。在Lua中return也是從函數返回,可是Lua沒有提共exit相似的方法,若是函數調用層次太深,須要結束協程的運行就變得很困難,lua-nginx-module中是經過協程的yield來實現相似的功能。lua_resume返回1時判斷協程是否能夠直接結束。函數
ngx.exit,ngx.exec和ngx.redirect都採用了這種方法。post
if (r->uri_changed) { return ngx_http_lua_handle_rewrite_jump(L, r, ctx); } if (ctx->exited) { return ngx_http_lua_handle_exit(L, r, ctx); } if (ctx->exec_uri.len) { return ngx_http_lua_handle_exec(L, r, ctx); }
這三種狀況都不須要協程繼續運行了,退出執行相應的處理lua
前面介紹了這兩個API須要異步的執行,致使協程yield,這時ngx_http_lua_run_thread返回NGX_AGAIN,等到socket或定時器事件觸發後繼續運行。 ctx->co_op表明協程yield的緣由,其值爲NGX_HTTP_LUA_USER_CORO_NOP時代表是因爲ngx.socket或者ngx.sleep致使的。spa
switch(ctx->co_op) { case NGX_HTTP_LUA_USER_CORO_NOP: dd("hit! it is the API yield"); ngx_http_lua_assert(lua_gettop(ctx->cur_co_ctx->co) == 0); ctx->cur_co_ctx = NULL; return NGX_AGAIN;
以前提到過ngx.thread.spawn生成新的"light thread"時的執行順序,新的"light thread"會先執行。這個邏輯如何實現呢?在ngx.thread.spawn中生成新的"light thread"後,執行了下面的操做debug
coctx->co_status = NGX_HTTP_LUA_CO_RUNNING; ctx->co_op = NGX_HTTP_LUA_USER_THREAD_RESUME; ctx->cur_co_ctx->thread_spawn_yielded = 1; if (ngx_http_lua_post_thread(r, ctx, ctx->cur_co_ctx) != NGX_OK) { return luaL_error(L, "no memory"); } coctx->parent_co_ctx = ctx->cur_co_ctx; ctx->cur_co_ctx = coctx; ngx_http_lua_probe_user_thread_spawn(r, L, coctx->co); dd("yielding with arg %s, top=%d, index-1:%s", luaL_typename(L, -1), (int) lua_gettop(L), luaL_typename(L, 1)); return lua_yield(L, 1);
上面的代碼中coctx表明生成的"light thread",ctx->co_o表示協程yield的緣由。主要關注如下幾點code
在lua_resume返回LUA_YIELD後判斷ctx->co_op協程
case NGX_HTTP_LUA_USER_THREAD_RESUME: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "lua user thread resume"); ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP; nrets = lua_gettop(ctx->cur_co_ctx->co) - 1; dd("nrets = %d", nrets); #ifdef NGX_LUA_USE_ASSERT /* ignore the return value (the thread) already pushed */ orig_coctx->co_top--; #endif break;
注意這裏最後是break,而不是return。當時是由於ngx.thread.spawn致使父協程yield,下一個循環中會執行新生成的"light thread"。
lua-nginx-module中的coroutine API和原生Lua中相似,和ngx.thread不一樣,coroutine.create建立的協程須要手動去運行,因此resume和yield都須要在ngx_http_lua_run_thread中進行協程的切換。
case NGX_HTTP_LUA_USER_CORO_RESUME: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "lua coroutine: resume"); /* * the target coroutine lies at the base of the * parent's stack */ ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP; old_co = ctx->cur_co_ctx->parent_co_ctx->co; nrets = lua_gettop(old_co); if (nrets) { dd("moving %d return values to parent", nrets); lua_xmove(old_co, ctx->cur_co_ctx->co, nrets); #ifdef NGX_LUA_USE_ASSERT ctx->cur_co_ctx->parent_co_ctx->co_top -= nrets; #endif } break; default: /* ctx->co_op == NGX_HTTP_LUA_USER_CORO_YIELD */ /* 此處省略 */
lua_resume返回值爲0代表協程執行完畢,通常狀況下執行完畢就真的結束了。而這裏由於有ngx.thread API的存在,可能有多個"light thread"在跑,須要等到父協程和全部的"light thread"所有結束才能真正返回,進入Nginx的下一個階段。
若是父進程執行結束了,會判斷ctx->uthreads的值,ctx->uthreads表明還在運行的"light thread"的個數。若是不爲零返回NGX_AGAIN,若是爲0返回NGX_OK後結束當前階段的處理。
if (ngx_http_lua_is_entry_thread(ctx)) { lua_settop(L, 0); ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx); dd("uthreads: %d", (int) ctx->uthreads); if (ctx->uthreads) { ctx->cur_co_ctx = NULL; return NGX_AGAIN; } /* all user threads terminated already */ goto done; }
若是是"light thread"結束了,一樣判斷父協程和其餘的"light thread"是否所有結束了,若是是返回NGX_OK結束,不然放回NGX_AGAIN。
if (ctx->cur_co_ctx->is_uthread) { /* being a user thread */ /* 此處省略 */ ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx); ctx->uthreads--; if (ctx->uthreads == 0) { if (ngx_http_lua_entry_thread_alive(ctx)) { ctx->cur_co_ctx = NULL; return NGX_AGAIN; } /* all threads terminated already */ goto done; } /* some other user threads still running */ ctx->cur_co_ctx = NULL; return NGX_AGAIN; }
此圖只爲顯示主要流程,省略了不少次要的細節處理。