您的位置:首页 > 编程语言 > Lua

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

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进行执行
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: