lua代码的执行,lua代码执行


##代码的执行 

在init_by_lua等阶段  openresty是在主协程中通过lua_pcall直接执行lua代码

而在access_by_lua  content_by_lua等阶段中  openresty创建一个新的协程,通过lua_resume执行lua代码

二者的区别在于能否执行ngx.slepp. ngx.thread ngx.socket 这些有让出操作的函数

我们依旧以content_by_**阶段为例进行讲解

 

#content_by_**阶段

content_by_**阶段 对应的请求来临时 执行流程为 ngx_http_lua_content_handler -> ngx_http_lua_content_handler_file-> ngx_http_lua_content_by_chunk

ngx_http_lua_content_handler 和 ngx_http_lua_content_handler_file 完成了请求上下文初始化,代码加载等操作

ngx_http_lua_content_by_chunk进行代码的执行工作

  #ngx_http_lua_content_by_chunk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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

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

这几个字段的用处在ngx_http_lua_content_handler确认之前是进入过对应阶段

1 2 3 4 5 6 7 8 9 10 11 12 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 }
1   

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创建协程

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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多行,内容如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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 }
1   

1015行,通过lua_resume执行协程的函数,并根据返回的结果进行不同的处理

LUA_YIELD: 协程被挂起

0: 协程执行结束

其他: 运行出错,如内存不足等

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 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                 }  
1   

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的返回值

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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被调用的情况

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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的存在,可能有多个协程在跑,需要判断父协程和所有的子协程的运行情况。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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中删除这个协程

1223行,还在运行的子协程个数-1

1226行,判断主协程是否还需要运行,是的话 返回NGX_AGAIN,否则goto done,进行一些数据相应工作并返回NGX_OK

1232-1234行,表示有子协程还在运行,返回NGX_AGAIN

 

 ##总结

1、在init_by_lua等阶段  openresty是在主协程中通过lua_pcall直接执行lua代码,而在access_by_lua  content_by_lua等阶段中  openresty创建一个新的协程,通过lua_resume执行lua代码

2、openresty将要延后执行的协程放入链表中,在*_run_posted_threads函数中通过调用ngx_http_lua_run_thread进行执行

相关内容

    暂无相关文章