BUG/MEDIUM: spoe: Don't start new applet if there are enough idle ones

It is possible to start too many applets on sporadic burst of events after
an inactivity period. It is due to the way we estimate if a new applet must
be created or not. It is based on a frequency counter. We compare the events
processing rate against the number of events currently processed (in
progress or waiting to be processed). But we should also take care of the
number of idle applets.

We already track the number of idle applets, but it is global and not
per-thread. Thus we now also track the number of idle applets per-thread. It
is not a big deal because this fills a hole in the spoe_agent structure.
Thanks to this counter, we can refrain applets creation if there is enough
idle applets to handle currently processed events.

This patch should be backported to every stable versions.

(cherry picked from commit e99c43907c2365e74a0be0198b08434f68d5b80a)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit b950734a393419c9558edf8e11a25cf39e75973f)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 5d1ec1bab76798d738f509c36c7eac838f5e5445)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/include/haproxy/spoe-t.h b/include/haproxy/spoe-t.h
index 197f47b..38e3272 100644
--- a/include/haproxy/spoe-t.h
+++ b/include/haproxy/spoe-t.h
@@ -308,6 +308,7 @@
 		struct freq_ctr conn_per_sec;   /* connections per second */
 		struct freq_ctr err_per_sec;    /* connection errors per second */
 
+		unsigned int    idles;          /* # of idle applets */
 		struct eb_root  idle_applets;   /* idle SPOE applets available to process data */
 		struct list     applets;        /* all SPOE applets for this agent */
 		struct list     sending_queue;  /* Queue of streams waiting to send data */
diff --git a/src/flt_spoe.c b/src/flt_spoe.c
index 586079c..8ca3a14 100644
--- a/src/flt_spoe.c
+++ b/src/flt_spoe.c
@@ -1245,6 +1245,7 @@
 		if (appctx->st0 == SPOE_APPCTX_ST_IDLE) {
 			eb32_delete(&spoe_appctx->node);
 			_HA_ATOMIC_DEC(&agent->counters.idles);
+			agent->rt[tid].idles--;
 		}
 
 		appctx->st0 = SPOE_APPCTX_ST_END;
@@ -1458,6 +1459,7 @@
 
 		default:
 			_HA_ATOMIC_INC(&agent->counters.idles);
+			agent->rt[tid].idles++;
 			appctx->st0 = SPOE_APPCTX_ST_IDLE;
 			SPOE_APPCTX(appctx)->node.key = 0;
 			eb32_insert(&agent->rt[tid].idle_applets, &SPOE_APPCTX(appctx)->node);
@@ -1772,6 +1774,7 @@
 			goto next;
 		}
 		_HA_ATOMIC_INC(&agent->counters.idles);
+		agent->rt[tid].idles++;
 		appctx->st0 = SPOE_APPCTX_ST_IDLE;
 		eb32_insert(&agent->rt[tid].idle_applets, &SPOE_APPCTX(appctx)->node);
 	}
@@ -1937,6 +1940,7 @@
 
 		case SPOE_APPCTX_ST_IDLE:
 			_HA_ATOMIC_DEC(&agent->counters.idles);
+			agent->rt[tid].idles--;
 			eb32_delete(&SPOE_APPCTX(appctx)->node);
 			if (stopping &&
 			    LIST_ISEMPTY(&agent->rt[tid].sending_queue) &&
@@ -2078,8 +2082,8 @@
 	struct spoe_appctx *spoe_appctx;
 
 	/* Check if we need to create a new SPOE applet or not. */
-	if (!eb_is_empty(&agent->rt[tid].idle_applets) &&
-	    (agent->rt[tid].processing == 1 || agent->rt[tid].processing < read_freq_ctr(&agent->rt[tid].processing_per_sec)))
+	if (agent->rt[tid].processing < agent->rt[tid].idles  ||
+	    agent->rt[tid].processing < read_freq_ctr(&agent->rt[tid].processing_per_sec))
 		goto end;
 
 	SPOE_PRINTF(stderr, "%d.%06d [SPOE/%-15s] %s: stream=%p"
@@ -3135,6 +3139,7 @@
 		conf->agent->rt[i].engine_id    = NULL;
 		conf->agent->rt[i].frame_size   = conf->agent->max_frame_size;
 		conf->agent->rt[i].processing   = 0;
+		conf->agent->rt[i].idles        = 0;
 		LIST_INIT(&conf->agent->rt[i].applets);
 		LIST_INIT(&conf->agent->rt[i].sending_queue);
 		LIST_INIT(&conf->agent->rt[i].waiting_queue);