ngx_lua的協程調度(五)之ngx_http_lua_run_thread

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

lua_resume函數

int lua_resume (lua_State *L, int narg);

這是lua_resume的聲明,按照返回值可分爲幾種狀況異步

  • LUA_YIELD: 協程yield
  • 0: 協程執行結束
  • 其餘: 運行出錯,如內存不足等

協程yield的處理

致使協程yield的API主要分如下幾種socket

ngx.exit, ngx.exec和ngx.redirect

這裏仍是能夠拿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);
                }
  • r->uri_changed爲true代表調用了ngx.redirect
  • ctx->exited爲true代表調用了ngx.exit
  • ctx->exec_uri.len爲true代表調用了ngx.exec

這三種狀況都不須要協程繼續運行了,退出執行相應的處理lua

ngx.socket和ngx.sleep

前面介紹了這兩個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

以前提到過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

  • 將ctx->co_op設置爲NGX_HTTP_LUA_USER_THREAD_RESUME
  • ngx_http_lua_post_thread將父協程放到待運行的隊列中
  • 將ctx->cur_co_ctx設置爲coctx,ctx->cur_co_ctx表明以後須要執行的協程(這裏的light thread也是協程)

在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"。

coroutine

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

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;
                }

ngx_http_lua_run_thread的處理流程圖

此圖只爲顯示主要流程,省略了不少次要的細節處理。

輸入圖片說明

相關文章
相關標籤/搜索