ngx_freebsd_sendfile_chain.c revision e18a033b
1
2/*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8#include <ngx_config.h>
9#include <ngx_core.h>
10#include <ngx_event.h>
11
12
13/*
14 * Although FreeBSD sendfile() allows to pass a header and a trailer,
15 * it cannot send a header with a part of the file in one packet until
16 * FreeBSD 5.3.  Besides, over the fast ethernet connection sendfile()
17 * may send the partially filled packets, i.e. the 8 file pages may be sent
18 * as the 11 full 1460-bytes packets, then one incomplete 324-bytes packet,
19 * and then again the 11 full 1460-bytes packets.
20 *
21 * Therefore we use the TCP_NOPUSH option (similar to Linux's TCP_CORK)
22 * to postpone the sending - it not only sends a header and the first part of
23 * the file in one packet, but also sends the file pages in the full packets.
24 *
25 * But until FreeBSD 4.5 turning TCP_NOPUSH off does not flush a pending
26 * data that less than MSS, so that data may be sent with 5 second delay.
27 * So we do not use TCP_NOPUSH on FreeBSD prior to 4.5, although it can be used
28 * for non-keepalive HTTP connections.
29 */
30
31
32ngx_chain_t *
33ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
34{
35    int               rc, flags;
36    off_t             send, prev_send, sent;
37    size_t            file_size;
38    ssize_t           n;
39    ngx_uint_t        eintr, eagain;
40    ngx_err_t         err;
41    ngx_buf_t        *file;
42    ngx_event_t      *wev;
43    ngx_chain_t      *cl;
44    ngx_iovec_t       header, trailer;
45    struct sf_hdtr    hdtr;
46    struct iovec      headers[NGX_IOVS_PREALLOCATE];
47    struct iovec      trailers[NGX_IOVS_PREALLOCATE];
48#if (NGX_HAVE_AIO_SENDFILE)
49    ngx_uint_t        ebusy;
50    ngx_event_aio_t  *aio;
51#endif
52
53    wev = c->write;
54
55    if (!wev->ready) {
56        return in;
57    }
58
59#if (NGX_HAVE_KQUEUE)
60
61    if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
62        (void) ngx_connection_error(c, wev->kq_errno,
63                               "kevent() reported about an closed connection");
64        wev->error = 1;
65        return NGX_CHAIN_ERROR;
66    }
67
68#endif
69
70    /* the maximum limit size is the maximum size_t value - the page size */
71
72    if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
73        limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
74    }
75
76    send = 0;
77    eagain = 0;
78    flags = 0;
79
80#if (NGX_HAVE_AIO_SENDFILE && NGX_SUPPRESS_WARN)
81    aio = NULL;
82    file = NULL;
83#endif
84
85    header.iovs = headers;
86    header.nalloc = NGX_IOVS_PREALLOCATE;
87
88    trailer.iovs = trailers;
89    trailer.nalloc = NGX_IOVS_PREALLOCATE;
90
91    for ( ;; ) {
92        eintr = 0;
93#if (NGX_HAVE_AIO_SENDFILE)
94        ebusy = 0;
95#endif
96        prev_send = send;
97
98        /* create the header iovec and coalesce the neighbouring bufs */
99
100        cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
101
102        if (cl == NGX_CHAIN_ERROR) {
103            return NGX_CHAIN_ERROR;
104        }
105
106        send += header.size;
107
108        if (cl && cl->buf->in_file && send < limit) {
109            file = cl->buf;
110
111            /* coalesce the neighbouring file bufs */
112
113            file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send);
114
115            send += file_size;
116
117            if (send < limit) {
118
119                /*
120                 * create the trailer iovec and coalesce the neighbouring bufs
121                 */
122
123                cl = ngx_output_chain_to_iovec(&trailer, cl, limit - send,
124                                               c->log);
125                if (cl == NGX_CHAIN_ERROR) {
126                    return NGX_CHAIN_ERROR;
127                }
128
129                send += trailer.size;
130
131            } else {
132                trailer.count = 0;
133            }
134
135            if (ngx_freebsd_use_tcp_nopush
136                && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET)
137            {
138                if (ngx_tcp_nopush(c->fd) == NGX_ERROR) {
139                    err = ngx_socket_errno;
140
141                    /*
142                     * there is a tiny chance to be interrupted, however,
143                     * we continue a processing without the TCP_NOPUSH
144                     */
145
146                    if (err != NGX_EINTR) {
147                        wev->error = 1;
148                        (void) ngx_connection_error(c, err,
149                                                    ngx_tcp_nopush_n " failed");
150                        return NGX_CHAIN_ERROR;
151                    }
152
153                } else {
154                    c->tcp_nopush = NGX_TCP_NOPUSH_SET;
155
156                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
157                                   "tcp_nopush");
158                }
159            }
160
161            /*
162             * sendfile() does unneeded work if sf_hdtr's count is 0,
163             * but corresponding pointer is not NULL
164             */
165
166            hdtr.headers = header.count ? header.iovs : NULL;
167            hdtr.hdr_cnt = header.count;
168            hdtr.trailers = trailer.count ? trailer.iovs : NULL;
169            hdtr.trl_cnt = trailer.count;
170
171            /*
172             * the "nbytes bug" of the old sendfile() syscall:
173             * http://bugs.freebsd.org/33771
174             */
175
176            if (!ngx_freebsd_sendfile_nbytes_bug) {
177                header.size = 0;
178            }
179
180            sent = 0;
181
182#if (NGX_HAVE_AIO_SENDFILE)
183            aio = file->file->aio;
184            flags = (aio && aio->preload_handler) ? SF_NODISKIO : 0;
185#endif
186
187            rc = sendfile(file->file->fd, c->fd, file->file_pos,
188                          file_size + header.size, &hdtr, &sent, flags);
189
190            if (rc == -1) {
191                err = ngx_errno;
192
193                switch (err) {
194                case NGX_EAGAIN:
195                    eagain = 1;
196                    break;
197
198                case NGX_EINTR:
199                    eintr = 1;
200                    break;
201
202#if (NGX_HAVE_AIO_SENDFILE)
203                case NGX_EBUSY:
204                    ebusy = 1;
205                    break;
206#endif
207
208                default:
209                    wev->error = 1;
210                    (void) ngx_connection_error(c, err, "sendfile() failed");
211                    return NGX_CHAIN_ERROR;
212                }
213
214                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
215                               "sendfile() sent only %O bytes", sent);
216
217            /*
218             * sendfile() in FreeBSD 3.x-4.x may return value >= 0
219             * on success, although only 0 is documented
220             */
221
222            } else if (rc >= 0 && sent == 0) {
223
224                /*
225                 * if rc is OK and sent equal to zero, then someone
226                 * has truncated the file, so the offset became beyond
227                 * the end of the file
228                 */
229
230                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
231                         "sendfile() reported that \"%s\" was truncated at %O",
232                         file->file->name.data, file->file_pos);
233
234                return NGX_CHAIN_ERROR;
235            }
236
237            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
238                           "sendfile: %d, @%O %O:%uz",
239                           rc, file->file_pos, sent, file_size + header.size);
240
241        } else {
242            n = ngx_writev(c, &header);
243
244            if (n == NGX_ERROR) {
245                return NGX_CHAIN_ERROR;
246            }
247
248            sent = (n == NGX_AGAIN) ? 0 : n;
249        }
250
251        c->sent += sent;
252
253        in = ngx_chain_update_sent(in, sent);
254
255#if (NGX_HAVE_AIO_SENDFILE)
256
257        if (ebusy) {
258            if (aio->event.active) {
259                /*
260                 * tolerate duplicate calls; they can happen due to subrequests
261                 * or multiple calls of the next body filter from a filter
262                 */
263
264                if (sent) {
265                    c->busy_count = 0;
266                }
267
268                return in;
269            }
270
271            if (sent == 0) {
272                c->busy_count++;
273
274                if (c->busy_count > 2) {
275                    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
276                                  "sendfile(%V) returned busy again",
277                                  &file->file->name);
278
279                    c->busy_count = 0;
280                    aio->preload_handler = NULL;
281
282                    send = prev_send;
283                    continue;
284                }
285
286            } else {
287                c->busy_count = 0;
288            }
289
290            n = aio->preload_handler(file);
291
292            if (n > 0) {
293                send = prev_send + sent;
294                continue;
295            }
296
297            return in;
298        }
299
300        if (flags == SF_NODISKIO) {
301            c->busy_count = 0;
302        }
303
304#endif
305
306        if (eagain) {
307
308            /*
309             * sendfile() may return EAGAIN, even if it has sent a whole file
310             * part, it indicates that the successive sendfile() call would
311             * return EAGAIN right away and would not send anything.
312             * We use it as a hint.
313             */
314
315            wev->ready = 0;
316            return in;
317        }
318
319        if (eintr) {
320            send = prev_send + sent;
321            continue;
322        }
323
324        if (send - prev_send != sent) {
325            wev->ready = 0;
326            return in;
327        }
328
329        if (send >= limit || in == NULL) {
330            return in;
331        }
332    }
333}
334