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被调用的情况

如果有需要延后执行的协程,调用ngx_http_lua_post_thread放入链表中,排队等候处理

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中删除这个协程

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

PS:推荐一个好朋友的微信公众号,一个每天都在思考或者在思考路上的公众号运营少女~

时间: 2025-01-05 19:17:43

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

Python源码剖析笔记3-Python执行原理初探

Python源码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源码剖析笔记,然而慢慢觉得没有从一个宏观的角度理解python执行原理的话,从底向上分析未免太容易让人疑惑,不如先从宏观上对python执行原理有了一个基本了解,再慢慢探究细节,这样也许会好很多.这也是最近这么久没有更新了笔记了,一直在看源码剖析书籍和源码,希望能够从一个宏观层面理清python执行原理.人说读书从薄读厚,再从厚读薄方是理解了

Skynet服务器框架(二) C源码剖析启动流程

引言: 之前我们已经完成了在Linux下配置安装 skynet 的环境,并成功启动了 skynet 服务框架,为了从底层更好地理解整个框架的实现过程,我们有必要剖析一下源码,由于底层的源码都是用C语言写的,lua脚本基本是用来进行业务层开发,所以我们从C源码开始解读框架.打开下载包的 skynet-src 目录,这里是skynet框架的核心C源码,接下来我们就要来解读 skynet_main.c 和 skynet_start.c 这两个与skynet启动相关的C源码. 1.入口函数和初始化: 我

Phaser实现源码剖析

在这里首先说明一下,由于Phaser在4.3代码里是存在,但并没有被开放出来供使用,但已经被本人大致研究了,因此也一并进行剖析. Phaser是一个可以重复利用的同步栅栏,功能上与CyclicBarrier和CountDownLatch相似,不过提供更加灵活的用法.也就是说,Phaser的同步模型与它们差不多.一般运用的场景是一组线程希望同时到达某个执行点后(先到达的会被阻塞),执行一个指定任务,然后这些线程才被唤醒继续执行其它任务. Phaser一般是定义一个parties数(parties一

菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)

俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中的奥秘,下面我们就一起来研究一下nginx的main函数. 1.nginx的main函数解读 nginx启动显然是由main函数驱动的,main函数在在core/nginx.c文件中,其源代码解析如下,涉及到的数据结构在本节仅指出其作用,将在第二节中详细解释. nginx main函数的流程图如下:

《STL源码剖析》---stl_iterator.h阅读笔记

STL设计的中心思想是将容器(container)和算法(algorithm)分开,迭代器是容器(container)和算法(algorithm)之间的桥梁. 迭代器可以如下定义:提供一种方法,能够依序寻访某个容器内的所有元素,而又无需暴露该容器的内部表达方式. 在阅读代码之前,要先了解一个新概念:Traits编程技法 template <class T> struct MyIter { typedef T value_type //内嵌型别声明 T *ptr; MyIter(T *p = 0

《python源码剖析》笔记 Python虚拟机框架

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1. Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令, 并在当前的上下文环境中执行这条字节码指令. Python虚拟机实际上是在模拟操作中执行文件的过程 PyCodeObject对象中包含了字节码指令以及程序的所有静态信息,但没有包含 程序运行时的动态信息--执行环境(PyFrameObject) 2.Python源码中的PyFrameObject

《python源码剖析》笔记 python虚拟机中的一般表达式

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.字节码指令 LOAD_CONST:从consts表中读取序号为i的元素并压入到运行时栈中 STORE_NAME:改变local名字空间.从符号表names取序号为i的元素作为变量名, 取运行时栈的栈顶元素作为变量值,完成从变量名到变量值的映射关系的创建. BUILD_MAP:创建一个空的PyDictObject对象,并压入运行时栈 DUP_TOP:将栈顶元素的引用计数增加1,并将它再次

strlen源码剖析(可查看glibc和VC的CRT源代码)

学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效.恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简单的函数strlen研究了一下,并对各种实现作了简单的效率测试. strlen的函数原形如下: size_t strlen(const char *str); strlen返回str中字符的个数,其中str为一个以'\0'结尾的字符串(a null-terminated string). 1. 简单实现

Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程模型的变种,无论其怎么演化,其核心组件都包含了Reactor实例(提供事件注册.注销.通知功能).多路复用器(由操作系统提供,比如kqueue.select.epoll等).事件处理器(负责事件的处理)以及事件源(linux中这就是描述符)这四个组件.一般,会单独启动一个线程运行Reactor实例来