From 4b517ca93aaaead8aa6143aa2836dc96417653c6 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 25 Nov 2011 20:33:58 +0100 Subject: MEDIUM: buffers: add some new primitives and rework existing ones A number of primitives were missing for buffer management, and some of them were particularly awkward to use. Specifically, the functions used to compute free space could not always be used depending what was wrapping in the buffers. Some documentation has been added about how the buffers work and their properties. Some functions are still missing such as a buffer replacement which would support wrapping buffers. --- include/proto/buffers.h | 295 ++++++++++++++++++++++++++++++----------------- include/types/buffers.h | 28 ++++- src/buffers.c | 2 +- 3 files changed, 213 insertions(+), 112 deletions(-) diff --git a/include/proto/buffers.h b/include/proto/buffers.h index af470ba..b241a56 100644 --- a/include/proto/buffers.h +++ b/include/proto/buffers.h @@ -65,17 +65,199 @@ static inline void buffer_init(struct buffer *buf) buf->r = buf->lr = buf->w = buf->data; } +/*****************************************************************/ +/* These functions are used to compute various buffer area sizes */ +/*****************************************************************/ + +/* Return the number of reserved bytes in the buffer, which ensures that once + * all pending data are forwarded, the buffer still has global.tune.maxrewrite + * bytes free. The result is between 0 and global.maxrewrite, which is itself + * smaller than any buf->size. + */ +static inline int buffer_reserved(const struct buffer *buf) +{ + int ret = global.tune.maxrewrite - buf->to_forward - buf->send_max; + + if (buf->to_forward == BUF_INFINITE_FORWARD) + return 0; + if (ret <= 0) + return 0; + return ret; +} + /* Return the max number of bytes the buffer can contain so that once all the * pending bytes are forwarded, the buffer still has global.tune.maxrewrite * bytes free. The result sits between buf->size - maxrewrite and buf->size. */ -static inline int buffer_max_len(struct buffer *buf) +static inline int buffer_max_len(const struct buffer *buf) +{ + return buf->size - buffer_reserved(buf); +} + +/* Return the maximum amount of bytes that can be written into the buffer, + * including reserved space which may be overwritten. + */ +static inline int buffer_total_space(const struct buffer *buf) +{ + return buf->size - buf->l; +} + +/* Return the maximum amount of bytes that can be written into the buffer, + * excluding the reserved space, which is preserved. + */ +static inline int buffer_total_space_res(const struct buffer *buf) +{ + return buffer_max_len(buf) - buf->l; +} + +/* Returns the number of contiguous bytes between and +, + * and enforces a limit on buf->data + buf->size. must be within the + * buffer. + */ +static inline int buffer_contig_area(const struct buffer *buf, const char *start, int count) +{ + if (count > buf->data - start + buf->size) + count = buf->data - start + buf->size; + return count; +} + +/* Return the amount of bytes that can be written into the buffer at once, + * including reserved space which may be overwritten. This version is optimized + * to reduce the amount of operations but it's not easy to understand as it is. + * Drawing a buffer with wrapping data on a paper helps a lot. + */ +static inline int buffer_contig_space(struct buffer *buf) +{ + int space_from_end = buf->l - (buf->r - buf->data); + if (space_from_end < 0) /* data does not wrap */ + space_from_end = buf->r - buf->data; + return buf->size - space_from_end; +} + +/* Return the amount of bytes that can be written into the buffer at once, + * excluding reserved space, which is preserved. Same comment as above for + * the optimization leading to hardly understandable code. + */ +static inline int buffer_contig_space_res(struct buffer *buf) +{ + /* Proceed differently if the buffer is full, partially used or empty. + * The hard situation is when it's partially used and either data or + * reserved space wraps at the end. + */ + int res = buffer_reserved(buf); + int spare = buf->size - res; + + if (buf->l >= spare) + spare = 0; + else if (buf->l) { + spare = buf->w - res - buf->r; + if (spare <= 0) + spare += buf->size; + spare = buffer_contig_area(buf, buf->r, spare); + } + return spare; +} + +/* Same as above, but lets the caller pass the pre-computed value of + * buffer_reserved() in if it already knows it, to save some + * computations. + */ +static inline int buffer_contig_space_with_res(struct buffer *buf, int res) +{ + /* Proceed differently if the buffer is full, partially used or empty. + * The hard situation is when it's partially used and either data or + * reserved space wraps at the end. + */ + int spare = buf->size - res; + + if (buf->l >= spare) + spare = 0; + else if (buf->l) { + spare = buf->w - res - buf->r; + if (spare <= 0) + spare += buf->size; + spare = buffer_contig_area(buf, buf->r, spare); + } + return spare; +} + +/* Normalizes a pointer which is supposed to be relative to the beginning of a + * buffer, so that wrapping is correctly handled. The intent is to use this + * when increasing a pointer. Note that the wrapping test is only performed + * once, so the original pointer must be between ->data and ->data+2*size - 1, + * otherwise an invalid pointer might be returned. + */ +static inline char *buffer_pointer(const struct buffer *buf, char *ptr) { - if (buf->to_forward == BUF_INFINITE_FORWARD || - buf->to_forward + buf->send_max >= global.tune.maxrewrite) - return buf->size; + if (ptr - buf->size >= buf->data) + ptr -= buf->size; + return ptr; +} + +/* Returns the distance between two pointers, taking into account the ability + * to wrap around the buffer's end. + */ +static inline int buffer_count(const struct buffer *buf, char *from, char *to) +{ + int count = to - from; + if (count < 0) + count += buf->size; + return count; +} + +/* returns the amount of pending bytes in the buffer. It is the amount of bytes + * that is not scheduled to be sent. + */ +static inline int buffer_pending(const struct buffer *buf) +{ + return buf->l - buf->send_max; +} + +/* Returns the size of the working area which the caller knows ends at . + * If equals buf->r (modulo size), then it means that the free area which + * follows is part of the working area. Otherwise, the working area stops at + * . It always starts at buf->w+send_max. The work area includes the + * reserved area. + */ +static inline int buffer_work_area(const struct buffer *buf, char *end) +{ + end = buffer_pointer(buf, end); + if (end == buf->r) /* pointer exactly at end, lets push forwards */ + end = buf->w; + return buffer_count(buf, buffer_pointer(buf, buf->w + buf->send_max), end); +} + +/* Return 1 if the buffer has less than 1/4 of its capacity free, otherwise 0 */ +static inline int buffer_almost_full(const struct buffer *buf) +{ + if (buffer_total_space(buf) < buf->size / 4) + return 1; + return 0; +} + +/* + * Return the max amount of bytes that can be read from the buffer at once. + * Note that this may be lower than the actual buffer length when the data + * wrap after the end, so it's preferable to call this function again after + * reading. Also note that this function respects the send_max limit. + */ +static inline int buffer_contig_data(struct buffer *buf) +{ + int ret; + + if (!buf->send_max || !buf->l) + return 0; + + if (buf->r > buf->w) + ret = buf->r - buf->w; else - return buf->size - global.tune.maxrewrite + buf->to_forward + buf->send_max; + ret = buf->data + buf->size - buf->w; + + /* limit the amount of outgoing data if required */ + if (ret > buf->send_max) + ret = buf->send_max; + + return ret; } /* Returns true if the buffer's input is already closed */ @@ -254,17 +436,6 @@ static inline void buffer_dont_read(struct buffer *buf) buf->flags |= BF_DONT_READ; } -/* returns the maximum number of bytes writable at once in this buffer */ -static inline int buffer_max(const struct buffer *buf) -{ - if (buf->l == buf->size) - return 0; - else if (buf->r >= buf->w) - return buf->data + buf->size - buf->r; - else - return buf->w - buf->r; -} - /* * Tries to realign the given buffer, and returns how many bytes can be written * there at once without overwriting anything. @@ -275,97 +446,7 @@ static inline int buffer_realign(struct buffer *buf) /* let's realign the buffer to optimize I/O */ buf->r = buf->w = buf->lr = buf->data; } - return buffer_max(buf); -} - -/* - * Return the maximum amount of bytes that can be written into the buffer in - * one call to buffer_put_*(). - */ -static inline int buffer_free_space(struct buffer *buf) -{ - return buffer_max_len(buf) - buf->l; -} - -/* - * Return the max amount of bytes that can be stuffed into the buffer at once. - * Note that this may be lower than the actual buffer size when the free space - * wraps after the end, so it's preferable to call this function again after - * writing. Also note that this function respects max_len. - */ -static inline int buffer_contig_space(struct buffer *buf) -{ - int ret; - - if (buf->l == 0) { - buf->r = buf->w = buf->lr = buf->data; - ret = buffer_max_len(buf); - } - else if (buf->r > buf->w) { - ret = buf->data + buffer_max_len(buf) - buf->r; - } - else { - ret = buf->w - buf->r; - if (ret > buffer_max_len(buf)) - ret = buffer_max_len(buf); - } - return ret; -} - -/* - * Same as above but the caller may pass the value of buffer_max_len(buf) if it - * knows it, thus avoiding some calculations. - */ -static inline int buffer_contig_space_with_len(struct buffer *buf, int maxlen) -{ - int ret; - - if (buf->l == 0) { - buf->r = buf->w = buf->lr = buf->data; - ret = maxlen; - } - else if (buf->r > buf->w) { - ret = buf->data + maxlen - buf->r; - } - else { - ret = buf->w - buf->r; - if (ret > maxlen) - ret = maxlen; - } - return ret; -} - -/* Return 1 if the buffer has less than 1/4 of its capacity free, otherwise 0 */ -static inline int buffer_almost_full(struct buffer *buf) -{ - if (buffer_free_space(buf) < buf->size / 4) - return 1; - return 0; -} - -/* - * Return the max amount of bytes that can be read from the buffer at once. - * Note that this may be lower than the actual buffer length when the data - * wrap after the end, so it's preferable to call this function again after - * reading. Also note that this function respects the send_max limit. - */ -static inline int buffer_contig_data(struct buffer *buf) -{ - int ret; - - if (!buf->send_max || !buf->l) - return 0; - - if (buf->r > buf->w) - ret = buf->r - buf->w; - else - ret = buf->data + buf->size - buf->w; - - /* limit the amount of outgoing data if required */ - if (ret > buf->send_max) - ret = buf->send_max; - - return ret; + return buffer_contig_space(buf); } /* diff --git a/include/types/buffers.h b/include/types/buffers.h index 39a1192..42e2a56 100644 --- a/include/types/buffers.h +++ b/include/types/buffers.h @@ -209,7 +209,7 @@ struct buffer { In order not to mix data streams, the producer may only feed the invisible data with data to forward, and only when the visible buffer is empty. The - consumer may not always be able to feed the invisible buffer due to platform + producer may not always be able to feed the invisible buffer due to platform limitations (lack of kernel support). Conversely, the consumer must always take data from the invisible data first @@ -226,9 +226,13 @@ struct buffer { ->send_max. The ->to_forward parameter indicates how many bytes may be fed into either data buffer without waking the parent up. The special value BUF_INFINITE_FORWARD is never decreased nor increased. The ->send_max - parameter says how many bytes may be read from the visible buffer. Thus it - may never exceed ->l. This parameter is updated by any buffer_write() as - well as any data forwarded through the visible buffer. + parameter says how many bytes may be consumed from the visible buffer. Thus + it may never exceed ->l. This parameter is updated by any buffer_write() as + well as any data forwarded through the visible buffer. Since the ->to_forward + attribute applies to data after ->w+send_max, an analyser will not see a + buffer which has a non-null to_forward with send_max < l. A producer is + responsible for raising ->send_max by min(to_forward, l-send_max) when it + injects data into the buffer. The consumer is responsible for decreasing ->send_max when it sends data from the visible buffer, and ->pipe->data when it sends data from the @@ -264,6 +268,22 @@ struct buffer { Note that this also means that anyone touching ->to_forward must also take care of updating the BF_FULL flag. For this reason, it's really advised to use buffer_forward() only. + + A buffer may contain up to 5 areas : + - the data waiting to be sent. These data are located between ->w and + ->w+send_max ; + - the data to process and possibly transform. These data start at + ->w+send_max and may be up to r-w bytes long. Generally ->lr remains in + this area ; + - the data to preserve. They start at the end of the previous one and stop + at ->r. The limit between the two solely depends on the protocol being + analysed ; ->lr may be used as a marker. + - the spare area : it is the remainder of the buffer, which can be used to + store new incoming data. It starts at ->r and is up to ->size-l long. It + may be limited by global.maxrewrite. + - the reserved are : this is the area which must not be filled and is + reserved for possible rewrites ; it is up to global.maxrewrite bytes + long. */ #endif /* _TYPES_BUFFERS_H */ diff --git a/src/buffers.c b/src/buffers.c index 44b3f7d..43c0647 100644 --- a/src/buffers.c +++ b/src/buffers.c @@ -196,7 +196,7 @@ int buffer_put_block(struct buffer *buf, const char *blk, int len) return 0; /* OK so the data fits in the buffer in one or two blocks */ - max = buffer_contig_space_with_len(buf, max); + max = buffer_contig_space_with_res(buf, buf->size - max); memcpy(buf->r, blk, MIN(len, max)); if (len > max) memcpy(buf->data, blk + max, len - max); -- 1.7.2.3