MINOR: debug: Add an option that causes random allocation failures.

When compiling with DEBUG_FAIL_ALLOC, add a new option, tune.fail-alloc,
that gives the percentage of chances an allocation fails.
This is useful to check that allocation failures are always handled
gracefully.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index adc6efa..fe5eb25 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1648,6 +1648,12 @@
   counts only the pure Lua runtime. If the Lua does a sleep, the sleep is
   not taken in account. The default timeout is 4s.
 
+tune.fail-alloc
+  If compiled with DEBUG_FAIL_ALLOC, gives the percentage of chances an
+  allocation attempt fails. Must be between 0 (no failure) and 100 (no
+  success). This is useful to debug and make sure memory failures are handled
+  gracefully.
+
 tune.maxaccept <number>
   Sets the maximum number of consecutive connections a process may accept in a
   row before switching to other work. In single process mode, higher numbers
diff --git a/include/common/config.h b/include/common/config.h
index 6fd85f8..55ecd59 100644
--- a/include/common/config.h
+++ b/include/common/config.h
@@ -51,7 +51,7 @@
 /* On architectures supporting threads and double-word CAS, we can implement
  * lock-less memory pools. This isn't supported for debugging modes however.
  */
-#if defined(USE_THREAD) && defined(HA_HAVE_CAS_DW) && !defined(DEBUG_NO_LOCKLESS_POOLS) && !defined(DEBUG_UAF)
+#if defined(USE_THREAD) && defined(HA_HAVE_CAS_DW) && !defined(DEBUG_NO_LOCKLESS_POOLS) && !defined(DEBUG_UAF) && !defined(DEBUG_FAIL_ALLOC)
 #define CONFIG_HAP_LOCKLESS_POOLS
 #ifndef CONFIG_HAP_POOL_CACHE_SIZE
 #define CONFIG_HAP_POOL_CACHE_SIZE 524288
diff --git a/src/memory.c b/src/memory.c
index 6cafd8b..04259a5 100644
--- a/src/memory.c
+++ b/src/memory.c
@@ -16,6 +16,7 @@
 #include <types/global.h>
 #include <types/stats.h>
 
+#include <common/cfgparse.h>
 #include <common/config.h>
 #include <common/debug.h>
 #include <common/hathreads.h>
@@ -46,6 +47,11 @@
 static struct list pools = LIST_HEAD_INIT(pools);
 int mem_poison_byte = -1;
 
+#ifdef DEBUG_FAIL_ALLOC
+static int mem_fail_rate = 0;
+static int mem_should_fail(const struct pool_head *);
+#endif
+
 /* Try to find an existing shared pool with the same characteristics and
  * returns it, otherwise creates this one. NULL is returned if no memory
  * is available for a new creation. Two flags are supported :
@@ -301,6 +307,10 @@
 	void *ptr = NULL;
 	int failed = 0;
 
+#ifdef DEBUG_FAIL_ALLOC
+	if (mem_should_fail(pool))
+		return NULL;
+#endif
 	/* stop point */
 	avail += pool->used;
 
@@ -562,6 +572,70 @@
 
 INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
 
+#ifdef DEBUG_FAIL_ALLOC
+#define MEM_FAIL_MAX_CHAR 32
+#define MEM_FAIL_MAX_STR 128
+static int mem_fail_cur_idx;
+static char mem_fail_str[MEM_FAIL_MAX_CHAR * MEM_FAIL_MAX_STR];
+__decl_hathreads(static HA_SPINLOCK_T mem_fail_lock);
+
+int mem_should_fail(const struct pool_head *pool)
+{
+	int ret;
+	int n;
+
+	if (mem_fail_rate > 0 && !(global.mode & MODE_STARTING)) {
+		int randnb = random() % 100;
+
+		if (mem_fail_rate > randnb)
+			ret = 1;
+		else
+			ret = 0;
+	}
+	HA_SPIN_LOCK(START_LOCK, &mem_fail_lock);
+	n = snprintf(&mem_fail_str[mem_fail_cur_idx * MEM_FAIL_MAX_CHAR],
+	    MEM_FAIL_MAX_CHAR - 2,
+	    "%d %.18s %d %d", mem_fail_cur_idx, pool->name, ret, tid);
+	while (n < MEM_FAIL_MAX_CHAR - 1)
+		mem_fail_str[mem_fail_cur_idx * MEM_FAIL_MAX_CHAR + n++] = ' ';
+	if (mem_fail_cur_idx < MEM_FAIL_MAX_STR - 1)
+		mem_fail_str[mem_fail_cur_idx * MEM_FAIL_MAX_CHAR + n] = '\n';
+	else
+		mem_fail_str[mem_fail_cur_idx * MEM_FAIL_MAX_CHAR + n] = 0;
+	mem_fail_cur_idx++;
+	if (mem_fail_cur_idx == MEM_FAIL_MAX_STR)
+		mem_fail_cur_idx = 0;
+	HA_SPIN_UNLOCK(START_LOCK, &mem_fail_lock);
+	return ret;
+
+}
+
+/* config parser for global "tune.fail-alloc" */
+static int mem_parse_global_fail_alloc(char **args, int section_type, struct proxy *curpx,
+                                      struct proxy *defpx, const char *file, int line,
+                                      char **err)
+{
+	if (too_many_args(1, args, err, NULL))
+		return -1;
+	mem_fail_rate = atoi(args[1]);
+	if (mem_fail_rate < 0 || mem_fail_rate > 100) {
+	    memprintf(err, "'%s' expects a numeric value between 0 and 100.", args[0]);
+	    return -1;
+	}
+	return 0;
+}
+#endif
+
+/* register global config keywords */
+static struct cfg_kw_list mem_cfg_kws = {ILH, {
+#ifdef DEBUG_FAIL_ALLOC
+	{ CFG_GLOBAL, "tune.fail-alloc", mem_parse_global_fail_alloc },
+#endif
+	{ 0, NULL, NULL }
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &mem_cfg_kws);
+
 /*
  * Local variables:
  *  c-indent-level: 8