From cc9a168cdc066b76b8b389c90d28453a2bb1b5bd Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Tue, 2 Dec 2014 13:54:01 +0100 Subject: MEDIUM: buffer: implement b_alloc_single() and b_alloc_pair() These primitives are used to atomically allocate one or two buffers. The b_alloc_single() function is not obvious. While we allocate only one buffer, we want to ensure that at least two remain available after our allocation. The purpose is to ensure we'll never enter a deadlock where all sessions allocate exactly one buffer, and none of them will be able to allocate the second buffer needed to build a response in order to release the first one. For this reason, if we pick the last buffer from the pool, we try to refill the pool with a new one, and in case of failure, we return the one we got. This function will be used on the I/O Rx path. Function b_alloc_pair() is simpler as it doesn't apply any margin, it simply ensures that both buffers are allocated, and will fail if either side fails to grab a buffer. This one will be used during session processing. --- include/common/buffer.h | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/include/common/buffer.h b/include/common/buffer.h index 380e21f..e2f4707 100644 --- a/include/common/buffer.h +++ b/include/common/buffer.h @@ -443,6 +443,58 @@ static inline void b_free(struct buffer **buf) *buf = &buf_empty; } +/* Ensures that is allocated. If an allocation is needed, it ensures + * that there is still at least 1 buffer available in the pool after this + * allocation so that we don't leave the pool in a condition where a session + * could not be allocated anymore, resulting in a deadlock. This means that + * we sometimes need to try to allocate extra entries. + */ +static inline struct buffer *b_alloc_single(struct buffer **buf) +{ + struct buffer *next; + + if ((*buf)->size) + return *buf; + + next = b_alloc(buf); + if (!next || (pool2_buffer->allocated - pool2_buffer->used) >= 2) + return next; + + /* here we know we have allocated the last entry from the pool, so + * we need to check if it's possible to refill the pool otherwise + * we must return the one we picked. + */ + next = pool_refill_alloc(pool2_buffer); + pool_free2(pool2_buffer, next); + if ((pool2_buffer->allocated - pool2_buffer->used) >= 2) + return *buf; + + /* cannot allocate a second buffer */ + __b_drop(buf); + *buf = &buf_wanted; + return NULL; +} + +/* Ensures that both and are allocated. Tries to allocated both of + * them, and if either fails, release the allocated one and replace them with + * buf_wanted. Returns 0 in case of failure, non-zero otherwise. It does not + * try to apply any margin, it's made for session processing. + */ +static inline int b_alloc_pair(struct buffer **b1, struct buffer **b2) +{ + if (!(*b1)->size && !b_alloc(b1)) + return 0; + + if ((*b2)->size || b_alloc(b2)) + return 1; + + if (buffer_empty(*b1)) { + __b_drop(b1); + *b1 = &buf_wanted; + } + return 0; +} + #endif /* _COMMON_BUFFER_H */ /* -- 1.7.12.1