src/core/ngx_output_chain.c - nginx source code

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Nginx, Inc.
  4. */


  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_event.h>


  8. #if 0
  9. #define NGX_SENDFILE_LIMIT  4096
  10. #endif

  11. /*
  12. * When DIRECTIO is enabled FreeBSD, Solaris, and MacOSX read directly
  13. * to an application memory from a device if parameters are aligned
  14. * to device sector boundary (512 bytes).  They fallback to usual read
  15. * operation if the parameters are not aligned.
  16. * Linux allows DIRECTIO only if the parameters are aligned to a filesystem
  17. * sector boundary, otherwise it returns EINVAL.  The sector size is
  18. * usually 512 bytes, however, on XFS it may be 4096 bytes.
  19. */

  20. #define NGX_NONE            1


  21. static ngx_inline ngx_int_t
  22.     ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf);
  23. static ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool,
  24.     ngx_chain_t **chain, ngx_chain_t *in);
  25. static ngx_int_t ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx,
  26.     off_t bsize);
  27. static ngx_int_t ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx,
  28.     off_t bsize);
  29. static ngx_int_t ngx_output_chain_copy_buf(ngx_output_chain_ctx_t *ctx);


  30. ngx_int_t
  31. ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
  32. {
  33.     off_t         bsize;
  34.     ngx_int_t     rc, last;
  35.     ngx_chain_t  *cl, *out, **last_out;

  36.     if (ctx->in == NULL && ctx->busy == NULL
  37. #if (NGX_HAVE_FILE_AIO || NGX_THREADS)
  38.         && !ctx->aio
  39. #endif
  40.        )
  41.     {
  42.         /*
  43.          * the short path for the case when the ctx->in and ctx->busy chains
  44.          * are empty, the incoming chain is empty too or has the single buf
  45.          * that does not require the copy
  46.          */

  47.         if (in == NULL) {
  48.             return ctx->output_filter(ctx->filter_ctx, in);
  49.         }

  50.         if (in->next == NULL
  51. #if (NGX_SENDFILE_LIMIT)
  52.             && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT)
  53. #endif
  54.             && ngx_output_chain_as_is(ctx, in->buf))
  55.         {
  56.             return ctx->output_filter(ctx->filter_ctx, in);
  57.         }
  58.     }

  59.     /* add the incoming buf to the chain ctx->in */

  60.     if (in) {
  61.         if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {
  62.             return NGX_ERROR;
  63.         }
  64.     }

  65.     out = NULL;
  66.     last_out = &out;
  67.     last = NGX_NONE;

  68.     for ( ;; ) {

  69. #if (NGX_HAVE_FILE_AIO || NGX_THREADS)
  70.         if (ctx->aio) {
  71.             return NGX_AGAIN;
  72.         }
  73. #endif

  74.         while (ctx->in) {

  75.             /*
  76.              * cycle while there are the ctx->in bufs
  77.              * and there are the free output bufs to copy in
  78.              */

  79.             bsize = ngx_buf_size(ctx->in->buf);

  80.             if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) {

  81.                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  82.                               "zero size buf in output "
  83.                               "t:%d r:%d f:%d %p %p-%p %p %O-%O",
  84.                               ctx->in->buf->temporary,
  85.                               ctx->in->buf->recycled,
  86.                               ctx->in->buf->in_file,
  87.                               ctx->in->buf->start,
  88.                               ctx->in->buf->pos,
  89.                               ctx->in->buf->last,
  90.                               ctx->in->buf->file,
  91.                               ctx->in->buf->file_pos,
  92.                               ctx->in->buf->file_last);

  93.                 ngx_debug_point();

  94.                 cl = ctx->in;
  95.                 ctx->in = cl->next;

  96.                 ngx_free_chain(ctx->pool, cl);

  97.                 continue;
  98.             }

  99.             if (bsize < 0) {

  100.                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  101.                               "negative size buf in output "
  102.                               "t:%d r:%d f:%d %p %p-%p %p %O-%O",
  103.                               ctx->in->buf->temporary,
  104.                               ctx->in->buf->recycled,
  105.                               ctx->in->buf->in_file,
  106.                               ctx->in->buf->start,
  107.                               ctx->in->buf->pos,
  108.                               ctx->in->buf->last,
  109.                               ctx->in->buf->file,
  110.                               ctx->in->buf->file_pos,
  111.                               ctx->in->buf->file_last);

  112.                 ngx_debug_point();

  113.                 return NGX_ERROR;
  114.             }

  115.             if (ngx_output_chain_as_is(ctx, ctx->in->buf)) {

  116.                 /* move the chain link to the output chain */

  117.                 cl = ctx->in;
  118.                 ctx->in = cl->next;

  119.                 *last_out = cl;
  120.                 last_out = &cl->next;
  121.                 cl->next = NULL;

  122.                 continue;
  123.             }

  124.             if (ctx->buf == NULL) {

  125.                 rc = ngx_output_chain_align_file_buf(ctx, bsize);

  126.                 if (rc == NGX_ERROR) {
  127.                     return NGX_ERROR;
  128.                 }

  129.                 if (rc != NGX_OK) {

  130.                     if (ctx->free) {

  131.                         /* get the free buf */

  132.                         cl = ctx->free;
  133.                         ctx->buf = cl->buf;
  134.                         ctx->free = cl->next;

  135.                         ngx_free_chain(ctx->pool, cl);

  136.                     } else if (out || ctx->allocated == ctx->bufs.num) {

  137.                         break;

  138.                     } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {
  139.                         return NGX_ERROR;
  140.                     }
  141.                 }
  142.             }

  143.             rc = ngx_output_chain_copy_buf(ctx);

  144.             if (rc == NGX_ERROR) {
  145.                 return rc;
  146.             }

  147.             if (rc == NGX_AGAIN) {
  148.                 if (out) {
  149.                     break;
  150.                 }

  151.                 return rc;
  152.             }

  153.             /* delete the completed buf from the ctx->in chain */

  154.             if (ngx_buf_size(ctx->in->buf) == 0) {
  155.                 cl = ctx->in;
  156.                 ctx->in = cl->next;

  157.                 ngx_free_chain(ctx->pool, cl);
  158.             }

  159.             cl = ngx_alloc_chain_link(ctx->pool);
  160.             if (cl == NULL) {
  161.                 return NGX_ERROR;
  162.             }

  163.             cl->buf = ctx->buf;
  164.             cl->next = NULL;
  165.             *last_out = cl;
  166.             last_out = &cl->next;
  167.             ctx->buf = NULL;
  168.         }

  169.         if (out == NULL && last != NGX_NONE) {

  170.             if (ctx->in) {
  171.                 return NGX_AGAIN;
  172.             }

  173.             return last;
  174.         }

  175.         last = ctx->output_filter(ctx->filter_ctx, out);

  176.         if (last == NGX_ERROR || last == NGX_DONE) {
  177.             return last;
  178.         }

  179.         ngx_chain_update_chains(ctx->pool, &ctx->free, &ctx->busy, &out,
  180.                                 ctx->tag);
  181.         last_out = &out;
  182.     }
  183. }


  184. static ngx_inline ngx_int_t
  185. ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)
  186. {
  187.     ngx_uint_t  sendfile;

  188.     if (ngx_buf_special(buf)) {
  189.         return 1;
  190.     }

  191. #if (NGX_THREADS)
  192.     if (buf->in_file) {
  193.         buf->file->thread_handler = ctx->thread_handler;
  194.         buf->file->thread_ctx = ctx->filter_ctx;
  195.     }
  196. #endif

  197.     sendfile = ctx->sendfile;

  198. #if (NGX_SENDFILE_LIMIT)

  199.     if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) {
  200.         sendfile = 0;
  201.     }

  202. #endif

  203. #if !(NGX_HAVE_SENDFILE_NODISKIO)

  204.     /*
  205.      * With DIRECTIO, disable sendfile() unless sendfile(SF_NOCACHE)
  206.      * is available.
  207.      */

  208.     if (buf->in_file && buf->file->directio) {
  209.         sendfile = 0;
  210.     }

  211. #endif

  212.     if (!sendfile) {

  213.         if (!ngx_buf_in_memory(buf)) {
  214.             return 0;
  215.         }

  216.         buf->in_file = 0;
  217.     }

  218.     if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) {
  219.         return 0;
  220.     }

  221.     if (ctx->need_in_temp && (buf->memory || buf->mmap)) {
  222.         return 0;
  223.     }

  224.     return 1;
  225. }


  226. static ngx_int_t
  227. ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
  228.     ngx_chain_t *in)
  229. {
  230.     ngx_chain_t  *cl, **ll;
  231. #if (NGX_SENDFILE_LIMIT)
  232.     ngx_buf_t    *b, *buf;
  233. #endif

  234.     ll = chain;

  235.     for (cl = *chain; cl; cl = cl->next) {
  236.         ll = &cl->next;
  237.     }

  238.     while (in) {

  239.         cl = ngx_alloc_chain_link(pool);
  240.         if (cl == NULL) {
  241.             return NGX_ERROR;
  242.         }

  243. #if (NGX_SENDFILE_LIMIT)

  244.         buf = in->buf;

  245.         if (buf->in_file
  246.             && buf->file_pos < NGX_SENDFILE_LIMIT
  247.             && buf->file_last > NGX_SENDFILE_LIMIT)
  248.         {
  249.             /* split a file buf on two bufs by the sendfile limit */

  250.             b = ngx_calloc_buf(pool);
  251.             if (b == NULL) {
  252.                 return NGX_ERROR;
  253.             }

  254.             ngx_memcpy(b, buf, sizeof(ngx_buf_t));

  255.             if (ngx_buf_in_memory(buf)) {
  256.                 buf->pos += (ssize_t) (NGX_SENDFILE_LIMIT - buf->file_pos);
  257.                 b->last = buf->pos;
  258.             }

  259.             buf->file_pos = NGX_SENDFILE_LIMIT;
  260.             b->file_last = NGX_SENDFILE_LIMIT;

  261.             cl->buf = b;

  262.         } else {
  263.             cl->buf = buf;
  264.             in = in->next;
  265.         }

  266. #else
  267.         cl->buf = in->buf;
  268.         in = in->next;

  269. #endif

  270.         cl->next = NULL;
  271.         *ll = cl;
  272.         ll = &cl->next;
  273.     }

  274.     return NGX_OK;
  275. }


  276. static ngx_int_t
  277. ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)
  278. {
  279.     size_t      size;
  280.     ngx_buf_t  *in;

  281.     in = ctx->in->buf;

  282.     if (in->file == NULL || !in->file->directio) {
  283.         return NGX_DECLINED;
  284.     }

  285.     ctx->directio = 1;

  286.     size = (size_t) (in->file_pos - (in->file_pos & ~(ctx->alignment - 1)));

  287.     if (size == 0) {

  288.         if (bsize >= (off_t) ctx->bufs.size) {
  289.             return NGX_DECLINED;
  290.         }

  291.         size = (size_t) bsize;

  292.     } else {
  293.         size = (size_t) ctx->alignment - size;

  294.         if ((off_t) size > bsize) {
  295.             size = (size_t) bsize;
  296.         }
  297.     }

  298.     ctx->buf = ngx_create_temp_buf(ctx->pool, size);
  299.     if (ctx->buf == NULL) {
  300.         return NGX_ERROR;
  301.     }

  302.     /*
  303.      * we do not set ctx->buf->tag, because we do not want
  304.      * to reuse the buf via ctx->free list
  305.      */

  306. #if (NGX_HAVE_ALIGNED_DIRECTIO)
  307.     ctx->unaligned = 1;
  308. #endif

  309.     return NGX_OK;
  310. }


  311. static ngx_int_t
  312. ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)
  313. {
  314.     size_t       size;
  315.     ngx_buf_t   *b, *in;
  316.     ngx_uint_t   recycled;

  317.     in = ctx->in->buf;
  318.     size = ctx->bufs.size;
  319.     recycled = 1;

  320.     if (in->last_in_chain) {

  321.         if (bsize < (off_t) size) {

  322.             /*
  323.              * allocate a small temp buf for a small last buf
  324.              * or its small last part
  325.              */

  326.             size = (size_t) bsize;
  327.             recycled = 0;

  328.         } else if (!ctx->directio
  329.                    && ctx->bufs.num == 1
  330.                    && (bsize < (off_t) (size + size / 4)))
  331.         {
  332.             /*
  333.              * allocate a temp buf that equals to a last buf,
  334.              * if there is no directio, the last buf size is lesser
  335.              * than 1.25 of bufs.size and the temp buf is single
  336.              */

  337.             size = (size_t) bsize;
  338.             recycled = 0;
  339.         }
  340.     }

  341.     b = ngx_calloc_buf(ctx->pool);
  342.     if (b == NULL) {
  343.         return NGX_ERROR;
  344.     }

  345.     if (ctx->directio) {

  346.         /*
  347.          * allocate block aligned to a disk sector size to enable
  348.          * userland buffer direct usage conjunctly with directio
  349.          */

  350.         b->start = ngx_pmemalign(ctx->pool, size, (size_t) ctx->alignment);
  351.         if (b->start == NULL) {
  352.             return NGX_ERROR;
  353.         }

  354.     } else {
  355.         b->start = ngx_palloc(ctx->pool, size);
  356.         if (b->start == NULL) {
  357.             return NGX_ERROR;
  358.         }
  359.     }

  360.     b->pos = b->start;
  361.     b->last = b->start;
  362.     b->end = b->last + size;
  363.     b->temporary = 1;
  364.     b->tag = ctx->tag;
  365.     b->recycled = recycled;

  366.     ctx->buf = b;
  367.     ctx->allocated++;

  368.     return NGX_OK;
  369. }


  370. static ngx_int_t
  371. ngx_output_chain_copy_buf(ngx_output_chain_ctx_t *ctx)
  372. {
  373.     off_t        size;
  374.     ssize_t      n;
  375.     ngx_buf_t   *src, *dst;
  376.     ngx_uint_t   sendfile;

  377.     src = ctx->in->buf;
  378.     dst = ctx->buf;

  379.     size = ngx_buf_size(src);
  380.     size = ngx_min(size, dst->end - dst->pos);

  381.     sendfile = ctx->sendfile && !ctx->directio;

  382. #if (NGX_SENDFILE_LIMIT)

  383.     if (src->in_file && src->file_pos >= NGX_SENDFILE_LIMIT) {
  384.         sendfile = 0;
  385.     }

  386. #endif

  387.     if (ngx_buf_in_memory(src)) {
  388.         ngx_memcpy(dst->pos, src->pos, (size_t) size);
  389.         src->pos += (size_t) size;
  390.         dst->last += (size_t) size;

  391.         if (src->in_file) {

  392.             if (sendfile) {
  393.                 dst->in_file = 1;
  394.                 dst->file = src->file;
  395.                 dst->file_pos = src->file_pos;
  396.                 dst->file_last = src->file_pos + size;

  397.             } else {
  398.                 dst->in_file = 0;
  399.             }

  400.             src->file_pos += size;

  401.         } else {
  402.             dst->in_file = 0;
  403.         }

  404.         if (src->pos == src->last) {
  405.             dst->flush = src->flush;
  406.             dst->last_buf = src->last_buf;
  407.             dst->last_in_chain = src->last_in_chain;
  408.         }

  409.     } else {

  410. #if (NGX_HAVE_ALIGNED_DIRECTIO)

  411.         if (ctx->unaligned) {
  412.             if (ngx_directio_off(src->file->fd) == NGX_FILE_ERROR) {
  413.                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno,
  414.                               ngx_directio_off_n " \"%s\" failed",
  415.                               src->file->name.data);
  416.             }
  417.         }

  418. #endif

  419. #if (NGX_HAVE_FILE_AIO)
  420.         if (ctx->aio_handler) {
  421.             n = ngx_file_aio_read(src->file, dst->pos, (size_t) size,
  422.                                   src->file_pos, ctx->pool);
  423.             if (n == NGX_AGAIN) {
  424.                 ctx->aio_handler(ctx, src->file);
  425.                 return NGX_AGAIN;
  426.             }

  427.         } else
  428. #endif
  429. #if (NGX_THREADS)
  430.         if (ctx->thread_handler) {
  431.             src->file->thread_task = ctx->thread_task;
  432.             src->file->thread_handler = ctx->thread_handler;
  433.             src->file->thread_ctx = ctx->filter_ctx;

  434.             n = ngx_thread_read(src->file, dst->pos, (size_t) size,
  435.                                 src->file_pos, ctx->pool);
  436.             if (n == NGX_AGAIN) {
  437.                 ctx->thread_task = src->file->thread_task;
  438.                 return NGX_AGAIN;
  439.             }

  440.         } else
  441. #endif
  442.         {
  443.             n = ngx_read_file(src->file, dst->pos, (size_t) size,
  444.                               src->file_pos);
  445.         }

  446. #if (NGX_HAVE_ALIGNED_DIRECTIO)

  447.         if (ctx->unaligned) {
  448.             ngx_err_t  err;

  449.             err = ngx_errno;

  450.             if (ngx_directio_on(src->file->fd) == NGX_FILE_ERROR) {
  451.                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno,
  452.                               ngx_directio_on_n " \"%s\" failed",
  453.                               src->file->name.data);
  454.             }

  455.             ngx_set_errno(err);

  456.             ctx->unaligned = 0;
  457.         }

  458. #endif

  459.         if (n == NGX_ERROR) {
  460.             return (ngx_int_t) n;
  461.         }

  462.         if (n != size) {
  463.             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  464.                           ngx_read_file_n " read only %z of %O from \"%s\"",
  465.                           n, size, src->file->name.data);
  466.             return NGX_ERROR;
  467.         }

  468.         dst->last += n;

  469.         if (sendfile) {
  470.             dst->in_file = 1;
  471.             dst->file = src->file;
  472.             dst->file_pos = src->file_pos;
  473.             dst->file_last = src->file_pos + n;

  474.         } else {
  475.             dst->in_file = 0;
  476.         }

  477.         src->file_pos += n;

  478.         if (src->file_pos == src->file_last) {
  479.             dst->flush = src->flush;
  480.             dst->last_buf = src->last_buf;
  481.             dst->last_in_chain = src->last_in_chain;
  482.         }
  483.     }

  484.     return NGX_OK;
  485. }


  486. ngx_int_t
  487. ngx_chain_writer(void *data, ngx_chain_t *in)
  488. {
  489.     ngx_chain_writer_ctx_t *ctx = data;

  490.     off_t              size;
  491.     ngx_chain_t       *cl, *ln, *chain;
  492.     ngx_connection_t  *c;

  493.     c = ctx->connection;

  494.     for (size = 0; in; in = in->next) {

  495.         if (ngx_buf_size(in->buf) == 0 && !ngx_buf_special(in->buf)) {

  496.             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  497.                           "zero size buf in chain writer "
  498.                           "t:%d r:%d f:%d %p %p-%p %p %O-%O",
  499.                           in->buf->temporary,
  500.                           in->buf->recycled,
  501.                           in->buf->in_file,
  502.                           in->buf->start,
  503.                           in->buf->pos,
  504.                           in->buf->last,
  505.                           in->buf->file,
  506.                           in->buf->file_pos,
  507.                           in->buf->file_last);

  508.             ngx_debug_point();

  509.             continue;
  510.         }

  511.         if (ngx_buf_size(in->buf) < 0) {

  512.             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  513.                           "negative size buf in chain writer "
  514.                           "t:%d r:%d f:%d %p %p-%p %p %O-%O",
  515.                           in->buf->temporary,
  516.                           in->buf->recycled,
  517.                           in->buf->in_file,
  518.                           in->buf->start,
  519.                           in->buf->pos,
  520.                           in->buf->last,
  521.                           in->buf->file,
  522.                           in->buf->file_pos,
  523.                           in->buf->file_last);

  524.             ngx_debug_point();

  525.             return NGX_ERROR;
  526.         }

  527.         size += ngx_buf_size(in->buf);

  528.         ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0,
  529.                        "chain writer buf fl:%d s:%uO",
  530.                        in->buf->flush, ngx_buf_size(in->buf));

  531.         cl = ngx_alloc_chain_link(ctx->pool);
  532.         if (cl == NULL) {
  533.             return NGX_ERROR;
  534.         }

  535.         cl->buf = in->buf;
  536.         cl->next = NULL;
  537.         *ctx->last = cl;
  538.         ctx->last = &cl->next;
  539.     }

  540.     ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
  541.                    "chain writer in: %p", ctx->out);

  542.     for (cl = ctx->out; cl; cl = cl->next) {

  543.         if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {

  544.             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  545.                           "zero size buf in chain writer "
  546.                           "t:%d r:%d f:%d %p %p-%p %p %O-%O",
  547.                           cl->buf->temporary,
  548.                           cl->buf->recycled,
  549.                           cl->buf->in_file,
  550.                           cl->buf->start,
  551.                           cl->buf->pos,
  552.                           cl->buf->last,
  553.                           cl->buf->file,
  554.                           cl->buf->file_pos,
  555.                           cl->buf->file_last);

  556.             ngx_debug_point();

  557.             continue;
  558.         }

  559.         if (ngx_buf_size(cl->buf) < 0) {

  560.             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
  561.                           "negative size buf in chain writer "
  562.                           "t:%d r:%d f:%d %p %p-%p %p %O-%O",
  563.                           cl->buf->temporary,
  564.                           cl->buf->recycled,
  565.                           cl->buf->in_file,
  566.                           cl->buf->start,
  567.                           cl->buf->pos,
  568.                           cl->buf->last,
  569.                           cl->buf->file,
  570.                           cl->buf->file_pos,
  571.                           cl->buf->file_last);

  572.             ngx_debug_point();

  573.             return NGX_ERROR;
  574.         }

  575.         size += ngx_buf_size(cl->buf);
  576.     }

  577.     if (size == 0 && !c->buffered) {
  578.         return NGX_OK;
  579.     }

  580.     chain = c->send_chain(c, ctx->out, ctx->limit);

  581.     ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
  582.                    "chain writer out: %p", chain);

  583.     if (chain == NGX_CHAIN_ERROR) {
  584.         return NGX_ERROR;
  585.     }

  586.     if (chain && c->write->ready) {
  587.         ngx_post_event(c->write, &ngx_posted_next_events);
  588.     }

  589.     for (cl = ctx->out; cl && cl != chain; /* void */) {
  590.         ln = cl;
  591.         cl = cl->next;
  592.         ngx_free_chain(ctx->pool, ln);
  593.     }

  594.     ctx->out = chain;

  595.     if (ctx->out == NULL) {
  596.         ctx->last = &ctx->out;

  597.         if (!c->buffered) {
  598.             return NGX_OK;
  599.         }
  600.     }

  601.     return NGX_AGAIN;
  602. }