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; }
此图只为显示主要流程,省略了不少次要的细节处理。