text: 
  /*
   * 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: */
  