comparison src/os/unix/ngx_linux_sendfile_chain.c @ 6949:ff0c8e11edbc

Simplified and improved sendfile() code on Linux. The ngx_linux_sendfile() function is now used for both normal sendfile() and sendfile in threads. The ngx_linux_sendfile_thread() function was modified to use the same interface as ngx_linux_sendfile(), and is simply called from ngx_linux_sendfile() when threads are enabled. Special return code NGX_DONE is used to indicate that a thread task was posted and no further actions are needed. If number of bytes sent is less that what we were sending, we now always retry sending. This is needed for sendfile() in threads as the number of bytes we are sending might have been changed since the thread task was posted. And this is also needed for Linux 4.3+, as sendfile() might be interrupted at any time and provides no indication if it was interrupted or not (ticket #1174).
author Maxim Dounin <mdounin@mdounin.ru>
date Tue, 28 Mar 2017 18:15:39 +0300
parents f01ab2dbcfdc
children 400a3412b1e3
comparison
equal deleted inserted replaced
6948:3fb9b5eb75c0 6949:ff0c8e11edbc
18 18
19 #if !(NGX_HAVE_SENDFILE64) 19 #if !(NGX_HAVE_SENDFILE64)
20 #error sendfile64() is required! 20 #error sendfile64() is required!
21 #endif 21 #endif
22 22
23 static ngx_int_t ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, 23 static ssize_t ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file,
24 size_t size, size_t *sent); 24 size_t size);
25 static void ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log); 25 static void ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log);
26 #endif 26 #endif
27 27
28 28
29 /* 29 /*
54 ngx_buf_t *file; 54 ngx_buf_t *file;
55 ngx_event_t *wev; 55 ngx_event_t *wev;
56 ngx_chain_t *cl; 56 ngx_chain_t *cl;
57 ngx_iovec_t header; 57 ngx_iovec_t header;
58 struct iovec headers[NGX_IOVS_PREALLOCATE]; 58 struct iovec headers[NGX_IOVS_PREALLOCATE];
59 #if (NGX_THREADS)
60 ngx_int_t rc;
61 ngx_uint_t thread_handled, thread_complete;
62 #endif
63 59
64 wev = c->write; 60 wev = c->write;
65 61
66 if (!wev->ready) { 62 if (!wev->ready) {
67 return in; 63 return in;
80 header.iovs = headers; 76 header.iovs = headers;
81 header.nalloc = NGX_IOVS_PREALLOCATE; 77 header.nalloc = NGX_IOVS_PREALLOCATE;
82 78
83 for ( ;; ) { 79 for ( ;; ) {
84 prev_send = send; 80 prev_send = send;
85 #if (NGX_THREADS)
86 thread_handled = 0;
87 thread_complete = 0;
88 #endif
89 81
90 /* create the iovec and coalesce the neighbouring bufs */ 82 /* create the iovec and coalesce the neighbouring bufs */
91 83
92 cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log); 84 cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
93 85
177 ngx_debug_point(); 169 ngx_debug_point();
178 return NGX_CHAIN_ERROR; 170 return NGX_CHAIN_ERROR;
179 } 171 }
180 #endif 172 #endif
181 173
182 #if (NGX_THREADS) 174 n = ngx_linux_sendfile(c, file, file_size);
183 if (file->file->thread_handler) { 175
184 rc = ngx_linux_sendfile_thread(c, file, file_size, &sent); 176 if (n == NGX_ERROR) {
185 177 return NGX_CHAIN_ERROR;
186 switch (rc) { 178 }
187 case NGX_OK: 179
188 thread_handled = 1; 180 if (n == NGX_DONE) {
189 break; 181 /* thread task posted */
190 182 return in;
191 case NGX_DONE: 183 }
192 thread_complete = 1; 184
193 break; 185 sent = (n == NGX_AGAIN) ? 0 : n;
194
195 case NGX_AGAIN:
196 break;
197
198 default: /* NGX_ERROR */
199 return NGX_CHAIN_ERROR;
200 }
201
202 } else
203 #endif
204 {
205 n = ngx_linux_sendfile(c, file, file_size);
206
207 if (n == NGX_ERROR) {
208 return NGX_CHAIN_ERROR;
209 }
210
211 sent = (n == NGX_AGAIN) ? 0 : n;
212 }
213 186
214 } else { 187 } else {
215 n = ngx_writev(c, &header); 188 n = ngx_writev(c, &header);
216 189
217 if (n == NGX_ERROR) { 190 if (n == NGX_ERROR) {
223 196
224 c->sent += sent; 197 c->sent += sent;
225 198
226 in = ngx_chain_update_sent(in, sent); 199 in = ngx_chain_update_sent(in, sent);
227 200
228 if ((size_t) (send - prev_send) != sent) { 201 if (n == NGX_AGAIN) {
229 #if (NGX_THREADS)
230 if (thread_handled) {
231 return in;
232 }
233
234 if (thread_complete) {
235 send = prev_send + sent;
236 continue;
237 }
238 #endif
239 wev->ready = 0; 202 wev->ready = 0;
240 return in; 203 return in;
204 }
205
206 if ((size_t) (send - prev_send) != sent) {
207
208 /*
209 * sendfile() on Linux 4.3+ might be interrupted at any time,
210 * and provides no indication if it was interrupted or not,
211 * so we have to retry till an explicit EAGAIN
212 *
213 * sendfile() in threads can also report less bytes written
214 * than we are prepared to send now, since it was started in
215 * some point in the past, so we again have to retry
216 */
217
218 send = prev_send + sent;
219 continue;
241 } 220 }
242 221
243 if (send >= limit || in == NULL) { 222 if (send >= limit || in == NULL) {
244 return in; 223 return in;
245 } 224 }
256 int32_t offset; 235 int32_t offset;
257 #endif 236 #endif
258 ssize_t n; 237 ssize_t n;
259 ngx_err_t err; 238 ngx_err_t err;
260 239
240 #if (NGX_THREADS)
241
242 if (file->file->thread_handler) {
243 return ngx_linux_sendfile_thread(c, file, size);
244 }
245
246 #endif
247
261 #if (NGX_HAVE_SENDFILE64) 248 #if (NGX_HAVE_SENDFILE64)
262 offset = file->file_pos; 249 offset = file->file_pos;
263 #else 250 #else
264 offset = (int32_t) file->file_pos; 251 offset = (int32_t) file->file_pos;
265 #endif 252 #endif
322 size_t sent; 309 size_t sent;
323 ngx_err_t err; 310 ngx_err_t err;
324 } ngx_linux_sendfile_ctx_t; 311 } ngx_linux_sendfile_ctx_t;
325 312
326 313
327 static ngx_int_t 314 static ssize_t
328 ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size, 315 ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size)
329 size_t *sent)
330 { 316 {
331 ngx_event_t *wev; 317 ngx_event_t *wev;
332 ngx_thread_task_t *task; 318 ngx_thread_task_t *task;
333 ngx_linux_sendfile_ctx_t *ctx; 319 ngx_linux_sendfile_ctx_t *ctx;
334 320
354 340
355 if (task->event.complete) { 341 if (task->event.complete) {
356 task->event.complete = 0; 342 task->event.complete = 0;
357 343
358 if (ctx->err == NGX_EAGAIN) { 344 if (ctx->err == NGX_EAGAIN) {
359 *sent = 0; 345 /*
346 * if wev->complete is set, this means that a write event
347 * happened while we were waiting for the thread task, so
348 * we have to retry sending even on EAGAIN
349 */
360 350
361 if (wev->complete) { 351 if (wev->complete) {
362 return NGX_DONE; 352 return 0;
363 } 353 }
364 354
365 return NGX_AGAIN; 355 return NGX_AGAIN;
366 } 356 }
367 357
382 file->file->name.data, file->file_pos); 372 file->file->name.data, file->file_pos);
383 373
384 return NGX_ERROR; 374 return NGX_ERROR;
385 } 375 }
386 376
387 *sent = ctx->sent; 377 return ctx->sent;
388
389 if (ctx->sent == ctx->size || wev->complete) {
390 return NGX_DONE;
391 }
392
393 return NGX_AGAIN;
394 } 378 }
395 379
396 if (task->event.active && ctx->file == file) { 380 if (task->event.active && ctx->file == file) {
397 /* 381 /*
398 * tolerate duplicate calls; they can happen due to subrequests 382 * tolerate duplicate calls; they can happen due to subrequests
399 * or multiple calls of the next body filter from a filter 383 * or multiple calls of the next body filter from a filter
400 */ 384 */
401 385
402 *sent = 0; 386 return NGX_DONE;
403
404 return NGX_OK;
405 } 387 }
406 388
407 ctx->file = file; 389 ctx->file = file;
408 ctx->socket = c->fd; 390 ctx->socket = c->fd;
409 ctx->size = size; 391 ctx->size = size;
412 394
413 if (file->file->thread_handler(task, file->file) != NGX_OK) { 395 if (file->file->thread_handler(task, file->file) != NGX_OK) {
414 return NGX_ERROR; 396 return NGX_ERROR;
415 } 397 }
416 398
417 *sent = 0; 399 return NGX_DONE;
418
419 return NGX_OK;
420 } 400 }
421 401
422 402
423 static void 403 static void
424 ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log) 404 ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log)