MEDIUM: threads: automatically assign threads to groups

This takes care of unassigned threads groups and places unassigned
threads there, in a more or less balanced way. Too sparse allocations
may still fail though. For now with a maximum group number fixed to 1
nothing can really fail.
diff --git a/include/haproxy/thread.h b/include/haproxy/thread.h
index 14c88f0..c70108b 100644
--- a/include/haproxy/thread.h
+++ b/include/haproxy/thread.h
@@ -43,6 +43,7 @@
 void ha_tkill(unsigned int thr, int sig);
 void ha_tkillall(int sig);
 void ha_thread_relax(void);
+int thread_map_to_groups();
 extern int thread_cpus_enabled_at_boot;
 
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index fe49811..f8e777e 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2439,6 +2439,11 @@
 	if (!global.nbtgroups)
 		global.nbtgroups = 1;
 
+	if (thread_map_to_groups() < 0) {
+		err_code |= ERR_ALERT | ERR_FATAL;
+		goto out;
+	}
+
 	pool_head_requri = create_pool("requri", global.tune.requri_len , MEM_F_SHARED);
 
 	pool_head_capture = create_pool("capture", global.tune.cookie_len, MEM_F_SHARED);
diff --git a/src/thread.c b/src/thread.c
index 7374a8e..2a7d3af 100644
--- a/src/thread.c
+++ b/src/thread.c
@@ -1001,6 +1001,82 @@
 #endif // USE_THREAD
 
 
+/* scans the configured thread mapping and establishes the final one. Returns <0
+ * on failure, >=0 on success.
+ */
+int thread_map_to_groups()
+{
+	int t, g, ut, ug;
+	int q, r;
+
+	ut = ug = 0; // unassigned threads & groups
+
+	for (t = 0; t < global.nbthread; t++) {
+		if (!ha_thread_info[t].tg)
+			ut++;
+	}
+
+	for (g = 0; g < global.nbtgroups; g++) {
+		if (!ha_tgroup_info[g].count)
+			ug++;
+	}
+
+	if (ug > ut) {
+		ha_alert("More unassigned thread-groups (%d) than threads (%d). Please reduce thread-groups\n", ug, ut);
+		return -1;
+	}
+
+	/* look for first unassigned thread */
+	for (t = 0; t < global.nbthread && ha_thread_info[t].tg; t++)
+		;
+
+	/* assign threads to empty groups */
+	for (g = 0; ug && ut; ) {
+		/* due to sparse thread assignment we can end up with more threads
+		 * per group on last assigned groups than former ones, so we must
+		 * always try to pack the maximum remaining ones together first.
+		 */
+		q = ut / ug;
+		r = ut % ug;
+		if ((q + !!r) > MAX_THREADS_PER_GROUP) {
+			ha_alert("Too many remaining unassigned threads (%d) for thread groups (%d). Please increase thread-groups or make sure to keep thread numbers contiguous\n", ug, ut);
+			return -1;
+		}
+
+		/* thread <t> is the next unassigned one. Let's look for next
+		 * unassigned group, we know there are some left
+		 */
+		while (ut >= ug && ha_tgroup_info[g].count)
+			g++;
+
+		/* group g is unassigned, try to fill it with consecutive threads */
+		while (ut && ut >= ug && ha_tgroup_info[g].count < q + !!r &&
+		       (!ha_tgroup_info[g].count || t == ha_tgroup_info[g].base + ha_tgroup_info[g].count)) {
+
+			if (!ha_tgroup_info[g].count) {
+				/* assign new group */
+				ha_tgroup_info[g].base = t;
+				ug--;
+			}
+
+			ha_tgroup_info[g].count++;
+			ha_thread_info[t].tg = &ha_tgroup_info[g];
+
+			ut--;
+			/* switch to next unassigned thread */
+			while (++t < global.nbthread && ha_thread_info[t].tg)
+				;
+		}
+	}
+
+	if (ut) {
+		ha_alert("Remaining unassigned threads found (%d) because all groups are in use. Please increase 'thread-groups', reduce 'nbthreads' or remove or extend 'thread-group' enumerations.\n", ut);
+		return -1;
+	}
+
+	return 0;
+}
+
 /* Parse the "nbthread" global directive, which takes an integer argument that
  * contains the desired number of threads.
  */