MEDIUM: mux_fcgi: Implement the takeover() method.

Implement a takeover() method in the mux_fcgi, so that other threads may
take an idle connection over if they need it.
diff --git a/src/mux_fcgi.c b/src/mux_fcgi.c
index 904ce85..993e02c 100644
--- a/src/mux_fcgi.c
+++ b/src/mux_fcgi.c
@@ -2908,17 +2908,55 @@
 /* this is the tasklet referenced in fconn->wait_event.tasklet */
 static struct task *fcgi_io_cb(struct task *t, void *ctx, unsigned short status)
 {
-	struct fcgi_conn *fconn = ctx;
+	struct connection *conn;
+	struct fcgi_conn *fconn;
+	struct tasklet *tl = (struct tasklet *)t;
+	int conn_in_list;
 	int ret = 0;
 
+
+	HA_SPIN_LOCK(OTHER_LOCK, &toremove_lock[tid]);
+	if (tl->context == NULL) {
+		/* The connection has been taken over by another thread,
+		 * we're no longer responsible for it, so just free the
+		 * tasklet, and do nothing.
+		 */
+		HA_SPIN_UNLOCK(OTHER_LOCK, &toremove_lock[tid]);
+		tasklet_free(tl);
+		return NULL;
+
+	}
+	fconn = ctx;
+	conn = fconn->conn;
+
+	TRACE_POINT(FCGI_EV_FCONN_WAKE, conn);
+
-	TRACE_POINT(FCGI_EV_FCONN_WAKE, fconn->conn);
+	conn_in_list = conn->flags & CO_FL_LIST_MASK;
+	if (conn_in_list)
+		MT_LIST_DEL(&conn->list);
+
+	HA_SPIN_UNLOCK(OTHER_LOCK, &toremove_lock[tid]);
 
 	if (!(fconn->wait_event.events & SUB_RETRY_SEND))
 		ret = fcgi_send(fconn);
 	if (!(fconn->wait_event.events & SUB_RETRY_RECV))
 		ret |= fcgi_recv(fconn);
 	if (ret || b_data(&fconn->dbuf))
-		fcgi_process(fconn);
+		ret = fcgi_process(fconn);
+
+	/* If we were in an idle list, we want to add it back into it,
+	 * unless fcgi_process() returned -1, which mean it has destroyed
+	 * the connection (testing !ret is enough, if fcgi_process() wasn't
+	 * called then ret will be 0 anyway.
+	 */
+	if (!ret && conn_in_list) {
+		struct server *srv = objt_server(conn->target);
+
+		if (conn_in_list == CO_FL_SAFE_LIST)
+			MT_LIST_ADDQ(&srv->safe_conns[tid], &conn->list);
+		else
+			MT_LIST_ADDQ(&srv->idle_conns[tid], &conn->list);
+	}
 	return NULL;
 }
 
@@ -3048,6 +3086,22 @@
 		return t;
 	}
 
+	/* We're about to destroy the connection, so make sure nobody attempts
+	 * to steal it from us.
+	 */
+	HA_SPIN_LOCK(OTHER_LOCK, &toremove_lock[tid]);
+
+	if (fconn && fconn->conn->flags & CO_FL_LIST_MASK)
+		MT_LIST_DEL(&fconn->conn->list);
+
+	/* Somebody already stole the connection from us, so we should not
+	 * free it, we just have to free the task.
+	 */
+	if (!t->context)
+		fconn = NULL;
+
+	HA_SPIN_UNLOCK(OTHER_LOCK, &toremove_lock[tid]);
+
 	task_destroy(t);
 
 	if (!fconn) {
@@ -4009,6 +4063,50 @@
 			chunk_appendf(msg, " .cs.flg=0x%08x .cs.data=%p",
 				      fstrm->cs->flags, fstrm->cs->data);
 	}
+}
+
+/* Migrate the the connection to the current thread.
+ * Return 0 if successful, non-zero otherwise.
+ * Expected to be called with the old thread lock held.
+ */
+static int fcgi_takeover(struct connection *conn)
+{
+	struct fcgi_conn *fcgi = conn->ctx;
+
+	if (fd_takeover(conn->handle.fd, conn) != 0)
+		return -1;
+	if (fcgi->wait_event.events)
+		fcgi->conn->xprt->unsubscribe(fcgi->conn, fcgi->conn->xprt_ctx,
+		    fcgi->wait_event.events, &fcgi->wait_event);
+	/* To let the tasklet know it should free itself, and do nothing else,
+	 * set its context to NULL;
+	 */
+	fcgi->wait_event.tasklet->context = NULL;
+	tasklet_wakeup(fcgi->wait_event.tasklet);
+	if (fcgi->task) {
+		fcgi->task->context = NULL;
+		/* Wake the task, to let it free itself */
+		task_wakeup(fcgi->task, TASK_WOKEN_OTHER);
+
+		fcgi->task = task_new(tid_bit);
+		if (!fcgi->task) {
+			fcgi_release(fcgi);
+			return -1;
+		}
+		fcgi->task->process = fcgi_timeout_task;
+		fcgi->task->context = fcgi;
+	}
+	fcgi->wait_event.tasklet = tasklet_new();
+	if (!fcgi->wait_event.tasklet) {
+		fcgi_release(fcgi);
+		return -1;
+	}
+	fcgi->wait_event.tasklet->process = fcgi_io_cb;
+	fcgi->wait_event.tasklet->context = fcgi;
+	fcgi->conn->xprt->subscribe(fcgi->conn, fcgi->conn->xprt_ctx,
+		                    SUB_RETRY_RECV, &fcgi->wait_event);
+
+	return 0;
 }
 
 /****************************************/
@@ -4033,6 +4131,7 @@
 	.shutw         = fcgi_shutw,
 	.ctl           = fcgi_ctl,
 	.show_fd       = fcgi_show_fd,
+	.takeover      = fcgi_takeover,
 	.flags         = MX_FL_HTX,
 	.name          = "FCGI",
 };