openresty源码剖析——lua代码的执行
2017-05-09 19:15
1461 查看
上一篇文章中(http://www.cnblogs.com/magicsoar/p/6774872.html)我们讨论了openresty是如何加载lua代码的
那么加载完成之后的lua代码又是如何执行的呢
##代码的执行
在init_by_lua等阶段openresty是在主协程中通过lua_pcall直接执行lua代码
而在access_by_luacontent_by_lua等阶段中,openresty创建一个新的协程,通过lua_resume执行lua代码
二者的区别在于能否执行ngx.slepp.ngx.threadngx.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
27-50行,有一步是重新设置请求的上下文,将用于标示当前进入了那个阶段的变量重置为0
这几个字段的用处在ngx_http_lua_content_handler函数中用于确认之前是否进入过对应阶段
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创建协程
312行,获得了主协程栈中有多少元素
314-315行,获得全局变量中储存协程的tableLUA_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多行,内容如下:
1015行,通过lua_resume执行协程的函数,并根据返回的结果进行不同的处理
LUA_YIELD:协程被挂起
0:协程执行结束
其他:运行出错,如内存不足等
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的返回值
在openresty内部重新实现的coroutine.yield和coroutine.resume和ngx.thread.spawn中会对ctx->co_op进行赋值
1064行,caseNGX_HTTP_LUA_USER_CORO_NOP表示不再有协程需要处理了,跳出这一次循环,等待下一次的读写时间,或者定时器到期
1071行,caseNGX_HTTP_USER_THREAD_RESUME对应ngx.thread.spawn被调用的情况
1085行,caseNGX_HTTP_LUA_CORO_RESUME对应有lua代码调用coroutine.resume,把当前线程标记为NGX_HTTP_LUA_USER_CORO_NOP
1106行,default对应NGX_HTTP_LUA_CODO_YIELD,对应coroutine.yield被调用的情况
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.threadAPI的存在,可能有多个协程在跑,需要判断父协程和所有的子协程的运行情况。
1183行,判断是不是主协程
1187行,执行完毕的协程是主协程,从全局table中删除这个协程
1188-1193行,判断还在运行的子协程个数,如果非0返回NGX_AGAIN,否则gotodone进行一些数据发送的相关工作并返回NGX_OK
1195-1233,判断执行完毕的是不是子协程
1223行,由于协程已经执行完毕,从全局table中删除这个协程,可以被luaGC掉
1223行,还在运行的子协程个数-1
1226行,判断主协程是否还需要运行,是的话,返回NGX_AGAIN,否则gotodone,进行一些数据发送的相关工作并返回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进行执行
那么加载完成之后的lua代码又是如何执行的呢
##代码的执行
在init_by_lua等阶段openresty是在主协程中通过lua_pcall直接执行lua代码
而在access_by_luacontent_by_lua等阶段中,openresty创建一个新的协程,通过lua_resume执行lua代码
二者的区别在于能否执行ngx.slepp.ngx.threadngx.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
24ngx_int_t 25ngx_http_lua_content_by_chunk(lua_State*L,ngx_http_request_t*r) 26{ 27... 50ctx->entered_content_phase=1;//标示当前进入了content_phase 51 52/*{{{newcoroutinetohandlerequest*/ 53co=ngx_http_lua_new_thread(r,L,&co_ref);//创建了一个新的lua协程 54 61... 62/*movecodeclosuretonewcoroutine*/ 63lua_xmove(L,co,1);//主协程的栈顶是需要执行的lua函数,通过lua_xmove将栈顶函数交换到新lua协程中 64 65/*setclosure'senvtabletonewcoroutine'sglobalstable*/ 66ngx_http_lua_get_globals_table(co); 67lua_setfenv(co,-2); 68 69/*savenginxrequestincoroutineglobalstable*/ 70ngx_http_lua_set_req(co,r);//把当前请求r赋值给新协程的全局变量中 71... 103rc=ngx_http_lua_run_thread(L,r,ctx,0);//运行新协程 104... 109if(rc==NGX_AGAIN){ 110returnngx_http_lua_content_run_posted_threads(L,r,ctx,0);//执行需要延后执行的协程,0表示上面传来的状态是NGX_AGAIN 111} 112 113if(rc==NGX_DONE){ 114returnngx_http_lua_content_run_posted_threads(L,r,ctx,1);//执行需要延后执行的协程,1表示上面传来的状态是NGX_DONE 115} 116 117returnNGX_OK; 118}
27-50行,有一步是重新设置请求的上下文,将用于标示当前进入了那个阶段的变量重置为0
855ctx->entered_rewrite_phase=0; 856ctx->entered_access_phase=0; 857ctx->entered_content_phase=0;
这几个字段的用处在ngx_http_lua_content_handler函数中用于确认之前是否进入过对应阶段
135ngx_int_t 136ngx_http_lua_content_handler(ngx_http_request_t*r) 137{ 138... 170if(ctx->entered_content_phase){ 171dd("callingwevhandler"); 172rc=ctx->resume_handler(r); 173dd("wevhandlerreturns%d",(int)rc); 174returnrc; 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创建协程
303lua_State*
304ngx_http_lua_new_thread(ngx_http_request_t*r,lua_State*L,int*ref)
305{
306...
312base=lua_gettop(L);
313
314lua_pushlightuserdata(L,&ngx_http_lua_coroutines_key);//获取全局变量中储存协程的table
315lua_rawget(L,LUA_REGISTRYINDEX);
316
317co=lua_newthread(L);//创建新协程
319...
334*ref=luaL_ref(L,-2);//将创建的新协程保存对应的全局变量中
335
336if(*ref==LUA_NOREF){
337lua_settop(L,base);/*restoremainthreadstack*/
338returnNULL;
339}
340
341lua_settop(L,base);//恢复主协程的栈空间大小
342returnco;
343}
312行,获得了主协程栈中有多少元素
314-315行,获得全局变量中储存协程的tableLUA_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多行,内容如下:
951ngx_http_lua_run_thread(lua_State*L,ngx_http_request_t*r,
952ngx_http_lua_ctx_t*ctx,volatileintnrets)
953{
954...
973NGX_LUA_EXCEPTION_TRY{
974...
982for(;;){
983...
997orig_coctx=ctx->cur_co_ctx;
998...
1015rv=lua_resume(orig_coctx->co,nrets);//通过lua_resume执行协程中的函数
1016...
1032switch(rv){//处理lua_resume的返回值
1033caseLUA_YIELD:
1034..
1047if(r->uri_changed){
1048returnngx_http_lua_handle_rewrite_jump(L,r,ctx);
1049}
1050if(ctx->exited){
1051returnngx_http_lua_handle_exit(L,r,ctx);
1052}
1053if(ctx->exec_uri.len){
1054returnngx_http_lua_handle_exec(L,r,ctx);
1055}
1056switch(ctx->co_op){
1057...
1167}
1168continue;
1169case0:
1170...
1295continue;
1296...
1313default:
1314err="unknownerror";
1315break;
1316}
1317...
1444}
1445}NGX_LUA_EXCEPTION_CATCH{
1446dd("nginxexecutionrestored");
1447}
1448returnNGX_ERROR;
1449
1450no_parent:
1451...
1465return(r->header_sent||ctx->header_sent)?
1466NGX_ERROR:NGX_HTTP_INTERNAL_SERVER_ERROR;
1467
1468done:
1469...
1481returnNGX_OK;
1482}
1015行,通过lua_resume执行协程的函数,并根据返回的结果进行不同的处理
LUA_YIELD:协程被挂起
0:协程执行结束
其他:运行出错,如内存不足等
1032switch(rv){
1033caseLUA_YIELD:
1034...
1047if(r->uri_changed){
1048returnngx_http_lua_handle_rewrite_jump(L,r,ctx);//调用了ngx.redirect
1049}
1050
1051if(ctx->exited){
1052returnngx_http_lua_handle_exit(L,r,ctx);//调用了ngx.exit
1053}
1054
1055if(ctx->exec_uri.len){
1056returnngx_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的返回值
1063switch(ctx->co_op){
1064caseNGX_HTTP_LUA_USER_CORO_NOP:
1065...
1069ctx->cur_co_ctx=NULL;
1070returnNGX_AGAIN;
1071caseNGX_HTTP_LUA_USER_THREAD_RESUME://ngx.thread.spawn
1072...
1075ctx->co_op=NGX_HTTP_LUA_USER_CORO_NOP;
1076nrets=lua_gettop(ctx->cur_co_ctx->co)-1;
1077dd("nrets=%d",nrets);
1078...
1084break;
1085caseNGX_HTTP_LUA_USER_CORO_RESUME://coroutine.resume
1086...
1093ctx->co_op=NGX_HTTP_LUA_USER_CORO_NOP;
1094old_co=ctx->cur_co_ctx->parent_co_ctx->co;
1095nrets=lua_gettop(old_co);
1096if(nrets){
1097dd("moving%dreturnvaluestoparent",nrets);
1098lua_xmove(old_co,ctx->cur_co_ctx->co,nrets);
1099...
1103}
1104break;
1105default://coroutine.yield
1106...
在openresty内部重新实现的coroutine.yield和coroutine.resume和ngx.thread.spawn中会对ctx->co_op进行赋值
1064行,caseNGX_HTTP_LUA_USER_CORO_NOP表示不再有协程需要处理了,跳出这一次循环,等待下一次的读写时间,或者定时器到期
1071行,caseNGX_HTTP_USER_THREAD_RESUME对应ngx.thread.spawn被调用的情况
1085行,caseNGX_HTTP_LUA_CORO_RESUME对应有lua代码调用coroutine.resume,把当前线程标记为NGX_HTTP_LUA_USER_CORO_NOP
1106行,default对应NGX_HTTP_LUA_CODO_YIELD,对应coroutine.yield被调用的情况
1113default:
1114...
1119ctx->co_op=NGX_HTTP_LUA_USER_CORO_NOP;
1120
1121if(ngx_http_lua_is_thread(ctx)){
1122...
1132ngx_http_lua_probe_info("setcorunning");
1133ctx->cur_co_ctx->co_status=NGX_HTTP_LUA_CO_RUNNING;
1134
1135if(ctx->posted_threads){
1136ngx_http_lua_post_thread(r,ctx,ctx->cur_co_ctx);
1137ctx->cur_co_ctx=NULL;
1138returnNGX_AGAIN;
1139}
1140...
1144nrets=0;
1145continue;
1146}
1147...
1150nrets=lua_gettop(ctx->cur_co_ctx->co);
1151next_coctx=ctx->cur_co_ctx->parent_co_ctx;
1152next_co=next_coctx->co;
1153...
1158lua_pushboolean(next_co,1);
1159
1160if(nrets){
1161dd("moving%dreturnvaluestonextco",nrets);
1162lua_xmove(ctx->cur_co_ctx->co,next_co,nrets);
1163}
1164nrets++;/*addthetruebooleanvalue*/
1165ctx->cur_co_ctx=next_coctx;
1166break;
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.threadAPI的存在,可能有多个协程在跑,需要判断父协程和所有的子协程的运行情况。
1172case0:
1173...
1183if(ngx_http_lua_is_entry_thread(ctx)){
1184...
1187ngx_http_lua_del_thread(r,L,ctx,ctx->cur_co_ctx);
1188if(ctx->uthreads){
1189ctx->cur_co_ctx=NULL;
1190returnNGX_AGAIN;
1191}
1192/*alluserthreadsterminatedalready*/
1193gotodone;
1194}
1195if(ctx->cur_co_ctx->is_uthread){
1196...
1223ngx_http_lua_del_thread(r,L,ctx,ctx->cur_co_ctx);
1224ctx->uthreads--;
1225if(ctx->uthreads==0){
1226if(ngx_http_lua_entry_thread_alive(ctx)){
1227ctx->cur_co_ctx=NULL;
1228returnNGX_AGAIN;
1229}
1230gotodone;
1231}
1232/*someotheruserthreadsstillrunning*/
1233ctx->cur_co_ctx=NULL;
1234returnNGX_AGAIN;
1235}
1183行,判断是不是主协程
1187行,执行完毕的协程是主协程,从全局table中删除这个协程
1188-1193行,判断还在运行的子协程个数,如果非0返回NGX_AGAIN,否则gotodone进行一些数据发送的相关工作并返回NGX_OK
1195-1233,判断执行完毕的是不是子协程
1223行,由于协程已经执行完毕,从全局table中删除这个协程,可以被luaGC掉
1223行,还在运行的子协程个数-1
1226行,判断主协程是否还需要运行,是的话,返回NGX_AGAIN,否则gotodone,进行一些数据发送的相关工作并返回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源码剖析——lua代码的加载
- openresty源码剖析——lua代码的加载
- lua开源测试框架busted源码学习(二)--代码框架分析和用例执行流程
- Lua源码剖析-GC
- 【openresty】向lua代码中传递参数
- Lua源码剖析(lmathlib.c)
- Lua源码剖析(lstrlib.c)
- UDT源码剖析(六):UDT::socket()过程代码注释
- [Spark源码剖析] Task的调度与执行源码剖析
- FreeSWITCH IVR中lua调用并执行nodejs代码
- UDT源码剖析(三):UDT::startup()过程代码注释
- 从源码剖析一个Spark WordCount Job执行的全过程
- Monkey源码分析2—Monkey代码如何被启动执行
- Lua中编译执行代码相关的函数以及机制
- 执行代码[置顶] 初探Tomcat源码 —— 关闭钩子
- C#中获取当前执行的函数名、代码行、源码文件名
- lua 02 源码剖析
- Spring MVC源码深入剖析执行流程