/* * Copyright (C) Xiaozhe Wang (chaoslawful) * Copyright (C) Yichun Zhang (agentzh) */ #ifndef DDEBUG #define DDEBUG 0 #endif #include "ddebug.h" #include "ngx_http_lua_coroutine.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_probe.h" /* * Design: * * In order to support using ngx.* API in Lua coroutines, we have to create * new coroutine in the main coroutine instead of the calling coroutine */ static int ngx_http_lua_coroutine_create(lua_State *L); static int ngx_http_lua_coroutine_resume(lua_State *L); static int ngx_http_lua_coroutine_yield(lua_State *L); static int ngx_http_lua_coroutine_status(lua_State *L); static const ngx_str_t ngx_http_lua_co_status_names[] = { ngx_string("running"), ngx_string("suspended"), ngx_string("normal"), ngx_string("dead"), ngx_string("zombie") }; static int ngx_http_lua_coroutine_create(lua_State *L) { ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; r = ngx_http_lua_get_req(L); if (r == NULL) { return luaL_error(L, "no request found"); } ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { return luaL_error(L, "no request ctx found"); } return ngx_http_lua_coroutine_create_helper(L, r, ctx, NULL); } int ngx_http_lua_coroutine_create_helper(lua_State *L, ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t **pcoctx) { lua_State *vm; /* the Lua VM */ lua_State *co; /* new coroutine to be created */ ngx_http_lua_co_ctx_t *coctx; /* co ctx for the new coroutine */ luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, "Lua function expected"); ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT | NGX_HTTP_LUA_CONTEXT_TIMER | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); vm = ngx_http_lua_get_lua_vm(r, ctx); /* create new coroutine on root Lua state, so it always yields * to main Lua thread */ co = lua_newthread(vm); ngx_http_lua_probe_user_coroutine_create(r, L, co); coctx = ngx_http_lua_get_co_ctx(co, ctx); if (coctx == NULL) { coctx = ngx_http_lua_create_co_ctx(r, ctx); if (coctx == NULL) { return luaL_error(L, "no memory"); } } else { ngx_memzero(coctx, sizeof(ngx_http_lua_co_ctx_t)); coctx->co_ref = LUA_NOREF; } coctx->co = co; coctx->co_status = NGX_HTTP_LUA_CO_SUSPENDED; /* make new coroutine share globals of the parent coroutine. * NOTE: globals don't have to be separated! */ ngx_http_lua_get_globals_table(L); lua_xmove(L, co, 1); ngx_http_lua_set_globals_table(co); lua_xmove(vm, L, 1); /* move coroutine from main thread to L */ lua_pushvalue(L, 1); /* copy entry function to top of L*/ lua_xmove(L, co, 1); /* move entry function from L to co */ if (pcoctx) { *pcoctx = coctx; } #ifdef NGX_LUA_USE_ASSERT coctx->co_top = 1; #endif return 1; /* return new coroutine to Lua */ } static int ngx_http_lua_coroutine_resume(lua_State *L) { lua_State *co; ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; ngx_http_lua_co_ctx_t *coctx; ngx_http_lua_co_ctx_t *p_coctx; /* parent co ctx */ co = lua_tothread(L, 1); luaL_argcheck(L, co, 1, "coroutine expected"); r = ngx_http_lua_get_req(L); if (r == NULL) { return luaL_error(L, "no request found"); } ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { return luaL_error(L, "no request ctx found"); } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT | NGX_HTTP_LUA_CONTEXT_TIMER | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); p_coctx = ctx->cur_co_ctx; if (p_coctx == NULL) { return luaL_error(L, "no parent co ctx found"); } coctx = ngx_http_lua_get_co_ctx(co, ctx); if (coctx == NULL) { return luaL_error(L, "no co ctx found"); } ngx_http_lua_probe_user_coroutine_resume(r, L, co); if (coctx->co_status != NGX_HTTP_LUA_CO_SUSPENDED) { dd("coroutine resume: %d", coctx->co_status); lua_pushboolean(L, 0); lua_pushfstring(L, "cannot resume %s coroutine", ngx_http_lua_co_status_names[coctx->co_status].data); return 2; } p_coctx->co_status = NGX_HTTP_LUA_CO_NORMAL; coctx->parent_co_ctx = p_coctx; dd("set coroutine to running"); coctx->co_status = NGX_HTTP_LUA_CO_RUNNING; ctx->co_op = NGX_HTTP_LUA_USER_CORO_RESUME; ctx->cur_co_ctx = coctx; /* yield and pass args to main thread, and resume target coroutine from * there */ return lua_yield(L, lua_gettop(L) - 1); } static int ngx_http_lua_coroutine_yield(lua_State *L) { ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; ngx_http_lua_co_ctx_t *coctx; r = ngx_http_lua_get_req(L); if (r == NULL) { return luaL_error(L, "no request found"); } ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { return luaL_error(L, "no request ctx found"); } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT | NGX_HTTP_LUA_CONTEXT_TIMER | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); coctx = ctx->cur_co_ctx; coctx->co_status = NGX_HTTP_LUA_CO_SUSPENDED; ctx->co_op = NGX_HTTP_LUA_USER_CORO_YIELD; if (!coctx->is_uthread && coctx->parent_co_ctx) { dd("set coroutine to running"); coctx->parent_co_ctx->co_status = NGX_HTTP_LUA_CO_RUNNING; ngx_http_lua_probe_user_coroutine_yield(r, coctx->parent_co_ctx->co, L); } else { ngx_http_lua_probe_user_coroutine_yield(r, NULL, L); } /* yield and pass retvals to main thread, * and resume parent coroutine there */ return lua_yield(L, lua_gettop(L)); } void ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) { int rc; /* new coroutine table */ lua_createtable(L, 0 /* narr */, 14 /* nrec */); /* get old coroutine table */ lua_getglobal(L, "coroutine"); /* set running to the old one */ lua_getfield(L, -1, "running"); lua_setfield(L, -3, "running"); lua_getfield(L, -1, "create"); lua_setfield(L, -3, "_create"); lua_getfield(L, -1, "resume"); lua_setfield(L, -3, "_resume"); lua_getfield(L, -1, "yield"); lua_setfield(L, -3, "_yield"); lua_getfield(L, -1, "status"); lua_setfield(L, -3, "_status"); /* pop the old coroutine */ lua_pop(L, 1); lua_pushcfunction(L, ngx_http_lua_coroutine_create); lua_setfield(L, -2, "__create"); lua_pushcfunction(L, ngx_http_lua_coroutine_resume); lua_setfield(L, -2, "__resume"); lua_pushcfunction(L, ngx_http_lua_coroutine_yield); lua_setfield(L, -2, "__yield"); lua_pushcfunction(L, ngx_http_lua_coroutine_status); lua_setfield(L, -2, "__status"); lua_setglobal(L, "coroutine"); /* inject coroutine APIs */ { const char buf[] = "local keys = {'create', 'yield', 'resume', 'status'}\n" "local getfenv = getfenv\n" "for _, key in ipairs(keys) do\n" "local std = coroutine['_' .. key]\n" "local ours = coroutine['__' .. key]\n" "local raw_ctx = ngx._phase_ctx\n" "coroutine[key] = function (...)\n" "local r = getfenv(0).__ngx_req\n" "if r then\n" "local ctx = raw_ctx(r)\n" /* ignore header and body filters */ "if ctx ~= 0x020 and ctx ~= 0x040 then\n" "return ours(...)\n" "end\n" "end\n" "return std(...)\n" "end\n" "end\n" "local create, resume = coroutine.create, coroutine.resume\n" "coroutine.wrap = function(f)\n" "local co = create(f)\n" "return function(...) return select(2, resume(co, ...)) end\n" "end\n" "package.loaded.coroutine = coroutine"; #if 0 "debug.sethook(function () collectgarbage() end, 'rl', 1)" #endif ; rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, "=coroutine.wrap"); } if (rc != 0) { ngx_log_error(NGX_LOG_ERR, log, 0, "failed to load Lua code for coroutine.wrap(): %i: %s", rc, lua_tostring(L, -1)); lua_pop(L, 1); return; } rc = lua_pcall(L, 0, 0, 0); if (rc != 0) { ngx_log_error(NGX_LOG_ERR, log, 0, "failed to run the Lua code for coroutine.wrap(): %i: %s", rc, lua_tostring(L, -1)); lua_pop(L, 1); } } static int ngx_http_lua_coroutine_status(lua_State *L) { lua_State *co; /* new coroutine to be created */ ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; ngx_http_lua_co_ctx_t *coctx; /* co ctx for the new coroutine */ co = lua_tothread(L, 1); luaL_argcheck(L, co, 1, "coroutine expected"); r = ngx_http_lua_get_req(L); if (r == NULL) { return luaL_error(L, "no request found"); } ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { return luaL_error(L, "no request ctx found"); } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT | NGX_HTTP_LUA_CONTEXT_TIMER | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); coctx = ngx_http_lua_get_co_ctx(co, ctx); if (coctx == NULL) { lua_pushlstring(L, (const char *) ngx_http_lua_co_status_names[NGX_HTTP_LUA_CO_DEAD].data, ngx_http_lua_co_status_names[NGX_HTTP_LUA_CO_DEAD].len); return 1; } dd("co status: %d", coctx->co_status); lua_pushlstring(L, (const char *) ngx_http_lua_co_status_names[coctx->co_status].data, ngx_http_lua_co_status_names[coctx->co_status].len); return 1; } /* vi:set ft=c ts=4 sw=4 et fdm=marker: */