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进行代码的执行工作
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代码的执行的更多相关文章
- openresty源码剖析——lua代码的加载
##Openresty是什么 OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,通过把lua嵌入到Nginx中,使得我们可以用轻巧的lua语言进行nginx的相关开发,处理 ...
- ArrayList源码剖析与代码实测
ArrayList源码剖析与代码实测(基于OpenJdk14) 目录 ArrayList源码剖析与代码实测(基于OpenJdk14) 继承关系 从构造函数开始 从add方法深入 / 数组的扩容 其他的 ...
- Spark源码剖析 - 任务提交与执行
1. 任务概述 任务提交与执行过程: 1) build operator DAG:此阶段主要完成RDD的转换及DAG的构建: 2) split graph into stages of tasks:此 ...
- gin源码剖析
介绍 Gin 是一个 Golang 写的 web 框架,具有高性能的优点,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务.官方地址:https:// ...
- 06 drf源码剖析之权限
06 drf源码剖析之权限 目录 06 drf源码剖析之权限 1. 权限简述 2. 权限使用 3.源码剖析 4. 总结 1. 权限简述 权限与身份验证和限制一起,决定了是否应授予请求访问权限. 权限检 ...
- 05 drf源码剖析之认证
05 drf源码剖析之认证 目录 05 drf源码剖析之认证 1. 认证简述 2. 认证的使用 3. 源码剖析 4. 总结 1. 认证简述 当我们通过Web浏览器与API进行交互时,我们可以登录,然后 ...
- 07 drf源码剖析之节流
07 drf源码剖析之节流 目录 07 drf源码剖析之节流 1. 节流简述 2. 节流使用 3. 源码剖析 总结: 1. 节流简述 节流类似于权限,它确定是否应授权请求.节流指示临时状态,并用于控制 ...
- 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles
老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles poptest是国内唯一一家培养测试开 ...
- Spark源码剖析 - SparkContext的初始化(二)_创建执行环境SparkEnv
2. 创建执行环境SparkEnv SparkEnv是Spark的执行环境对象,其中包括众多与Executor执行相关的对象.由于在local模式下Driver会创建Executor,local-cl ...
随机推荐
- iOS开发之UIDevice通知
UIDevice类提供了一个单例对象,它代表着设备,通过它可以获得一些设备相关的信息,比如电池电量值(batteryLevel).电池状态(batteryState).设备的类型(model,比如iP ...
- 使用MyBatis对数据库中表实现CRUD操作(二)
一.使用MyBatis对表实现CRUD操作 1.定义sql映射 userMapper.xml <?xml version="1.0" encoding="UTF-8 ...
- 手动es6编译es5(命令行)
package.json:"devDependencies": { "babel-cli": "^6.18.0", "babel- ...
- React-native 初始化项目很慢
我是在Mac环境下,利用facebook开源的react-native创建原生app项目缓慢的问题 一:确定自己的环境配置是否有问题 二:打开终端,输入命令行 brew install wget 点击 ...
- 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 ...
- Excel图表-创意雷达图-原创图表
p{ font-size: 15px; } .alexrootdiv>div{ background: #eeeeee; border: 1px solid #aaa; width: 99%; ...
- 测试开发Python培训:模拟登录新浪微博-技术篇
测试开发Python培训:模拟登录新浪微博-技术篇 一般一个初学者项目的起点就是登陆功能的自动化,而面临的项目不同实现的技术难度是不一样的,poptest在做测试开发培训中更加关注技术难点,掌握技 ...
- HBase应用快速学习
HBase是一个高性能.面向列.可伸缩的开源分布式NoSQL数据库,是Google Bigtable的开源实现. HBase的思想和应用和传统的RDBMS,NoSQL等有比较大的区别,这篇文章从HBa ...
- Maven工程webinfo下面的JSP页面无法加载.js、.css文件的解决方案
--下面是我的工程路径 --我jsp的写法 -----启动工程,访问js文件的路径是这样的, href="http://localhost:8080/activiti/css/public. ...
- 用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 ...