[peerdist] Reduce number of concurrent requests to origin server

Origin server requests are likely to be significantly more expensive
than local peer requests: they consume sparse uplink bandwidth and, if
using HTTPS, substantial local resources.

Start with a low number of concurrent block downloads and adjust this
number in response to the availability of local peers, in order to
avoid hammering the origin server with the full concurrency limit.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/include/ipxe/peermux.h b/src/include/ipxe/peermux.h
index 54acbfe..8970759 100644
--- a/src/include/ipxe/peermux.h
+++ b/src/include/ipxe/peermux.h
@@ -18,6 +18,9 @@
 #include <ipxe/xferbuf.h>
 #include <ipxe/pccrc.h>
 
+/** Minimum number of concurrent block downloads */
+#define PEERMUX_MIN_BLOCKS 2
+
 /** Maximum number of concurrent block downloads */
 #define PEERMUX_MAX_BLOCKS 32
 
@@ -69,6 +72,10 @@
 
 	/** Block download initiation process */
 	struct process process;
+	/** Number of concurrent block downloads */
+	unsigned int count;
+	/** Maximum number of concurrent block downloads */
+	unsigned int limit;
 	/** List of busy block downloads */
 	struct list_head busy;
 	/** List of idle block downloads */
diff --git a/src/net/peermux.c b/src/net/peermux.c
index a391ed3..c354f21 100644
--- a/src/net/peermux.c
+++ b/src/net/peermux.c
@@ -92,7 +92,8 @@
 	if ( stats->total ) {
 		percentage = ( ( 100 * stats->local ) / stats->total );
 		snprintf ( progress->message, sizeof ( progress->message ),
-			   "%3d%% from %d peers", percentage, stats->peers );
+			   "%3d%% from %d peers (x%d)", percentage,
+			   stats->peers, peermux->limit );
 	}
 
 	return 0;
@@ -184,14 +185,17 @@
 	unsigned int next_block;
 	int rc;
 
-	/* Stop initiation process if all block downloads are busy */
-	peermblk = list_first_entry ( &peermux->idle,
-				      struct peerdist_multiplexed_block, list );
-	if ( ! peermblk ) {
+	/* Stop initiation process if we have reached the concurrency limit */
+	if ( peermux->count >= peermux->limit ) {
 		process_del ( &peermux->process );
 		return;
 	}
 
+	/* Get next available idle block download */
+	peermblk = list_first_entry ( &peermux->idle,
+				      struct peerdist_multiplexed_block, list );
+	assert ( peermblk != NULL );
+
 	/* Increment block index */
 	next_block = ( block->index + 1 );
 
@@ -251,6 +255,7 @@
 	/* Move to list of busy block downloads */
 	list_del ( &peermblk->list );
 	list_add_tail ( &peermblk->list, &peermux->busy );
+	peermux->count++;
 
 	return;
 
@@ -319,12 +324,19 @@
 	if ( count > stats->peers )
 		stats->peers = count;
 
-	/* Update block counts */
-	if ( peer )
+	/* Update block counts and concurrency limit */
+	if ( peer ) {
 		stats->local++;
+		if ( peermux->limit < PEERMUX_MAX_BLOCKS )
+			peermux->limit++;
+	} else {
+		if ( peermux->limit > PEERMUX_MIN_BLOCKS )
+			peermux->limit--;
+	}
 	stats->total++;
-	DBGC2 ( peermux, "PEERMUX %p downloaded %d/%d from %d peers\n",
-		peermux, stats->local, stats->total, stats->peers );
+	DBGC2 ( peermux, "PEERMUX %p downloaded %d/%d from %d peers (x%d)\n",
+		peermux, stats->local, stats->total, stats->peers,
+		peermux->limit );
 }
 
 /**
@@ -340,6 +352,7 @@
 	/* Move to list of idle downloads */
 	list_del ( &peermblk->list );
 	list_add_tail ( &peermblk->list, &peermux->idle );
+	peermux->count--;
 
 	/* If any error occurred, terminate the whole multiplexer */
 	if ( rc != 0 ) {
@@ -426,6 +439,7 @@
 			       &peermux->cache.info.raw.data );
 	process_init_stopped ( &peermux->process, &peermux_process_desc,
 			       &peermux->refcnt );
+	peermux->limit = PEERMUX_MIN_BLOCKS;
 	INIT_LIST_HEAD ( &peermux->busy );
 	INIT_LIST_HEAD ( &peermux->idle );
 	for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ ) {