MINOR: init: pre-allocate kernel data structures on init

The Linux kernel maintains data structures to track a processes' open file
descriptors, and it expands these structures as necessary when FD usage grows
(at every FD=2^X starting at 64). However when threading is in use, during
expansion the kernel will pause (observed up to 47ms) while it waits for thread
synchronization (see https://bugzilla.kernel.org/show_bug.cgi?id=217366).

This change addresses the issue and avoids the random pauses by opening the
maximum file descriptor during initialization, so that expansion will not occur
while processing traffic.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index f8d44cb..5f8474a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1107,6 +1107,7 @@
    - pidfile
    - pp2-never-send-local
    - presetenv
+   - prealloc-fd
    - resetenv
    - set-dumpable
    - set-var
@@ -2084,6 +2085,12 @@
   in the configuration file sees the new value. See also "setenv", "resetenv",
   and "unsetenv".
 
+prealloc-fd
+  Performs a one-time open of the maximum file descriptor which results in a
+  pre-allocation of the kernel's data structures. This prevents short pauses
+  when nbthread>1 and HAProxy opens a file descriptor which requires the kernel
+  to expand its data structures.
+
 resetenv [<name> ...]
   Removes all environment variables except the ones specified in argument. It
   allows to use a clean controlled environment before setting new values with
diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h
index e7d02fe..0bcfa57 100644
--- a/include/haproxy/global-t.h
+++ b/include/haproxy/global-t.h
@@ -188,6 +188,7 @@
 	} unix_bind;
 	struct proxy *cli_fe;           /* the frontend holding the stats settings */
 	int numa_cpu_mapping;
+	int prealloc_fd;
 	int cfg_curr_line;              /* line number currently being parsed */
 	const char *cfg_curr_file;      /* config file currently being parsed or NULL */
 	char *cfg_curr_section;         /* config section name currently being parsed or NULL */
diff --git a/src/cfgparse-global.c b/src/cfgparse-global.c
index cc643f4..30334ba 100644
--- a/src/cfgparse-global.c
+++ b/src/cfgparse-global.c
@@ -1357,3 +1357,21 @@
 	return err_code;
 }
 
+static int cfg_parse_prealloc_fd(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;
+
+	global.prealloc_fd = 1;
+
+	return 0;
+}
+
+static struct cfg_kw_list cfg_kws = {ILH, {
+	{ CFG_GLOBAL, "prealloc-fd", cfg_parse_prealloc_fd },
+	{ 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
diff --git a/src/haproxy.c b/src/haproxy.c
index 0eb78d8..0405647 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -3522,6 +3522,14 @@
 				 global.maxsock);
 	}
 
+	if (global.prealloc_fd && fcntl((int)limit.rlim_cur - 1, F_GETFD) == -1) {
+		if (dup2(0, (int)limit.rlim_cur - 1) == -1)
+			ha_warning("[%s.main()] Unable to preallocate file descriptor %lu : %s",
+			           argv[0], limit.rlim_cur-1, strerror(errno));
+		else
+			close((int)limit.rlim_cur - 1);
+	}
+
 	/* update the ready date a last time to also account for final setup time */
 	clock_update_date(0, 1);
 	clock_adjust_now_offset();