src/os/unix/ngx_linux_sendfile_chain.c - nginx source code

Data types defined

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. static ssize_t ngx_linux_sendfile(ngx_connection_t *c, ngx_buf_t *file,
  9.     size_t size);

  10. #if (NGX_THREADS)
  11. #include <ngx_thread_pool.h>

  12. #if !(NGX_HAVE_SENDFILE64)
  13. #error sendfile64() is required!
  14. #endif

  15. static ssize_t ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file,
  16.     size_t size);
  17. static void ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log);
  18. #endif


  19. /*
  20. * On Linux up to 2.4.21 sendfile() (syscall #187) works with 32-bit
  21. * offsets only, and the including <sys/sendfile.h> breaks the compiling,
  22. * if off_t is 64 bit wide.  So we use own sendfile() definition, where offset
  23. * parameter is int32_t, and use sendfile() for the file parts below 2G only,
  24. * see src/os/unix/ngx_linux_config.h
  25. *
  26. * Linux 2.4.21 has the new sendfile64() syscall #239.
  27. *
  28. * On Linux up to 2.6.16 sendfile() does not allow to pass the count parameter
  29. * more than 2G-1 bytes even on 64-bit platforms: it returns EINVAL,
  30. * so we limit it to 2G-1 bytes.
  31. *
  32. * On Linux 2.6.16 and later, sendfile() silently limits the count parameter
  33. * to 2G minus the page size, even on 64-bit platforms.
  34. */

  35. #define NGX_SENDFILE_MAXSIZE  2147483647L


  36. ngx_chain_t *
  37. ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
  38. {
  39.     int            tcp_nodelay;
  40.     off_t          send, prev_send;
  41.     size_t         file_size, sent;
  42.     ssize_t        n;
  43.     ngx_err_t      err;
  44.     ngx_buf_t     *file;
  45.     ngx_event_t   *wev;
  46.     ngx_chain_t   *cl;
  47.     ngx_iovec_t    header;
  48.     struct iovec   headers[NGX_IOVS_PREALLOCATE];

  49.     wev = c->write;

  50.     if (!wev->ready) {
  51.         return in;
  52.     }


  53.     /* the maximum limit size is 2G-1 - the page size */

  54.     if (limit == 0 || limit > (off_t) (NGX_SENDFILE_MAXSIZE - ngx_pagesize)) {
  55.         limit = NGX_SENDFILE_MAXSIZE - ngx_pagesize;
  56.     }


  57.     send = 0;

  58.     header.iovs = headers;
  59.     header.nalloc = NGX_IOVS_PREALLOCATE;

  60.     for ( ;; ) {
  61.         prev_send = send;

  62.         /* create the iovec and coalesce the neighbouring bufs */

  63.         cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);

  64.         if (cl == NGX_CHAIN_ERROR) {
  65.             return NGX_CHAIN_ERROR;
  66.         }

  67.         send += header.size;

  68.         /* set TCP_CORK if there is a header before a file */

  69.         if (c->tcp_nopush == NGX_TCP_NOPUSH_UNSET
  70.             && header.count != 0
  71.             && cl
  72.             && cl->buf->in_file)
  73.         {
  74.             /* the TCP_CORK and TCP_NODELAY are mutually exclusive */

  75.             if (c->tcp_nodelay == NGX_TCP_NODELAY_SET) {

  76.                 tcp_nodelay = 0;

  77.                 if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
  78.                                (const void *) &tcp_nodelay, sizeof(int)) == -1)
  79.                 {
  80.                     err = ngx_socket_errno;

  81.                     /*
  82.                      * there is a tiny chance to be interrupted, however,
  83.                      * we continue a processing with the TCP_NODELAY
  84.                      * and without the TCP_CORK
  85.                      */

  86.                     if (err != NGX_EINTR) {
  87.                         wev->error = 1;
  88.                         ngx_connection_error(c, err,
  89.                                              "setsockopt(TCP_NODELAY) failed");
  90.                         return NGX_CHAIN_ERROR;
  91.                     }

  92.                 } else {
  93.                     c->tcp_nodelay = NGX_TCP_NODELAY_UNSET;

  94.                     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
  95.                                    "no tcp_nodelay");
  96.                 }
  97.             }

  98.             if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) {

  99.                 if (ngx_tcp_nopush(c->fd) == -1) {
  100.                     err = ngx_socket_errno;

  101.                     /*
  102.                      * there is a tiny chance to be interrupted, however,
  103.                      * we continue a processing without the TCP_CORK
  104.                      */

  105.                     if (err != NGX_EINTR) {
  106.                         wev->error = 1;
  107.                         ngx_connection_error(c, err,
  108.                                              ngx_tcp_nopush_n " failed");
  109.                         return NGX_CHAIN_ERROR;
  110.                     }

  111.                 } else {
  112.                     c->tcp_nopush = NGX_TCP_NOPUSH_SET;

  113.                     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
  114.                                    "tcp_nopush");
  115.                 }
  116.             }
  117.         }

  118.         /* get the file buf */

  119.         if (header.count == 0 && cl && cl->buf->in_file && send < limit) {
  120.             file = cl->buf;

  121.             /* coalesce the neighbouring file bufs */

  122.             file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send);

  123.             send += file_size;
  124. #if 1
  125.             if (file_size == 0) {
  126.                 ngx_debug_point();
  127.                 return NGX_CHAIN_ERROR;
  128.             }
  129. #endif

  130.             n = ngx_linux_sendfile(c, file, file_size);

  131.             if (n == NGX_ERROR) {
  132.                 return NGX_CHAIN_ERROR;
  133.             }

  134.             if (n == NGX_DONE) {
  135.                 /* thread task posted */
  136.                 return in;
  137.             }

  138.             sent = (n == NGX_AGAIN) ? 0 : n;

  139.         } else {
  140.             n = ngx_writev(c, &header);

  141.             if (n == NGX_ERROR) {
  142.                 return NGX_CHAIN_ERROR;
  143.             }

  144.             sent = (n == NGX_AGAIN) ? 0 : n;
  145.         }

  146.         c->sent += sent;

  147.         in = ngx_chain_update_sent(in, sent);

  148.         if (n == NGX_AGAIN) {
  149.             wev->ready = 0;
  150.             return in;
  151.         }

  152.         if ((size_t) (send - prev_send) != sent) {

  153.             /*
  154.              * sendfile() on Linux 4.3+ might be interrupted at any time,
  155.              * and provides no indication if it was interrupted or not,
  156.              * so we have to retry till an explicit EAGAIN
  157.              *
  158.              * sendfile() in threads can also report less bytes written
  159.              * than we are prepared to send now, since it was started in
  160.              * some point in the past, so we again have to retry
  161.              */

  162.             send = prev_send + sent;
  163.         }

  164.         if (send >= limit || in == NULL) {
  165.             return in;
  166.         }
  167.     }
  168. }


  169. static ssize_t
  170. ngx_linux_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size)
  171. {
  172. #if (NGX_HAVE_SENDFILE64)
  173.     off_t      offset;
  174. #else
  175.     int32_t    offset;
  176. #endif
  177.     ssize_t    n;
  178.     ngx_err_t  err;

  179. #if (NGX_THREADS)

  180.     if (file->file->thread_handler) {
  181.         return ngx_linux_sendfile_thread(c, file, size);
  182.     }

  183. #endif

  184. #if (NGX_HAVE_SENDFILE64)
  185.     offset = file->file_pos;
  186. #else
  187.     offset = (int32_t) file->file_pos;
  188. #endif

  189. eintr:

  190.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  191.                    "sendfile: @%O %uz", file->file_pos, size);

  192.     n = sendfile(c->fd, file->file->fd, &offset, size);

  193.     if (n == -1) {
  194.         err = ngx_errno;

  195.         switch (err) {
  196.         case NGX_EAGAIN:
  197.             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
  198.                            "sendfile() is not ready");
  199.             return NGX_AGAIN;

  200.         case NGX_EINTR:
  201.             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
  202.                            "sendfile() was interrupted");
  203.             goto eintr;

  204.         default:
  205.             c->write->error = 1;
  206.             ngx_connection_error(c, err, "sendfile() failed");
  207.             return NGX_ERROR;
  208.         }
  209.     }

  210.     if (n == 0) {
  211.         /*
  212.          * if sendfile returns zero, then someone has truncated the file,
  213.          * so the offset became beyond the end of the file
  214.          */

  215.         ngx_log_error(NGX_LOG_ALERT, c->log, 0,
  216.                       "sendfile() reported that \"%s\" was truncated at %O",
  217.                       file->file->name.data, file->file_pos);

  218.         return NGX_ERROR;
  219.     }

  220.     ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "sendfile: %z of %uz @%O",
  221.                    n, size, file->file_pos);

  222.     return n;
  223. }


  224. #if (NGX_THREADS)

  225. typedef struct {
  226.     ngx_buf_t     *file;
  227.     ngx_socket_t   socket;
  228.     size_t         size;

  229.     size_t         sent;
  230.     ngx_err_t      err;
  231. } ngx_linux_sendfile_ctx_t;


  232. static ssize_t
  233. ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size)
  234. {
  235.     ngx_event_t               *wev;
  236.     ngx_thread_task_t         *task;
  237.     ngx_linux_sendfile_ctx_t  *ctx;

  238.     ngx_log_debug3(NGX_LOG_DEBUG_CORE, c->log, 0,
  239.                    "linux sendfile thread: %d, %uz, %O",
  240.                    file->file->fd, size, file->file_pos);

  241.     task = c->sendfile_task;

  242.     if (task == NULL) {
  243.         task = ngx_thread_task_alloc(c->pool, sizeof(ngx_linux_sendfile_ctx_t));
  244.         if (task == NULL) {
  245.             return NGX_ERROR;
  246.         }

  247.         task->event.log = c->log;
  248.         task->handler = ngx_linux_sendfile_thread_handler;

  249.         c->sendfile_task = task;
  250.     }

  251.     ctx = task->ctx;
  252.     wev = c->write;

  253.     if (task->event.complete) {
  254.         task->event.complete = 0;

  255.         if (ctx->err == NGX_EAGAIN) {
  256.             /*
  257.              * if wev->complete is set, this means that a write event
  258.              * happened while we were waiting for the thread task, so
  259.              * we have to retry sending even on EAGAIN
  260.              */

  261.             if (wev->complete) {
  262.                 return 0;
  263.             }

  264.             return NGX_AGAIN;
  265.         }

  266.         if (ctx->err) {
  267.             wev->error = 1;
  268.             ngx_connection_error(c, ctx->err, "sendfile() failed");
  269.             return NGX_ERROR;
  270.         }

  271.         if (ctx->sent == 0) {
  272.             /*
  273.              * if sendfile returns zero, then someone has truncated the file,
  274.              * so the offset became beyond the end of the file
  275.              */

  276.             ngx_log_error(NGX_LOG_ALERT, c->log, 0,
  277.                           "sendfile() reported that \"%s\" was truncated at %O",
  278.                           file->file->name.data, file->file_pos);

  279.             return NGX_ERROR;
  280.         }

  281.         return ctx->sent;
  282.     }

  283.     ctx->file = file;
  284.     ctx->socket = c->fd;
  285.     ctx->size = size;

  286.     wev->complete = 0;

  287.     if (file->file->thread_handler(task, file->file) != NGX_OK) {
  288.         return NGX_ERROR;
  289.     }

  290.     return NGX_DONE;
  291. }


  292. static void
  293. ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log)
  294. {
  295.     ngx_linux_sendfile_ctx_t *ctx = data;

  296.     off_t       offset;
  297.     ssize_t     n;
  298.     ngx_buf_t  *file;

  299.     ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "linux sendfile thread handler");

  300.     file = ctx->file;
  301.     offset = file->file_pos;

  302. again:

  303.     n = sendfile(ctx->socket, file->file->fd, &offset, ctx->size);

  304.     if (n == -1) {
  305.         ctx->err = ngx_errno;

  306.     } else {
  307.         ctx->sent = n;
  308.         ctx->err = 0;
  309.     }

  310. #if 0
  311.     ngx_time_update();
  312. #endif

  313.     ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
  314.                    "sendfile: %z (err: %d) of %uz @%O",
  315.                    n, ctx->err, ctx->size, file->file_pos);

  316.     if (ctx->err == NGX_EINTR) {
  317.         goto again;
  318.     }
  319. }

  320. #endif /* NGX_THREADS */