Hello ML,

Here is a serie of patches to make the cache capable of caching HTTP
objects larger than a buffer.

The 4th patch add "max-object-size" option to "cache" section so that
to limit the size of the HTTP objects to be cached.

Do not hesitate to test them.

Regards,
Fred.
>From 999ed7a5d07057d2914870c9213430722da7a02a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Tue, 23 Oct 2018 10:09:19 +0200
Subject: [PATCH 5/5] DOC: Update about the cache support for big objects.

---
 doc/configuration.txt | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9cc2a674..a139e438 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -17433,9 +17433,7 @@ The cache won't store and won't deliver objects in these cases:
 
 - If the response is not a 200
 - If the response contains a Vary header
-- If the response does not contain a Content-Length header or if the
-  Content-Length + the headers size is greater than a buffer size - the
-  reserve.
+- If the Content-Length + the headers size is greater than "max-object-size"
 - If the response is not cacheable
 
 - If the request is not a GET
@@ -17463,6 +17461,10 @@ total-max-size <megabytes>
   Define the size in RAM of the cache in megabytes. This size is split in
   blocks of 1kB which are used by the cache entries.
 
+max-object-size <bytes>
+  Define the maximum size of the objects to be cached. If not set, it equals
+  to a 256th of the cache size.
+
 max-age <seconds>
   Define the maximum expiration duration. The expiration is set has the lowest
   value between the s-maxage or max-age (in this order) directive in the
-- 
2.11.0

>From 902ad80d7858c246c51fc90aa17e0229f2175166 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Mon, 22 Oct 2018 16:59:13 +0200
Subject: [PATCH 4/5] MINOR: cache: Add "max-object-size" option.

This patch adds "max-object-size" option to the cache to limit
the size in bytes of the HTTP objects to be cached. When not provided,
the maximum size of an HTTP object is a 256th of the cache size.
---
 src/cache.c | 36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

diff --git a/src/cache.c b/src/cache.c
index c0e89579..d2411543 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -50,6 +50,7 @@ struct cache {
 	struct eb_root entries;  /* head of cache entries based on keys */
 	unsigned int maxage;     /* max-age */
 	unsigned int maxblocks;
+	unsigned int maxobjsz;   /* max-object-size */
 	char id[33];             /* cache name */
 };
 
@@ -230,9 +231,6 @@ cache_store_http_forward_data(struct stream *s, struct filter *filter,
 
 			to_append = MIN(ci_contig_data(msg->chn), len - st->hdrs_len);
 
-			/* Skip remaining headers to fill the cache */
-			c_adv(msg->chn, st->hdrs_len);
-
 			shctx_lock(shctx);
 			fb = shctx_row_reserve_hot(shctx, st->first_block, to_append);
 			if (!fb) {
@@ -242,6 +240,8 @@ cache_store_http_forward_data(struct stream *s, struct filter *filter,
 			}
 			shctx_unlock(shctx);
 
+			/* Skip remaining headers to fill the cache */
+			c_adv(msg->chn, st->hdrs_len);
 			append = shctx_row_data_append(shctx, st->first_block, st->first_block->last_append,
 			                               (unsigned char *)ci_head(msg->chn), to_append);
 			ret = st->hdrs_len + to_append - append;
@@ -431,6 +431,11 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
 	if (!(txn->req.flags & HTTP_MSGF_VER_11))
 		goto out;
 
+	/* Do not cache too big objects. */
+	if ((msg->flags & HTTP_MSGF_CNT_LEN) && shctx->max_obj_size > 0 &&
+	    msg->sov + msg->body_len > shctx->max_obj_size)
+		goto out;
+
 	/* cache only GET method */
 	if (txn->meth != HTTP_METH_GET)
 		goto out;
@@ -830,6 +835,7 @@ int cfg_parse_cache(const char *file, int linenum, char **args, int kwm)
 			}
 			tmp_cache_config->maxage = 60;
 			tmp_cache_config->maxblocks = 0;
+			tmp_cache_config->maxobjsz = 0;
 		}
 	} else if (strcmp(args[0], "total-max-size") == 0) {
 		int maxsize;
@@ -856,7 +862,21 @@ int cfg_parse_cache(const char *file, int linenum, char **args, int kwm)
 		}
 
 		tmp_cache_config->maxage = atoi(args[1]);
-	} else if (*args[0] != 0) {
+	} else if (strcmp(args[0], "max-object-size") == 0) {
+		if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
+			err_code |= ERR_ABORT;
+			goto out;
+		}
+
+		if (!*args[1]) {
+			ha_warning("parsing [%s:%d]: '%s' expects a maximum file size parameter in bytes.\n",
+			        file, linenum, args[0]);
+			err_code |= ERR_WARN;
+		}
+
+		tmp_cache_config->maxobjsz = atoi(args[1]);
+	}
+	else if (*args[0] != 0) {
 		ha_alert("parsing [%s:%d] : unknown keyword '%s' in 'cache' section\n", file, linenum, args[0]);
 		err_code |= ERR_ALERT | ERR_FATAL;
 		goto out;
@@ -882,7 +902,13 @@ int cfg_post_parse_section_cache()
 			goto out;
 		}
 
-		ret_shctx = shctx_init(&shctx, tmp_cache_config->maxblocks, CACHE_BLOCKSIZE, -1, sizeof(struct cache), 1);
+		if (!tmp_cache_config->maxobjsz)
+			/* Default max. file size is a 256th of the cache size. */
+			tmp_cache_config->maxobjsz =
+				(tmp_cache_config->maxblocks * CACHE_BLOCKSIZE) >> 8;
+
+		ret_shctx = shctx_init(&shctx, tmp_cache_config->maxblocks, CACHE_BLOCKSIZE,
+		                       tmp_cache_config->maxobjsz, sizeof(struct cache), 1);
 
 		if (ret_shctx < 0) {
 			if (ret_shctx == SHCTX_E_INIT_LOCK)
-- 
2.11.0

>From 1dd7c370a337ee6738e15848ed68d167820c09aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Mon, 22 Oct 2018 16:21:39 +0200
Subject: [PATCH 3/5] MINOR: shctx: Add a maximum object size parameter.

This patch adds a new parameter to shctx_init() function to be used to
limit the size of each shared object, -1 value meaning "no limit".
---
 include/proto/shctx.h |  3 ++-
 include/types/shctx.h |  1 +
 src/cache.c           |  2 +-
 src/shctx.c           | 11 ++++++++++-
 src/ssl_sock.c        |  2 +-
 5 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/include/proto/shctx.h b/include/proto/shctx.h
index 13e00c75..594a81d5 100644
--- a/include/proto/shctx.h
+++ b/include/proto/shctx.h
@@ -31,7 +31,8 @@
 #endif
 #endif
 
-int shctx_init(struct shared_context **orig_shctx, int maxblocks, int blocksize, int extra, int shared);
+int shctx_init(struct shared_context **orig_shctx,
+               int maxblocks, int blocksize, int maxobjsz, int extra, int shared);
 struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx,
                                            struct shared_block *last, int data_len);
 void shctx_row_inc_hot(struct shared_context *shctx, struct shared_block *first);
diff --git a/include/types/shctx.h b/include/types/shctx.h
index 186f7365..53dca3f1 100644
--- a/include/types/shctx.h
+++ b/include/types/shctx.h
@@ -40,6 +40,7 @@ struct shared_context {
 	struct list avail;  /* list for active and free blocks */
 	struct list hot;     /* list for locked blocks */
 	unsigned int nbav;  /* number of available blocks */
+	int max_obj_size;   /* maximum object size. */
 	void (*free_block)(struct shared_block *first, struct shared_block *block);
 	short int block_size;
 	unsigned char data[0];
diff --git a/src/cache.c b/src/cache.c
index a198b406..c0e89579 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -882,7 +882,7 @@ int cfg_post_parse_section_cache()
 			goto out;
 		}
 
-		ret_shctx = shctx_init(&shctx, tmp_cache_config->maxblocks, CACHE_BLOCKSIZE, sizeof(struct cache), 1);
+		ret_shctx = shctx_init(&shctx, tmp_cache_config->maxblocks, CACHE_BLOCKSIZE, -1, sizeof(struct cache), 1);
 
 		if (ret_shctx < 0) {
 			if (ret_shctx == SHCTX_E_INIT_LOCK)
diff --git a/src/shctx.c b/src/shctx.c
index 2a149a1d..604fd7df 100644
--- a/src/shctx.c
+++ b/src/shctx.c
@@ -43,6 +43,13 @@ struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx,
 	if (data_len > shctx->nbav * shctx->block_size)
 		goto out;
 
+	/* Check the object size limit. */
+	if (shctx->max_obj_size > 0) {
+		if ((first && first->len + data_len > shctx->max_obj_size) ||
+			(!first && data_len > shctx->max_obj_size))
+			goto out;
+	}
+
 	/* Note that <remain> is nul only if <first> is not nul. */
 	remain = 1;
 	if (first) {
@@ -284,7 +291,8 @@ int shctx_row_data_get(struct shared_context *shctx, struct shared_block *first,
  * Returns: -1 on alloc failure, <maxblocks> if it performs context alloc,
  * and 0 if cache is already allocated.
  */
-int shctx_init(struct shared_context **orig_shctx, int maxblocks, int blocksize, int extra, int shared)
+int shctx_init(struct shared_context **orig_shctx, int maxblocks, int blocksize,
+               int maxobjsz, int extra, int shared)
 {
 	int i;
 	struct shared_context *shctx;
@@ -351,6 +359,7 @@ int shctx_init(struct shared_context **orig_shctx, int maxblocks, int blocksize,
 	LIST_INIT(&shctx->hot);
 
 	shctx->block_size = blocksize;
+	shctx->max_obj_size = maxobjsz;
 
 	/* init the free blocks after the shared context struct */
 	cur = (void *)shctx + sizeof(struct shared_context) + extra;
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index ee713692..140f406b 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -4786,7 +4786,7 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
 	}
 	if (!ssl_shctx && global.tune.sslcachesize) {
 		alloc_ctx = shctx_init(&ssl_shctx, global.tune.sslcachesize,
-		                       sizeof(struct sh_ssl_sess_hdr) + SHSESS_BLOCK_MIN_SIZE,
+		                       sizeof(struct sh_ssl_sess_hdr) + SHSESS_BLOCK_MIN_SIZE, -1,
 		                       sizeof(*sh_ssl_sess_tree),
 		                       ((global.nbthread > 1) || (!global_ssl.private_cache && (global.nbproc > 1))) ? 1 : 0);
 		if (alloc_ctx < 0) {
-- 
2.11.0

>From 71641f0d2fd7b7176e62ca54f3dd8fa3651a4a1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Mon, 22 Oct 2018 18:01:48 +0200
Subject: [PATCH 2/5] MINOR: cache: Larger HTTP objects caching.

This patch makes the capable of storing HTTP objects larger than a buffer.
It makes usage of the "block by block shared object allocation" new shctx API.

A new pointer to struct shared_block has been added to the cache applet
context to memorize the next block to be used by the HTTP cache I/O handler
http_cache_io_handler() to emit the data. Another member, named "sent" memorize
the number of bytes already sent by this handler. So, to send an object from cache,
http_cache_io_handler() must be called until "sent" counter reaches the size
of this object.
---
 include/types/applet.h |   4 +-
 src/cache.c            | 151 ++++++++++++++++++++++++++++++++++---------------
 2 files changed, 109 insertions(+), 46 deletions(-)

diff --git a/include/types/applet.h b/include/types/applet.h
index 38908600..9f1adfd8 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -105,7 +105,9 @@ struct appctx {
 			int i0, i1;             /* to 0 by the CLI before first invocation of the keyword parser. */
 		} cli;                          /* context used by the CLI */
 		struct {
-			struct cache_entry *entry;
+			struct cache_entry *entry;  /* Entry to be sent from cache. */
+			int sent;                   /* The number of bytes already sent for this cache entry. */
+			struct shared_block *next;  /* The next block of data to be sent for this cache entry. */
 		} cache;
 		/* all entries below are used by various CLI commands, please
 		 * keep the grouped together and avoid adding new ones.
diff --git a/src/cache.c b/src/cache.c
index b549537c..a198b406 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -182,15 +182,30 @@ cache_store_http_headers(struct stream *s, struct filter *filter, struct http_ms
 	return 1;
 }
 
+static inline void disable_cache_entry(struct cache_st *st,
+                                       struct filter *filter, struct shared_context *shctx)
+{
+	struct cache_entry *object;
+
+	object = (struct cache_entry *)st->first_block->data;
+	filter->ctx = NULL; /* disable cache  */
+	shctx_lock(shctx);
+	shctx_row_dec_hot(shctx, st->first_block);
+	object->eb.key = 0;
+	shctx_unlock(shctx);
+	pool_free(pool_head_cache_st, st);
+}
+
 static int
 cache_store_http_forward_data(struct stream *s, struct filter *filter,
 		       struct http_msg *msg, unsigned int len)
 {
 	struct cache_st *st = filter->ctx;
 	struct shared_context *shctx = shctx_ptr((struct cache *)filter->config->conf);
-	struct cache_entry *object;
 	int ret;
 
+	ret = 0;
+
 	/*
 	 * We need to skip the HTTP headers first, because we saved them in the
 	 * http-response action.
@@ -210,31 +225,34 @@ cache_store_http_forward_data(struct stream *s, struct filter *filter,
 	else {
 		/* Forward data */
 		if (filter->ctx && st->first_block) {
-			/* disable buffering if too much data (never greater than a buffer size */
-			if (len - st->hdrs_len > global.tune.bufsize - global.tune.maxrewrite - st->first_block->len) {
-			  disable_cache:
-				object = (struct cache_entry *)st->first_block->data;
-				filter->ctx = NULL; /* disable cache  */
-				shctx_lock(shctx);
-				shctx_row_dec_hot(shctx, st->first_block);
-				object->eb.key = 0;
+			int to_append, append;
+			struct shared_block *fb;
+
+			to_append = MIN(ci_contig_data(msg->chn), len - st->hdrs_len);
+
+			/* Skip remaining headers to fill the cache */
+			c_adv(msg->chn, st->hdrs_len);
+
+			shctx_lock(shctx);
+			fb = shctx_row_reserve_hot(shctx, st->first_block, to_append);
+			if (!fb) {
 				shctx_unlock(shctx);
-				pool_free(pool_head_cache_st, st);
-			} else {
-				/* Skip remaining headers to fill the cache */
-				c_adv(msg->chn, st->hdrs_len);
-				ret = shctx_row_data_append(shctx,
-							    st->first_block, NULL,
-							    (unsigned char *)ci_head(msg->chn),
-							    MIN(ci_contig_data(msg->chn), len - st->hdrs_len));
-				/* Rewind the buffer to forward all data */
-				c_rew(msg->chn, st->hdrs_len);
-				st->hdrs_len = 0;
-				if (ret)
-					goto disable_cache;
+				disable_cache_entry(st, filter, shctx);
+				return len;
 			}
+			shctx_unlock(shctx);
+
+			append = shctx_row_data_append(shctx, st->first_block, st->first_block->last_append,
+			                               (unsigned char *)ci_head(msg->chn), to_append);
+			ret = st->hdrs_len + to_append - append;
+			/* Rewind the buffer to forward all data */
+			c_rew(msg->chn, st->hdrs_len);
+			st->hdrs_len = 0;
+			if (ret < 0)
+				disable_cache_entry(st, filter, shctx);
 		}
-		ret = len;
+		else
+			ret = len;
 	}
 
 	if ((ret != len) ||
@@ -413,10 +431,6 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
 	if (!(txn->req.flags & HTTP_MSGF_VER_11))
 		goto out;
 
-	/* does not cache if Content-Length unknown */
-	if (!(msg->flags & HTTP_MSGF_CNT_LEN))
-		goto out;
-
 	/* cache only GET method */
 	if (txn->meth != HTTP_METH_GET)
 		goto out;
@@ -435,12 +449,8 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
 	if (!(txn->flags & TX_CACHEABLE) || !(txn->flags & TX_CACHE_COOK))
 		goto out;
 
-	if ((msg->sov + msg->body_len) > (global.tune.bufsize - global.tune.maxrewrite))
-		goto out;
-
 	shctx_lock(shctx);
-
-	first = shctx_row_reserve_hot(shctx, NULL, sizeof(struct cache_entry) + msg->sov + msg->body_len);
+	first = shctx_row_reserve_hot(shctx, NULL, sizeof(struct cache_entry) + msg->sov);
 	if (!first) {
 		shctx_unlock(shctx);
 		goto out;
@@ -456,7 +466,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
 
 	/* reserve space for the cache_entry structure */
 	first->len = sizeof(struct cache_entry);
-
+	first->last_append = NULL;
 	/* cache the headers in a http action because it allows to chose what
 	 * to cache, for example you might want to cache a response before
 	 * modifying some HTTP headers, or on the contrary after modifying
@@ -529,14 +539,60 @@ static void http_cache_applet_release(struct appctx *appctx)
 	shctx_unlock(shctx_ptr(cache));
 }
 
-static void http_cache_io_handler(struct appctx *appctx)
+static int cache_channel_row_data_get(struct appctx *appctx, int len)
 {
+	int ret, total;
 	struct stream_interface *si = appctx->owner;
 	struct channel *res = si_ic(si);
 	struct cache *cache = (struct cache *)appctx->rule->arg.act.p[0];
-	struct cache_entry *cache_ptr = appctx->ctx.cache.entry;
 	struct shared_context *shctx = shctx_ptr(cache);
+	struct cache_entry *cache_ptr = appctx->ctx.cache.entry;
+	struct shared_block *blk, *next = appctx->ctx.cache.next;
+	int offset;
+
+	total = 0;
+	offset = 0;
+
+	if (!next) {
+		offset = sizeof(struct cache_entry);
+		next =  block_ptr(cache_ptr);
+	}
+
+	blk = next;
+	list_for_each_entry_from(blk, &shctx->hot, list) {
+		int sz;
+
+		if (len <= 0)
+			break;
+
+		sz = MIN(len, shctx->block_size - offset);
+
+		ret = ci_putblk(res, (const char *)blk->data + offset, sz);
+		if (unlikely(offset))
+			offset = 0;
+		if (ret <= 0) {
+			if (ret == -3 || ret == -1) {
+				si_applet_cant_put(si);
+				break;
+			}
+			return -1;
+		}
+
+		total += sz;
+		len -= sz;
+	}
+	appctx->ctx.cache.next = blk;
+
+	return total;
+}
+
+static void http_cache_io_handler(struct appctx *appctx)
+{
+	struct stream_interface *si = appctx->owner;
+	struct channel *res = si_ic(si);
+	struct cache_entry *cache_ptr = appctx->ctx.cache.entry;
 	struct shared_block *first = block_ptr(cache_ptr);
+	int *sent = &appctx->ctx.cache.sent;
 
 	if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
 		goto out;
@@ -552,18 +608,21 @@ static void http_cache_io_handler(struct appctx *appctx)
 
 	/* buffer are aligned there, should be fine */
 	if (appctx->st0 == HTTP_CACHE_INIT) {
-		int len = first->len - sizeof(struct cache_entry);
-		if ((shctx_row_data_get(shctx, first, (unsigned char *)ci_tail(res), sizeof(struct cache_entry), len)) != 0) {
-			/* should never get there, because at the moment, a
-			 * cache object can never be bigger than a buffer */
-			 abort();
+		int len = first->len - *sent - sizeof(struct cache_entry);
 
-			si_applet_cant_put(si);
-			goto out;
+		if (len > 0) {
+			int ret;
+
+			ret = cache_channel_row_data_get(appctx, len);
+			if (ret == -1)
+				appctx->st0 = HTTP_CACHE_END;
+			else
+				*sent += ret;
+		}
+		else {
+			*sent = 0;
+			appctx->st0 = HTTP_CACHE_FWD;
 		}
-		b_add(&res->buf, len);
-		res->total += len;
-		appctx->st0 = HTTP_CACHE_FWD;
 	}
 
 	if (appctx->st0 == HTTP_CACHE_FWD) {
@@ -694,6 +753,8 @@ enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *p
 			appctx->st0 = HTTP_CACHE_INIT;
 			appctx->rule = rule;
 			appctx->ctx.cache.entry = res;
+			appctx->ctx.cache.next = NULL;
+			appctx->ctx.cache.sent = 0;
 			return ACT_RET_CONT;
 		} else {
 			shctx_lock(shctx_ptr(cache));
-- 
2.11.0

>From 3ce5c2002790a0d28682b6302d947e5d7b30ed13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Mon, 22 Oct 2018 17:55:57 +0200
Subject: [PATCH 1/5] MINOR: shctx: Shared objects block by block allocation.

This patch makes shctx capable of storing objects in several parts,
each parts being made of several blocks. There is no more need to
walk through until reaching the end of a row to append new blocks.

A new pointer to a struct shared_block member, named last_reserved,
has been added to struct shared_block so that to memorize the last block which was
reserved by shctx_row_reserve_hot(). Same thing about "last_append" pointer which
is used to memorize the last block used by shctx_row_data_append() to store the data.
---
 include/proto/shctx.h |  19 +++++++-
 include/types/shctx.h |   2 +
 src/cache.c           |   6 +--
 src/shctx.c           | 122 +++++++++++++++++++++++++++++++++-----------------
 src/ssl_sock.c        |   4 +-
 5 files changed, 104 insertions(+), 49 deletions(-)

diff --git a/include/proto/shctx.h b/include/proto/shctx.h
index 55cb2a77..13e00c75 100644
--- a/include/proto/shctx.h
+++ b/include/proto/shctx.h
@@ -32,11 +32,13 @@
 #endif
 
 int shctx_init(struct shared_context **orig_shctx, int maxblocks, int blocksize, int extra, int shared);
-struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx, int data_len);
+struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx,
+                                           struct shared_block *last, int data_len);
 void shctx_row_inc_hot(struct shared_context *shctx, struct shared_block *first);
 void shctx_row_dec_hot(struct shared_context *shctx, struct shared_block *first);
 int shctx_row_data_append(struct shared_context *shctx,
-                          struct shared_block *first, unsigned char *data, int len);
+                          struct shared_block *first, struct shared_block *from,
+                          unsigned char *data, int len);
 int shctx_row_data_get(struct shared_context *shctx, struct shared_block *first,
                        unsigned char *dst, int offset, int len);
 
@@ -180,6 +182,19 @@ static inline void _shctx_unlock(struct shared_context *shctx)
 
 /* List Macros */
 
+/*
+ * Insert <s> block after <head> which is not necessarily the head of a list,
+ * so between <head> and the next element after <head>.
+ */
+static inline void shctx_block_append_hot(struct shared_context *shctx,
+                                          struct list *head,
+                                          struct shared_block *s)
+{
+	shctx->nbav--;
+	LIST_DEL(&s->list);
+	LIST_ADD(head, &s->list);
+}
+
 static inline void shctx_block_set_hot(struct shared_context *shctx,
 				    struct shared_block *s)
 {
diff --git a/include/types/shctx.h b/include/types/shctx.h
index 559bebad..186f7365 100644
--- a/include/types/shctx.h
+++ b/include/types/shctx.h
@@ -24,6 +24,8 @@ struct shared_block {
 	unsigned int len;          /* data length for the row */
 	unsigned int block_count;  /* number of blocks */
 	unsigned int refcount;
+	struct shared_block *last_reserved;
+	struct shared_block *last_append;
 	unsigned char data[0];
 };
 
diff --git a/src/cache.c b/src/cache.c
index 2024642f..b549537c 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -224,7 +224,7 @@ cache_store_http_forward_data(struct stream *s, struct filter *filter,
 				/* Skip remaining headers to fill the cache */
 				c_adv(msg->chn, st->hdrs_len);
 				ret = shctx_row_data_append(shctx,
-							    st->first_block,
+							    st->first_block, NULL,
 							    (unsigned char *)ci_head(msg->chn),
 							    MIN(ci_contig_data(msg->chn), len - st->hdrs_len));
 				/* Rewind the buffer to forward all data */
@@ -440,7 +440,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
 
 	shctx_lock(shctx);
 
-	first = shctx_row_reserve_hot(shctx, sizeof(struct cache_entry) + msg->sov + msg->body_len);
+	first = shctx_row_reserve_hot(shctx, NULL, sizeof(struct cache_entry) + msg->sov + msg->body_len);
 	if (!first) {
 		shctx_unlock(shctx);
 		goto out;
@@ -465,7 +465,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
 
 	/* does not need to be locked because it's in the "hot" list,
 	 * copy the headers */
-	if (shctx_row_data_append(shctx, first, (unsigned char *)ci_head(&s->res), msg->sov) < 0)
+	if (shctx_row_data_append(shctx, first, NULL, (unsigned char *)ci_head(&s->res), msg->sov) < 0)
 		goto out;
 
 	/* register the buffer in the filter ctx for filling it with data*/
diff --git a/src/shctx.c b/src/shctx.c
index 59ac8b83..2a149a1d 100644
--- a/src/shctx.c
+++ b/src/shctx.c
@@ -25,42 +25,67 @@ int use_shared_mem = 0;
 #endif
 
 /*
- * Reserve a row, put it in the hotlist, set the refcount to 1
+ * Reserve a new row if <first> is null, put it in the hotlist, set the refcount to 1
+ * or append new blocks to the row with <first> as first block if non null.
  *
  * Reserve blocks in the avail list and put them in the hot list
  * Return the first block put in the hot list or NULL if not enough blocks available
  */
-struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx, int data_len)
+struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx,
+                                           struct shared_block *first, int data_len)
 {
-	struct shared_block *block, *sblock, *ret = NULL, *first;
+	struct shared_block *last = NULL, *block, *sblock, *ret = NULL, *next;
 	int enough = 0;
 	int freed = 0;
+	int remain;
 
 	/* not enough usable blocks */
 	if (data_len > shctx->nbav * shctx->block_size)
 		goto out;
 
+	/* Note that <remain> is nul only if <first> is not nul. */
+	remain = 1;
+	if (first) {
+		/* Check that there is some block to reserve.
+		 * In this first block of code we compute the remaining room in the
+		 * current list of block already reserved for this object.
+		 * We return asap if there is enough room to copy <data_len> bytes.
+		 */
+		last = first->last_reserved;
+		/* Remaining room. */
+		remain = (shctx->block_size * first->block_count - first->len);
+		if (remain) {
+			if (remain > data_len) {
+				return last ? last : first;
+			} else {
+				data_len -= remain;
+				if (!data_len)
+					return last ? last : first;
+			}
+		}
+	}
+
 	while (!enough && !LIST_ISEMPTY(&shctx->avail)) {
 		int count = 0;
 		int first_count = 0, first_len = 0;
 
-		first = block = LIST_NEXT(&shctx->avail, struct shared_block *, list);
+		next = block = LIST_NEXT(&shctx->avail, struct shared_block *, list);
 		if (ret == NULL)
-			ret = first;
+			ret = next;
 
-		first_count = first->block_count;
-		first_len = first->len;
+		first_count = next->block_count;
+		first_len = next->len;
 		/*
 		Should never been set to 0.
-		if (first->block_count == 0)
-		first->block_count = 1;
+		if (next->block_count == 0)
+		next->block_count = 1;
 		*/
 
 		list_for_each_entry_safe_from(block, sblock, &shctx->avail, list) {
 
 			/* release callback */
 			if (first_len && shctx->free_block)
-				shctx->free_block(first, block);
+				shctx->free_block(next, block);
 
 			block->block_count = 1;
 			block->len = 0;
@@ -68,22 +93,41 @@ struct shared_block *shctx_row_reserve_hot(struct shared_context *shctx, int dat
 			freed++;
 			data_len -= shctx->block_size;
 
-			if (data_len > 0)
-				shctx_block_set_hot(shctx, block);
-
-			if (data_len <= 0 && !enough) {
-				shctx_block_set_hot(shctx, block);
-				ret->block_count = freed;
-				ret->refcount = 1;
-				enough = 1;
+			if (data_len > 0 || !enough) {
+				if (last) {
+					shctx_block_append_hot(shctx, &last->list, block);
+					last = block;
+				} else {
+					shctx_block_set_hot(shctx, block);
+				}
+				if (!remain) {
+					first->last_append = block;
+					remain = 1;
+				}
+				if (data_len <= 0) {
+					ret->block_count = freed;
+					ret->refcount = 1;
+					ret->last_reserved = block;
+					enough = 1;
+				}
 			}
-
 			count++;
 			if (count >= first_count)
 				break;
 		}
 	}
 
+	if (first) {
+		first->block_count += ret->block_count;
+		first->last_reserved = ret->last_reserved;
+		/* Reset this block. */
+		ret->last_reserved = NULL;
+		ret->block_count = 1;
+		ret->refcount = 0;
+		/* Return the first block. */
+		ret = first;
+	}
+
 out:
 	return ret;
 }
@@ -147,50 +191,44 @@ void shctx_row_dec_hot(struct shared_context *shctx, struct shared_block *first)
  * Return the amount of appended data if ret >= 0
  * or how much more space it needs to contains the data if < 0.
  */
-int shctx_row_data_append(struct shared_context *shctx, struct shared_block *first, unsigned char *data, int len)
+int shctx_row_data_append(struct shared_context *shctx,
+                          struct shared_block *first, struct shared_block *from,
+                          unsigned char *data, int len)
 {
 	int remain, start;
-	int count = 0;
 	struct shared_block *block;
 
-
 	/* return -<len> needed to work */
 	if (len > first->block_count * shctx->block_size - first->len)
 		return (first->block_count * shctx->block_size - first->len) - len;
 
-	/* skipping full buffers, stop at the first buffer with remaining space */
-	block = first;
+	block = from ? from : first;
 	list_for_each_entry_from(block, &shctx->hot, list) {
-		count++;
-
-
-		/* break if there is not enough blocks */
-		if (count > first->block_count)
-			break;
-
 		/* end of copy */
 		if (len <= 0)
 			break;
 
-		/* skip full buffers */
-		if (count * shctx->block_size <= first->len)
-			continue;
-
-		/* remaining space in the current block which is not full */
-		remain = (shctx->block_size * count - first->len) % shctx->block_size;
-		/* if remain == 0, previous buffer are full, or first->len == 0 */
-		remain = remain ? remain : shctx->block_size;
-
-		/* start must be calculated before remain is modified */
-		start = shctx->block_size - remain;
+		/* remaining written bytes in the current block. */
+		remain = (shctx->block_size * first->block_count - first->len) % shctx->block_size;
+		/* if remain == 0, previous buffers are full, or first->len == 0 */
+		if (!remain) {
+			remain = shctx->block_size;
+			start = 0;
+		}
+		else {
+			/* start must be calculated before remain is modified */
+			start = shctx->block_size - remain;
+		}
 
 		/* must not try to copy more than len */
 		remain = MIN(remain, len);
 
 		memcpy(block->data + start, data, remain);
+
 		data += remain;
 		len -= remain;
 		first->len += remain; /* update len in the head of the row */
+		first->last_append = block;
 	}
 
 	return len;
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 41833768..ee713692 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -3871,7 +3871,7 @@ static int sh_ssl_sess_store(unsigned char *s_id, unsigned char *data, int data_
 	struct shared_block *first;
 	struct sh_ssl_sess_hdr *sh_ssl_sess, *oldsh_ssl_sess;
 
-	first = shctx_row_reserve_hot(ssl_shctx, data_len + sizeof(struct sh_ssl_sess_hdr));
+	first = shctx_row_reserve_hot(ssl_shctx, NULL, data_len + sizeof(struct sh_ssl_sess_hdr));
 	if (!first) {
 		/* Could not retrieve enough free blocks to store that session */
 		return 0;
@@ -3897,7 +3897,7 @@ static int sh_ssl_sess_store(unsigned char *s_id, unsigned char *data, int data_
 		first->len = sizeof(struct sh_ssl_sess_hdr);
 	}
 
-	if (shctx_row_data_append(ssl_shctx, first, data, data_len) < 0) {
+	if (shctx_row_data_append(ssl_shctx, first, NULL, data, data_len) < 0) {
 		shctx_row_dec_hot(ssl_shctx, first);
 		return 0;
 	}
-- 
2.11.0

Reply via email to