• openresty源码剖析——lua代码的执行


    上一篇文章中(http://www.cnblogs.com/magicsoar/p/6774872.html)我们讨论了openresty是如何加载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
    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

     ##总结

    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进行执行

  • 相关阅读:
    免费录屏工具
    树莓派4安装mysql
    python中,print、变量、赋值详细理解篇01
    mysql db [Warning] IP address 'xxxx' could not be resolved: Name or service not known
    添加脚本命令
    LRUCache实现方案
    HTTP/2做错了什么?刚刚辉煌2年就要被弃用了!?
    TS 中的 export declare interface 和 export interface 到底有什么不同?
    使用nexus搭建npm私服
    nodejs 安装
  • 原文地址:https://www.cnblogs.com/magicsoar/p/6832204.html
Copyright © 2020-2023  润新知