From b97f199d4bb086c965ab33589de57a89b5794649 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 25 Feb 2010 23:54:31 +0100 Subject: [PATCH] [MEDIUM] http: don't use trash to realign large buffers The trash buffer may now be smaller than a buffer because we can tune it at run time. This causes a risk when we're trying to use it as a temporary buffer to realign unaligned requests, because we may have to put up to a full buffer into it. Instead of doing a double copy, we're now relying on an open-coded bouncing copy algorithm. The principle is that we move one byte at a time to its final place, and if that place also holds a byte, then we move it too, and so on. We finish when we've moved all the buffer. It limits the number of memory accesses, but since it proceeds one byte at a time and with random walk, it's not cache friendly and should be slower than a double copy. However, it's only used in extreme situations and the difference will not be noticeable. It has been extensively tested and works reliably. --- include/proto/buffers.h | 4 ++- src/buffers.c | 64 ++++++++++++++++++++++++++++++++++++++++- src/proto_http.c | 15 +++------- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/include/proto/buffers.h b/include/proto/buffers.h index e046f3927..f174b8770 100644 --- a/include/proto/buffers.h +++ b/include/proto/buffers.h @@ -2,7 +2,7 @@ * include/proto/buffers.h * Buffer management definitions, macros and inline functions. * - * Copyright (C) 2000-2009 Willy Tarreau - w@1wt.eu + * Copyright (C) 2000-2010 Willy Tarreau - w@1wt.eu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -429,6 +429,8 @@ int buffer_replace(struct buffer *b, char *pos, char *end, const char *str); int buffer_replace2(struct buffer *b, char *pos, char *end, const char *str, int len); int buffer_insert_line2(struct buffer *b, char *pos, const char *str, int len); void buffer_dump(FILE *o, struct buffer *b, int from, int to); +void buffer_bounce_realign(struct buffer *buf); + /* writes the chunk to buffer . Returns -1 in case of success, diff --git a/src/buffers.c b/src/buffers.c index 5f3cc2c44..79cc45bf5 100644 --- a/src/buffers.c +++ b/src/buffers.c @@ -1,7 +1,7 @@ /* * Buffer management functions. * - * Copyright 2000-2009 Willy Tarreau + * Copyright 2000-2010 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -298,6 +298,68 @@ int buffer_insert_line2(struct buffer *b, char *pos, const char *str, int len) } +/* Realigns a possibly non-contiguous buffer by bouncing bytes from source to + * destination. It does not use any intermediate buffer and does the move in + * place, though it will be slower than a simple memmove() on contiguous data, + * so it's desirable to use it only on non-contiguous buffers. No pointers are + * changed, the caller is responsible for that. + */ +void buffer_bounce_realign(struct buffer *buf) +{ + int advance, to_move; + char *from, *to; + + advance = buf->data + buf->size - buf->w; + if (!advance) + return; + + from = buf->w; + to_move = buf->l; + while (to_move) { + char last, save; + + last = *from; + to = from + advance; + if (to >= buf->data + buf->size) + to -= buf->size; + + while (1) { + save = *to; + *to = last; + last = save; + to_move--; + if (!to_move) + break; + + /* check if we went back home after rotating a number of bytes */ + if (to == from) + break; + + /* if we ended up in the empty area, let's walk to next place. The + * empty area is either between buf->r and from or before from or + * after buf->r. + */ + if (from > buf->r) { + if (to >= buf->r && to < from) + break; + } else if (from < buf->r) { + if (to < from || to >= buf->r) + break; + } + + /* we have overwritten a byte of the original set, let's move it */ + to += advance; + if (to >= buf->data + buf->size) + to -= buf->size; + } + + from++; + if (from >= buf->data + buf->size) + from -= buf->size; + } +} + + /* * Does an snprintf() at the end of chunk , respecting the limit of * at most chk->size chars. If the chk->len is over, nothing is added. Returns diff --git a/src/proto_http.c b/src/proto_http.c index e502bffe1..8816699ac 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2262,18 +2262,11 @@ void http_buffer_heavy_realign(struct buffer *buf, struct http_msg *msg) * - the buffer is in two blocks, we move it via the trash */ if (buf->l) { - int block1 = buf->l; - int block2 = 0; - if (buf->r <= buf->w) { + if (buf->r <= buf->w) /* non-contiguous block */ - block1 = buf->data + buf->size - buf->w; - block2 = buf->r - buf->data; - } - if (block2) - memcpy(trash, buf->data, block2); - memmove(buf->data, buf->w, block1); - if (block2) - memcpy(buf->data + block1, trash, block2); + buffer_bounce_realign(buf); + else + memmove(buf->data, buf->w, buf->l); } /* adjust all known pointers */