ngx_int_t
ngx_http_lua_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    ngx_http_request_t            *pr;
    ngx_http_lua_ctx_t            *pr_ctx;
    ngx_http_lua_ctx_t            *ctx = data;
    size_t                         len;
    ngx_str_t                     *body_str;
    u_char                        *p;
    ngx_chain_t                   *cl;

    if (ctx->run_post_subrequest) {
        return rc;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
            "lua run post subrequest handler: rc:%d, waiting:%d, done:%d",
            rc, ctx->waiting, ctx->done);

    ctx->run_post_subrequest = 1;

#if 0
    ngx_http_lua_dump_postponed(r);
#endif

    pr = r->parent;

    pr_ctx = ngx_http_get_module_ctx(pr, ngx_http_lua_module);
    if (pr_ctx == NULL) {
        return NGX_ERROR;
    }

    pr_ctx->waiting--;

    if (pr_ctx->waiting == 0) {
        pr_ctx->done = 1;
    }

    if (pr_ctx->entered_content_phase) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                "lua restoring write event handler");

        pr->write_event_handler = ngx_http_lua_content_wev_handler;
    }

    dd("status rc = %d", (int) rc);
    dd("status headers_out.status = %d", (int) r->headers_out.status);
    dd("uri: %.*s", (int) r->uri.len, r->uri.data);

    /*  capture subrequest response status */
    if (rc == NGX_ERROR) {
        pr_ctx->sr_statuses[ctx->index] = NGX_HTTP_INTERNAL_SERVER_ERROR;

    } else if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        dd("HERE");
        pr_ctx->sr_statuses[ctx->index] = rc;

    } else {
        dd("THERE");
        pr_ctx->sr_statuses[ctx->index] = r->headers_out.status;
    }

    if (pr_ctx->sr_statuses[ctx->index] == 0) {
        pr_ctx->sr_statuses[ctx->index] = NGX_HTTP_OK;
    }

    dd("pr_ctx status: %d", (int) pr_ctx->sr_statuses[ctx->index]);

    /* copy subrequest response headers */

    pr_ctx->sr_headers[ctx->index] = &r->headers_out;

    /* copy subrequest response body */

    body_str = &pr_ctx->sr_bodies[ctx->index];

    len = 0;
    for (cl = ctx->body; cl; cl = cl->next) {
        /*  ignore all non-memory buffers */
        len += cl->buf->last - cl->buf->pos;
    }

    body_str->len = len;

    if (len == 0) {
        body_str->data = NULL;

    } else {
        p = ngx_palloc(r->pool, len);
        if (p == NULL) {
            return NGX_ERROR;
        }

        body_str->data = p;

        /* copy from and then free the data buffers */

        for (cl = ctx->body; cl; cl = cl->next) {
            p = ngx_copy(p, cl->buf->pos,
                    cl->buf->last - cl->buf->pos);

            cl->buf->last = cl->buf->pos;

#if 0
            dd("free body chain link buf ASAP");
            ngx_pfree(r->pool, cl->buf->start);
#endif
        }
    }

    if (ctx->body) {

#if defined(nginx_version) && nginx_version >= 1001004
        ngx_chain_update_chains(r->pool,
#else
        ngx_chain_update_chains(
#endif
                                &pr_ctx->free_bufs, &pr_ctx->busy_bufs,
                                &ctx->body,
                                (ngx_buf_tag_t) &ngx_http_lua_module);

        dd("free bufs: %p", pr_ctx->free_bufs);
    }
ngx_int_t
ngx_http_lua_wev_handler(ngx_http_request_t *r)
{
    ngx_int_t                    rc;
    ngx_http_lua_ctx_t          *ctx;
    ngx_http_lua_main_conf_t    *lmcf;
    lua_State                   *cc;
    ngx_str_t                   *body_str;
    ngx_http_headers_out_t      *sr_headers;
    ngx_list_part_t             *part;
    ngx_table_elt_t             *header;
    ngx_uint_t                   i, index;

    dd("wev handler %.*s %.*s a:%d, postponed:%p",
            (int) r->uri.len, r->uri.data,
            (int) ngx_cached_err_log_time.len,
            ngx_cached_err_log_time.data,
            r == r->connection->data,
            r->postponed);
#if 0
    ngx_http_lua_dump_postponed(r);
#endif

    ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
    if (ctx == NULL) {
        goto error;
    }

    dd("ctx = %p", ctx);
    dd("request done: %d", (int) r->done);
    dd("cleanup done: %p", ctx->cleanup);

    if (ctx->cleanup == NULL) {
        /* already done */
        dd("cleanup is null: %.*s", (int) r->uri.len, r->uri.data);

        if (ctx->entered_content_phase) {
            ngx_http_finalize_request(r,
                    ngx_http_lua_flush_postponed_outputs(r));
        }

        return NGX_DONE;
    }

    dd("waiting: %d, done: %d", (int) ctx->waiting,
            ctx->done);

    if (ctx->waiting && ! ctx->done) {
        dd("%.*s waiting and not done", (int) r->uri.len, r->uri.data);

#if 0
        ngx_http_lua_dump_postponed(r);
#endif

        if (r == r->connection->data && r->postponed) {
            if (r->postponed->request) {
                r->connection->data = r->postponed->request;

#if defined(nginx_version) && nginx_version >= 8012
                ngx_http_post_request(r->postponed->request, NULL);
#else
                ngx_http_post_request(r->postponed->request);
#endif

            } else {
                ngx_http_lua_flush_postponed_outputs(r);
            }
        }

        return NGX_DONE;
    }

    ctx->done = 0;

    dd("nsubreqs: %d", (int) ctx->nsubreqs);

    for (index = 0; index < ctx->nsubreqs; index++) {
        dd("summary: reqs %d, subquery %d, waiting %d, req %.*s",
                (int) ctx->nsubreqs,
                (int) index,
                (int) ctx->waiting,
                (int) r->uri.len, r->uri.data);

        cc = ctx->cc;

        /*  {{{ construct ret value */
        lua_newtable(cc);

        /*  copy captured status */
        lua_pushinteger(cc, ctx->sr_statuses[index]);
        lua_setfield(cc, -2, "status");

        /*  copy captured body */

        body_str = &ctx->sr_bodies[index];

        lua_pushlstring(cc, (char *) body_str->data, body_str->len);
        lua_setfield(cc, -2, "body");

        if (body_str->data) {
            dd("free body buffer ASAP");
            ngx_pfree(r->pool, body_str->data);
        }

        /* copy captured headers */

        lua_newtable(cc); /* res.header */

        sr_headers = ctx->sr_headers[index];

        if (sr_headers->content_length == NULL
            && sr_headers->content_length_n >= 0)
        {
            lua_pushliteral(cc, "Content-Length"); /* header key */

            lua_pushnumber(cc, sr_headers->content_length_n);
                /* head key value */

            lua_rawset(cc, -3); /* head */
        }

        if (sr_headers->content_type.len) {
            lua_pushliteral(cc, "Content-Type"); /* header key */
            lua_pushlstring(cc, (char *) sr_headers->content_type.data,
                    sr_headers->content_type.len); /* head key value */
            lua_rawset(cc, -3); /* head */
        }

        dd("saving subrequest response headers");

        part = &sr_headers->headers.part;
        header = part->elts;

        for (i = 0; /* void */; i++) {

            if (i >= part->nelts) {
                if (part->next == NULL) {
                    break;
                }

                part = part->next;
                header = part->elts;
                i = 0;
            }

            dd("checking sr header %.*s", (int) header[i].key.len,
                    header[i].key.data);

#if 1
            if (header[i].hash == 0) {
                continue;
            }
#endif

            dd("pushing sr header %.*s", (int) header[i].key.len,
                    header[i].key.data);

            lua_pushlstring(cc, (char *) header[i].key.data,
                    header[i].key.len); /* header key */
            lua_pushvalue(cc, -1); /* stack: table key key */

            /* check if header already exists */
            lua_rawget(cc, -3); /* stack: table key value */

            if (lua_isnil(cc, -1)) {
                lua_pop(cc, 1); /* stack: table key */

                lua_pushlstring(cc, (char *) header[i].value.data,
                        header[i].value.len); /* stack: table key value */

                lua_rawset(cc, -3); /* stack: table */

            } else {
                if (! lua_istable(cc, -1)) { /* already inserted one value */
                    lua_createtable(cc, 4, 0);
                        /* stack: table key value table */

                    lua_insert(cc, -2); /* stack: table key table value */
                    lua_rawseti(cc, -2, 1); /* stack: table key table */

                    lua_pushlstring(cc, (char *) header[i].value.data,
                            header[i].value.len);
                        /* stack: table key table value */

                    lua_rawseti(cc, -2, lua_objlen(cc, -2) + 1);
                        /* stack: table key table */

                    lua_rawset(cc, -3); /* stack: table */

                } else {
                    lua_pushlstring(cc, (char *) header[i].value.data,
                            header[i].value.len);
                        /* stack: table key table value */

                    lua_rawseti(cc, -2, lua_objlen(cc, -2) + 1);
                        /* stack: table key table */

                    lua_pop(cc, 2); /* stack: table */
                }
            }
        }

        lua_setfield(cc, -2, "header");

        /*  }}} */
    }

    dd("free sr_statues/headers/bodies memory ASAP");

#if 1
    ngx_pfree(r->pool, ctx->sr_statuses);

    ctx->sr_statuses = NULL;
    ctx->sr_headers = NULL;
    ctx->sr_bodies = NULL;
#endif

    lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module);

    dd("about to run thread for %.*s...", (int) r->uri.len, r->uri.data);

    rc = ngx_http_lua_run_thread(lmcf->lua, r, ctx, ctx->nsubreqs);

    dd("already run thread for %.*s...", (int) r->uri.len, r->uri.data);

    if (rc == NGX_AGAIN || rc == NGX_DONE) {
        return NGX_DONE;
    }

    dd("entered content phase: %d", (int) ctx->entered_content_phase);

    if (ctx->entered_content_phase) {
        ngx_http_finalize_request(r, rc);
    }

    if (rc == NGX_OK) {
        return NGX_DECLINED;
    }

    return rc;

error:
    if (ctx->entered_content_phase) {
        ngx_http_finalize_request(r,
                ctx->headers_sent ? NGX_ERROR: NGX_HTTP_INTERNAL_SERVER_ERROR);
    }

    return NGX_ERROR;
}
ngx_int_t
ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r,
        ngx_http_lua_ctx_t *ctx, int nret)
{
    int                      rv;
    int                      cc_ref;
    lua_State               *cc;
    const char              *err, *msg;
    ngx_int_t                rc;

    /* set Lua VM panic handler */
    lua_atpanic(L, ngx_http_lua_atpanic);

    dd("ctx = %p", ctx);

    NGX_LUA_EXCEPTION_TRY {
        cc = ctx->cc;
        cc_ref = ctx->cc_ref;

        /*  run code */
        rv = lua_resume(cc, nret);

        dd("lua resume returns %d", (int) rv);

        switch (rv) {
            case LUA_YIELD:
                /*  yielded, let event handler do the rest job */
                /*  FIXME: add io cmd dispatcher here */

                dd("lua coroutine yielded");

#if 0
                ngx_http_lua_dump_postponed(r);
#endif

                lua_settop(cc, 0);
                return NGX_AGAIN;

            case 0:
                dd("normal end %.*s", (int) r->uri.len, r->uri.data);

#if 0
                ngx_http_lua_dump_postponed(r);
#endif

                ngx_http_lua_del_thread(r, L, cc_ref, 0);
                ctx->cc_ref = LUA_NOREF;

                if (ctx->cleanup) {
                    dd("cleaning up cleanup");
                    *ctx->cleanup = NULL;
                    ctx->cleanup = NULL;
                }

                if (ctx->entered_content_phase) {
                    rc = ngx_http_lua_send_chain_link(r, ctx,
                            NULL /* indicate last_buf */);

                    if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
                        return rc;
                    }
                }

                return NGX_OK;

            case LUA_ERRRUN:
                err = "runtime error";
                break;

            case LUA_ERRSYNTAX:
                err = "syntax error";
                break;

            case LUA_ERRMEM:
                err = "memory allocation error";
                break;

            case LUA_ERRERR:
                err = "error handler error";
                break;

            default:
                err = "unknown error";
                break;
        }

        if (lua_isstring(cc, -1)) {
            dd("user custom error msg");
            msg = lua_tostring(cc, -1);

        } else {
            if (lua_isnil(cc, -1)) {
                if (ctx->exited) {
                    dd("run here...exiting... %d", (int) ctx->exit_code);

                    ngx_http_lua_del_thread(r, L, cc_ref, 0);
                    ctx->cc_ref = LUA_NOREF;

                    if (ctx->cleanup) {
                        *ctx->cleanup = NULL;
                        ctx->cleanup = NULL;
                    }

                    if (ctx->exit_code == NGX_OK) {
                        if (ctx->entered_content_phase) {
                            rc = ngx_http_lua_send_chain_link(r, ctx,
                                    NULL /* indicate last_buf */);

                            if (rc == NGX_ERROR ||
                                    rc >= NGX_HTTP_SPECIAL_RESPONSE)
                            {
                                return rc;
                            }
                        }
                    }

                    return ctx->exit_code;
                }

                if (ctx->exec_uri.len) {
                    ngx_http_lua_del_thread(r, L, cc_ref, 0);
                    ctx->cc_ref = LUA_NOREF;

                    if (ctx->cleanup) {
                        *ctx->cleanup = NULL;
                        ctx->cleanup = NULL;
                    }

                    if (ctx->exec_uri.data[0] == '@') {
                        if (ctx->exec_args.len > 0) {
                            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                                    "query strings %V ignored when exec'ing "
                                    "named location %V",
                                    &ctx->exec_args, &ctx->exec_uri);
                        }

#if defined(nginx_version) && nginx_version >= 8011
                        /* ngx_http_named_location always increments
                         * r->main->count, which is not we want for
                         * non-content phases */

                        if (! ctx->entered_content_phase) {
                            r->main->count--;
                        }
#endif

                        return ngx_http_named_location(r, &ctx->exec_uri);
                    }

                    dd("internal redirect to %.*s", (int) ctx->exec_uri.len,
                            ctx->exec_uri.data);

#if defined(nginx_version) && nginx_version >= 8011
                    /* ngx_http_internal_redirect always increments
                     * r->main->count, which is not we want for
                     * non-content phases */

                    if (! ctx->entered_content_phase) {
                        r->main->count--;
                    }
#endif

                    return ngx_http_internal_redirect(r, &ctx->exec_uri,
                            &ctx->exec_args);
                }
            }

            msg = "unknown reason";
        }

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "content_by_lua aborted: %s: %s",
                err, msg);

        ngx_http_lua_del_thread(r, L, cc_ref, 0);
        ctx->cc_ref = LUA_NOREF;

        if (ctx->cleanup) {
            *ctx->cleanup = NULL;
            ctx->cleanup = NULL;
        }

        dd("headers sent? %d", ctx->headers_sent ? 1 : 0);

        return ctx->headers_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;

    } NGX_LUA_EXCEPTION_CATCH {
        dd("nginx execution restored");
    }

    return NGX_ERROR;
}
ngx_int_t
ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r,
        ngx_http_lua_ctx_t *ctx, int nret)
{
    int                      rv;
    int                      cc_ref;
    lua_State               *cc;
    const char              *err, *msg;
    ngx_int_t                rc;

    /* set Lua VM panic handler */
    lua_atpanic(L, ngx_http_lua_atpanic);

    dd("ctx = %p", ctx);

    NGX_LUA_EXCEPTION_TRY {
        cc = ctx->cc;
        cc_ref = ctx->cc_ref;

        /* XXX: work-around to nginx regex subsystem */
        ngx_http_lua_pcre_malloc_init(r->pool);

        /*  run code */
        rv = lua_resume(cc, nret);

        /* XXX: work-around to nginx regex subsystem */
        ngx_http_lua_pcre_malloc_done();

        dd("lua resume returns %d", (int) rv);

        switch (rv) {
            case LUA_YIELD:
                /*  yielded, let event handler do the rest job */
                /*  FIXME: add io cmd dispatcher here */

                dd("lua coroutine yielded");

#if 0
                ngx_http_lua_dump_postponed(r);
#endif

                lua_settop(cc, 0);
                return NGX_AGAIN;

            case 0:
                dd("normal end %.*s", (int) r->uri.len, r->uri.data);

#if 0
                ngx_http_lua_dump_postponed(r);
#endif

                ngx_http_lua_del_thread(r, L, cc_ref, 0);
                ctx->cc_ref = LUA_NOREF;

                if (ctx->cleanup) {
                    dd("cleaning up cleanup");
                    *ctx->cleanup = NULL;
                    ctx->cleanup = NULL;
                }

                if (ctx->entered_content_phase) {
                    rc = ngx_http_lua_send_chain_link(r, ctx,
                            NULL /* indicate last_buf */);

                    if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
                        return rc;
                    }
                }

                return NGX_OK;

            case LUA_ERRRUN:
                err = "runtime error";
                break;

            case LUA_ERRSYNTAX:
                err = "syntax error";
                break;

            case LUA_ERRMEM:
                err = "memory allocation error";
                break;

            case LUA_ERRERR:
                err = "error handler error";
                break;

            default:
                err = "unknown error";
                break;
        }

        if (lua_isstring(cc, -1)) {
            dd("user custom error msg");
            msg = lua_tostring(cc, -1);

        } else {
            if (lua_isnil(cc, -1)) {
                if (ctx->exited) {
                    dd("run here...exiting... %d", (int) ctx->exit_code);

                    ngx_http_lua_del_thread(r, L, cc_ref, 0);
                    ctx->cc_ref = LUA_NOREF;

                    if (ctx->cleanup) {
                        *ctx->cleanup = NULL;
                        ctx->cleanup = NULL;
                    }

                    if ((ctx->exit_code == NGX_OK &&
                                ctx->entered_content_phase) ||
                                (ctx->exit_code >= NGX_HTTP_OK &&
                                ctx->exit_code < NGX_HTTP_SPECIAL_RESPONSE))
                    {
                        rc = ngx_http_lua_send_chain_link(r, ctx,
                                NULL /* indicate last_buf */);

                        if (rc == NGX_ERROR ||
                                rc >= NGX_HTTP_SPECIAL_RESPONSE)
                        {
                            return rc;
                        }
                    }

                    return ctx->exit_code;
                }

                if (ctx->exec_uri.len) {
                    ngx_http_lua_del_thread(r, L, cc_ref, 0);
                    ctx->cc_ref = LUA_NOREF;

                    if (ctx->cleanup) {
                        *ctx->cleanup = NULL;
                        ctx->cleanup = NULL;
                    }

                    if (ctx->exec_uri.data[0] == '@') {
                        if (ctx->exec_args.len > 0) {
                            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                                    "query strings %V ignored when exec'ing "
                                    "named location %V",
                                    &ctx->exec_args, &ctx->exec_uri);
                        }

                        r->write_event_handler = ngx_http_request_empty_handler;

                        rc = ngx_http_named_location(r, &ctx->exec_uri);
                        if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE)
                        {
                            return rc;
                        }

                        if (! ctx->entered_content_phase &&
                                r != r->connection->data)
                        {
                            /* XXX ensure the main request ref count
                             * is decreased because the current
                             * request will be quit */
                            r->main->count--;
                        }

                        return NGX_DONE;
                    }

                    dd("internal redirect to %.*s", (int) ctx->exec_uri.len,
                            ctx->exec_uri.data);

                    /* resume the write event handler */
                    r->write_event_handler = ngx_http_request_empty_handler;

                    rc = ngx_http_internal_redirect(r, &ctx->exec_uri,
                            &ctx->exec_args);

                    dd("internal redirect returned %d when in content phase? "
                            "%d", (int) rc, ctx->entered_content_phase);

                    if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
                        return rc;
                    }

                    dd("XXYY HERE %d\n", (int) r->main->count);

                    if (! ctx->entered_content_phase &&
                            r != r->connection->data)
                    {
                        /* XXX ensure the main request ref count
                         * is decreased because the current
                         * request will be quit */
                        r->main->count--;
                    }

                    return NGX_DONE;
                }
            }

            msg = "unknown reason";
        }

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "lua handler aborted: %s: %s",
                err, msg);

        ngx_http_lua_del_thread(r, L, cc_ref, 0);
        ctx->cc_ref = LUA_NOREF;

        if (ctx->cleanup) {
            *ctx->cleanup = NULL;
            ctx->cleanup = NULL;
        }

        dd("headers sent? %d", ctx->headers_sent ? 1 : 0);

        return ctx->headers_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;

    } NGX_LUA_EXCEPTION_CATCH {
        dd("nginx execution restored");
    }

    return NGX_ERROR;
}