上一篇文章中(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进行执行

openresty源码剖析——lua代码的执行的更多相关文章

  1. openresty源码剖析——lua代码的加载

    ##Openresty是什么 OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,通过把lua嵌入到Nginx中,使得我们可以用轻巧的lua语言进行nginx的相关开发,处理 ...

  2. ArrayList源码剖析与代码实测

    ArrayList源码剖析与代码实测(基于OpenJdk14) 目录 ArrayList源码剖析与代码实测(基于OpenJdk14) 继承关系 从构造函数开始 从add方法深入 / 数组的扩容 其他的 ...

  3. Spark源码剖析 - 任务提交与执行

    1. 任务概述 任务提交与执行过程: 1) build operator DAG:此阶段主要完成RDD的转换及DAG的构建: 2) split graph into stages of tasks:此 ...

  4. gin源码剖析

    介绍 Gin 是一个 Golang 写的 web 框架,具有高性能的优点,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务.官方地址:https:// ...

  5. 06 drf源码剖析之权限

    06 drf源码剖析之权限 目录 06 drf源码剖析之权限 1. 权限简述 2. 权限使用 3.源码剖析 4. 总结 1. 权限简述 权限与身份验证和限制一起,决定了是否应授予请求访问权限. 权限检 ...

  6. 05 drf源码剖析之认证

    05 drf源码剖析之认证 目录 05 drf源码剖析之认证 1. 认证简述 2. 认证的使用 3. 源码剖析 4. 总结 1. 认证简述 当我们通过Web浏览器与API进行交互时,我们可以登录,然后 ...

  7. 07 drf源码剖析之节流

    07 drf源码剖析之节流 目录 07 drf源码剖析之节流 1. 节流简述 2. 节流使用 3. 源码剖析 总结: 1. 节流简述 节流类似于权限,它确定是否应授权请求.节流指示临时状态,并用于控制 ...

  8. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  9. Spark源码剖析 - SparkContext的初始化(二)_创建执行环境SparkEnv

    2. 创建执行环境SparkEnv SparkEnv是Spark的执行环境对象,其中包括众多与Executor执行相关的对象.由于在local模式下Driver会创建Executor,local-cl ...

随机推荐

  1. iOS开发之UIDevice通知

    UIDevice类提供了一个单例对象,它代表着设备,通过它可以获得一些设备相关的信息,比如电池电量值(batteryLevel).电池状态(batteryState).设备的类型(model,比如iP ...

  2. 使用MyBatis对数据库中表实现CRUD操作(二)

    一.使用MyBatis对表实现CRUD操作 1.定义sql映射 userMapper.xml <?xml version="1.0" encoding="UTF-8 ...

  3. 手动es6编译es5(命令行)

    package.json:"devDependencies": { "babel-cli": "^6.18.0", "babel- ...

  4. React-native 初始化项目很慢

    我是在Mac环境下,利用facebook开源的react-native创建原生app项目缓慢的问题 一:确定自己的环境配置是否有问题 二:打开终端,输入命令行 brew install wget 点击 ...

  5. cat: can't open '/lib/modules/2.6.35.3-571-gcca29a0/modules.dep': No such file or directory

    在使用modprobe 或者modinfo  cat: can't open '/lib/modules/2.6.35.3-571-gcca29a0/modules.dep': No such fil ...

  6. Excel图表-创意雷达图-原创图表

    p{ font-size: 15px; } .alexrootdiv>div{ background: #eeeeee; border: 1px solid #aaa; width: 99%; ...

  7. 测试开发Python培训:模拟登录新浪微博-技术篇

    测试开发Python培训:模拟登录新浪微博-技术篇   一般一个初学者项目的起点就是登陆功能的自动化,而面临的项目不同实现的技术难度是不一样的,poptest在做测试开发培训中更加关注技术难点,掌握技 ...

  8. HBase应用快速学习

    HBase是一个高性能.面向列.可伸缩的开源分布式NoSQL数据库,是Google Bigtable的开源实现. HBase的思想和应用和传统的RDBMS,NoSQL等有比较大的区别,这篇文章从HBa ...

  9. Maven工程webinfo下面的JSP页面无法加载.js、.css文件的解决方案

    --下面是我的工程路径 --我jsp的写法 -----启动工程,访问js文件的路径是这样的, href="http://localhost:8080/activiti/css/public. ...

  10. 用MPLAB IDE编程时,软件总是弹出一个窗口提示: “the extended cpu mode configuration bit is enabled,but the program that was loaded was not built using extended cpu instructions. therefore,your code may not work properly

    用MPLAB IDE编程时,软件总是弹出一个窗口提示:"the extended cpu mode configuration bit is enabled,but the program ...