MINOR: pools: add a new global option "no-memory-trimming"

Some users with very large numbers of connections have been facing
extremely long malloc_trim() calls on reload that managed to trigger
the watchdog! That's a bit counter-productive. It's even possible
that some implementations are not perfectly reliable or that their
trimming time grows quadratically with the memory used. Instead of
constantly trying to work around these issues, let's offer an option
to disable this mechanism, since nobody had been complaining in the
past, and this was only meant to be an improvement.

This should be backported to 2.4 where trimming on reload started to
appear.

(cherry picked from commit c4e56dc58c9ada7c4a8d585cb117a5b825916002)
[wt: minor context adj]
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit 4bcff9ba47e1c8f78405a1d6686edda68f254b44)
Signed-off-by: Willy Tarreau <w@1wt.eu>
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 03420c9..c9e844d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1045,6 +1045,7 @@
    - maxsslconn
    - maxsslrate
    - maxzlibmem
+   - no-memory-trimming
    - noepoll
    - nokqueue
    - noevports
@@ -2302,6 +2303,22 @@
   with "show info" on the line "MaxZlibMemUsage", the memory used by zlib is
   "ZlibMemUsage" in bytes.
 
+no-memory-trimming
+  Disables memory trimming ("malloc_trim") at a few moments where attempts are
+  made to reclaim lots of memory (on memory shortage or on reload). Trimming
+  memory forces the system's allocator to scan all unused areas and to release
+  them. This is generally seen as nice action to leave more available memory to
+  a new process while the old one is unlikely to make significant use of it.
+  But some systems dealing with tens to hundreds of thousands of concurrent
+  connections may experience a lot of memory fragmentation, that may render
+  this release operation extremely long. During this time, no more traffic
+  passes through the process, new connections are not accepted anymore, some
+  health checks may even fail, and the watchdog may even trigger and kill the
+  unresponsive process, leaving a huge core dump. If this ever happens, then it
+  is suggested to use this option to disable trimming and stop trying to be
+  nice with the new process. Note that advanced memory allocators usually do
+  not suffer from such a problem.
+
 noepoll
   Disables the use of the "epoll" event polling system on Linux. It is
   equivalent to the command-line argument "-de". The next polling system
diff --git a/src/pool.c b/src/pool.c
index 6670ef6..1f116d5 100644
--- a/src/pool.c
+++ b/src/pool.c
@@ -42,6 +42,7 @@
 
 #if defined(HA_HAVE_MALLOC_TRIM)
 static int using_libc_allocator = 0;
+static int disable_trim __read_mostly = 0;
 
 /* ask the allocator to trim memory pools.
  * This must run under thread isolation so that competing threads trying to
@@ -53,6 +54,9 @@
 {
 	int isolated = thread_isolated();
 
+	if (disable_trim)
+		return;
+
 	if (!isolated)
 		thread_isolate();
 
@@ -667,11 +671,23 @@
 }
 #endif
 
+/* config parser for global "no-memory-trimming" */
+static int mem_parse_global_no_mem_trim(char **args, int section_type, struct proxy *curpx,
+                                       const struct proxy *defpx, const char *file, int line,
+                                       char **err)
+{
+	if (too_many_args(0, args, err, NULL))
+		return -1;
+	disable_trim = 1;
+	return 0;
+}
+
 /* 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
+	{ CFG_GLOBAL, "no-memory-trimming", mem_parse_global_no_mem_trim },
 	{ 0, NULL, NULL }
 }};