openresty源碼剖析——lua代碼的執行

上一篇文章中(http://www.cnblogs.com/magicsoar/p/6774872.html)咱們討論了openresty是如何加載lua代碼的html

那麼加載完成以後的lua代碼又是如何執行的呢nginx

 

##代碼的執行 socket

在init_by_lua等階段  openresty是在主協程中經過lua_pcall直接執行lua代碼函數

而在access_by_lua  content_by_lua等階段中,openresty建立一個新的協程,經過lua_resume執行lua代碼post

兩者的區別在於可否執行ngx.slepp. ngx.thread ngx.socket 這些有讓出操做的函數lua

咱們依舊以content_by_**階段爲例進行講解spa

 

#content_by_**階段線程

content_by_**階段對應的請求來臨時,執行流程爲 ngx_http_lua_content_handler -> ngx_http_lua_content_handler_file-> ngx_http_lua_content_by_chunkrest

ngx_http_lua_content_handler 和 ngx_http_lua_content_handler_file 完成了請求上下文初始化,代碼加載等操做code

ngx_http_lua_content_by_chunk進行代碼的執行工做

 
#ngx_http_lua_content_by_chunk
24 ngx_int_t
25 ngx_http_lua_content_by_chunk(lua_State *L, ngx_http_request_t *r)
26 {
27 ...
50     ctx->entered_content_phase = 1;//標示當前進入了content_phase
51
52     /*  {{{ new coroutine to handle request */
53     co = ngx_http_lua_new_thread(r, L, &co_ref);//建立了一個新的lua協程
54
61 ...
62     /*  move code closure to new coroutine */
63     lua_xmove(L, co, 1);//主協程的棧頂是須要執行的lua函數,經過lua_xmove將棧頂函數交換到新lua協程中
64
65     /*  set closure's env table to new coroutine's globals table */
66     ngx_http_lua_get_globals_table(co);
67     lua_setfenv(co, -2);
68
69     /*  save nginx request in coroutine globals table */
70     ngx_http_lua_set_req(co, r);//把當前請求r賦值給新協程的全局變量中
71 ...
103     rc = ngx_http_lua_run_thread(L, r, ctx, 0);//運行新協程
104 ...
109     if (rc == NGX_AGAIN) {
110         return ngx_http_lua_content_run_posted_threads(L, r, ctx, 0);//執行須要延後執行的協程,0表示上面傳來的狀態是NGX_AGAIN
111     }
112
113     if (rc == NGX_DONE) {
114         return ngx_http_lua_content_run_posted_threads(L, r, ctx, 1);//執行須要延後執行的協程,1表示上面傳來的狀態是NGX_DONE
115     }
116
117     return NGX_OK;
118 }

27-50行,有一步是從新設置請求的上下文,將用於標示當前進入了那個階段的變量重置爲0

855     ctx->entered_rewrite_phase = 0;
856     ctx->entered_access_phase = 0;
857     ctx->entered_content_phase = 0;

這幾個字段的用處在ngx_http_lua_content_handler函數中用於確認以前是否進入過對應階段

135 ngx_int_t
136 ngx_http_lua_content_handler(ngx_http_request_t *r)
137 {
138 ...
170     if (ctx->entered_content_phase) {
171         dd("calling wev handler");
172         rc = ctx->resume_handler(r);
173         dd("wev handler returns %d", (int) rc);
174         return rc;
175     }
176 ...
206 }
 

53行,建立了一個新的lua協程

63行,加載代碼的時候,咱們把須要執行的lua函數放到了主協程的棧頂,因此這裏咱們須要經過lua_xmove將函數移到新協程中

70行,把當前請求r賦值給新協程的全局變量中,從而可讓lua執行獲取和請求相關的一些函數,好比ngx.req.get_method()和ngx.set_method,ngx.req.stat_time()等

103行,運行新建立的lua協程

109-114行,ngx.thread.spawn中建立子協程後,會調用ngx_http_lua_post_thread。ngx_http_lua_post_thread函數將父協程放在了ctx->posted_threads指向的鏈表中,這裏的ngx_http_lua_content_run_posted_threads運行延後執行的主協程

 

#ngx_http_lua_new_thread建立協程

303 lua_State *
304 ngx_http_lua_new_thread(ngx_http_request_t *r, lua_State *L, int *ref)
305 {
306 ...
312     base = lua_gettop(L);
313
314     lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key);//獲取全局變量中儲存協程的table
315     lua_rawget(L, LUA_REGISTRYINDEX);
316
317     co = lua_newthread(L);//建立新協程
319 ...
334     *ref = luaL_ref(L, -2);//將建立的新協程保存對應的全局變量中
335
336     if (*ref == LUA_NOREF) {
337         lua_settop(L, base);  /* restore main thread stack */
338         return NULL;
339     }
340
341     lua_settop(L, base);//恢復主協程的棧空間大小
342     return co;
343 }

312行,得到了主協程棧中有多少元素

314-315行,得到全局變量中儲存協程的table  LUA_REGISTRYINDEX[‘ngx_http_lua_code_coroutines_key’]

由於lua中協程也是GC的對象,會被lua系統進行垃圾回收,爲了保證掛起的協程不會被GC掉,openresty使用了 LUA_REGISTRYINDEX[‘ngx_http_lua_code_coroutines_key’]來保存新建立的協程,在協程執行完畢後將協程從table

中刪除,使的GC能夠將這個協程垃圾回收掉

317行,建立了一個lua_newthread並把其壓入主協程的棧頂

334行,將新建立的協程保存到LUA_REGISTRYINDEX[‘ngx_http_lua_code_coroutines_key’]

341行,恢復主協程的棧空間大小

343行,返回新建立的協程

 

#ngx_http_lua_run_thread運行協程

ngx_http_lua_run_thread函數的代碼行數比較多,有500多行,內容以下:

951 ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r,
952     ngx_http_lua_ctx_t *ctx, volatile int nrets)
953 {
954 ...
973     NGX_LUA_EXCEPTION_TRY {
974 ...
982         for ( ;; ) {
983 ...
997             orig_coctx = ctx->cur_co_ctx;
998 ...
1015             rv = lua_resume(orig_coctx->co, nrets);//經過lua_resume執行協程中的函數
1016 ...
1032             switch (rv) {//處理lua_resume的返回值
1033             case LUA_YIELD:
1034 ..
1047                 if (r->uri_changed) {
1048                     return ngx_http_lua_handle_rewrite_jump(L, r, ctx);
1049                 }
1050                 if (ctx->exited) {
1051                     return ngx_http_lua_handle_exit(L, r, ctx);
1052                 }
1053                 if (ctx->exec_uri.len) {
1054                     return ngx_http_lua_handle_exec(L, r, ctx);
1055                 }
1056                 switch(ctx->co_op) {
1057 ...
1167                 }
1168                 continue;
1169             case 0:
1170 ...
1295                 continue;
1296 ...
1313             default:
1314                 err = "unknown error";
1315                 break;
1316             }
1317 ...
1444         }
1445     } NGX_LUA_EXCEPTION_CATCH {
1446         dd("nginx execution restored");
1447     }
1448     return NGX_ERROR;
1449
1450 no_parent:
1451 ...
1465     return (r->header_sent || ctx->header_sent) ?
1466                 NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
1467
1468 done:
1469 ...
1481     return NGX_OK;
1482 }
 

1015行,經過lua_resume執行協程的函數,並根據返回的結果進行不一樣的處理

LUA_YIELD: 協程被掛起

0: 協程執行結束

其餘: 運行出錯,如內存不足等

 

1032             switch (rv) {
1033             case LUA_YIELD:
1034 ...
1047                 if (r->uri_changed) {
1048                     return ngx_http_lua_handle_rewrite_jump(L, r, ctx);//調用了ngx.redirect
1049                 }
1050
1051                 if (ctx->exited) {
1052                     return ngx_http_lua_handle_exit(L, r, ctx);//調用了ngx.exit
1053                 }
1054
1055                 if (ctx->exec_uri.len) {
1056                     return ngx_http_lua_handle_exec(L, r, ctx);//調用了ngx.exec
1057                 }  
 

lua_resume返回LUA_YIELD,表示被掛起

先處理如下3種狀況:

r->uri_changed爲true代表調用了ngx.redirect

ext->exited爲true代表調用了ngx.exit

ctx->exec_uri.len爲true代表調用了ngx.exec

其他狀況須要再比較ctx->co_op的返回值

1063                 switch(ctx->co_op) {
1064                 case NGX_HTTP_LUA_USER_CORO_NOP:
1065 ...
1069                     ctx->cur_co_ctx = NULL;
1070                     return NGX_AGAIN;
1071                 case NGX_HTTP_LUA_USER_THREAD_RESUME://ngx.thread.spawn
1072 ...
1075                     ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP;
1076                     nrets = lua_gettop(ctx->cur_co_ctx->co) - 1;
1077                     dd("nrets = %d", nrets);
1078 ...
1084                     break;
1085                 case NGX_HTTP_LUA_USER_CORO_RESUME://coroutine.resume
1086 ...
1093                     ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP;
1094                     old_co = ctx->cur_co_ctx->parent_co_ctx->co;
1095                     nrets = lua_gettop(old_co);
1096                     if (nrets) {
1097                         dd("moving %d return values to parent", nrets);
1098                         lua_xmove(old_co, ctx->cur_co_ctx->co, nrets);
1099 ...
1103                     }
1104                     break;
1105                 default://coroutine.yield
1106 ...

 

在openresty內部從新實現的coroutine.yield  和coroutine.resume 和 ngx.thread.spawn中 會對ctx->co_op進行賦值

1064行,case NGX_HTTP_LUA_USER_CORO_NOP表示再也不有協程須要處理了,跳出這一次循環,等待下一次的讀寫時間,或者定時器到期

1071行,case NGX_HTTP_USER_THREAD_RESUME 對應 ngx.thread.spawn被調用的狀況

1085行,case NGX_HTTP_LUA_CORO_RESUME 對應有lua代碼調用coroutine.resume,把當前線程標記爲NGX_HTTP_LUA_USER_CORO_NOP

1106行,default 對應NGX_HTTP_LUA_CODO_YIELD,對應coroutine.yield被調用的狀況

1113                 default:
1114 ...
1119                     ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP;
1120
1121                     if (ngx_http_lua_is_thread(ctx)) {
1122 ...
1132                         ngx_http_lua_probe_info("set co running");
1133                         ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_RUNNING;
1134
1135                         if (ctx->posted_threads) {
1136                             ngx_http_lua_post_thread(r, ctx, ctx->cur_co_ctx);
1137                             ctx->cur_co_ctx = NULL;
1138                             return NGX_AGAIN;
1139                         }
1140 ...
1144                         nrets = 0;
1145                         continue;
1146                     }
1147 ...
1150                     nrets = lua_gettop(ctx->cur_co_ctx->co);
1151                     next_coctx = ctx->cur_co_ctx->parent_co_ctx;
1152                     next_co = next_coctx->co;
1153 ...
1158                     lua_pushboolean(next_co, 1);
1159
1160                     if (nrets) {
1161                         dd("moving %d return values to next co", nrets);
1162                         lua_xmove(ctx->cur_co_ctx->co, next_co, nrets);
1163                     }
1164                     nrets++;  /* add the true boolean value */
1165                     ctx->cur_co_ctx = next_coctx;
1166                     break;
1167                 } 

  

default 對應NGX_HTTP_LUA_CODO_YIELD,表示coroutine.yield被調用的狀況

1121行,判斷是否是主協程,或者是調用ngx.thread.spawn的協程

1135行,判斷鏈表中有沒有排隊須要執行的協程,若是有的話,調用ngx_http_lua_post_thread將這個協程放到他們的後面,沒有的話,直接讓他本身恢復執行便可,回到 for 循環開頭

1136-1167行,ngx.thread.spawn建立的子協程,須要將返回值放入父協程中

1150-1152行和1165行,將當前須要執行的協程,由子協程切換爲父協程

1159行,放入布爾值true

1161行,將子協程的全部返回值經過lua_xmove放入父協程中

1170行,因爲多了一個布爾值true返回值個數+1

1166行,回到for循環開頭,在父協程上執行lua_resume

 

lua_resume返回0,表示當前協程執行完畢

這裏由於有ngx.thread API的存在,可能有多個協程在跑,須要判斷父協程和全部的子協程的運行狀況。

1172             case 0:
1173 ...
1183                 if (ngx_http_lua_is_entry_thread(ctx)) {
1184 ...
1187                     ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx);
1188                     if (ctx->uthreads) {
1189                         ctx->cur_co_ctx = NULL;
1190                         return NGX_AGAIN;
1191                     }
1192                     /* all user threads terminated already */
1193                     goto done;
1194                 }
1195                 if (ctx->cur_co_ctx->is_uthread) {
1196 ...
1223                     ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx);
1224                     ctx->uthreads--;
1225                     if (ctx->uthreads == 0) {
1226                         if (ngx_http_lua_entry_thread_alive(ctx)) {
1227                             ctx->cur_co_ctx = NULL;
1228                             return NGX_AGAIN;
1229                         }
1230                         goto done;
1231                     }
1232                     /* some other user threads still running */
1233                     ctx->cur_co_ctx = NULL;
1234                     return NGX_AGAIN;
1235                 }

 

1183行,判斷是否是主協程

1187行,執行完畢的協程是主協程,從全局table中刪除這個協程

1188-1193行,判斷還在運行的子協程個數,若是非0 返回NGX_AGAIN,不然goto done 進行一些數據發送的相關工做並返回NGX_OK

1195-1233,判斷執行完畢的是否是子協程

1223行,因爲協程已經執行完畢,從全局table中刪除這個協程,能夠被lua  GC掉

1223行,還在運行的子協程個數-1

1226行,判斷主協程是否還須要運行,是的話,返回NGX_AGAIN,不然goto done,進行一些數據發送的相關工做並返回NGX_OK

1232-1234行,表示有子協程還在運行,返回NGX_AGAIN

 

 ##總結

一、在init_by_lua等階段 ,openresty是在主協程中經過lua_pcall直接執行lua代碼,而在access_by_lua、content_by_lua等階段中,openresty建立一個新的協程,經過lua_resume執行lua代碼

二、openresty將要延後執行的協程放入鏈表中,在*_run_posted_threads函數中經過調用ngx_http_lua_run_thread進行執行

相關文章
相關標籤/搜索