BUG/MEDIUM: stconn: Schedule a shutw on shutr if data must be sent first

The commit 7f59d68fe ("BUG/MEDIIM: stconn: Flush output data before
forwarding close to write side") introduced a regression. When the read side
is closed, the close is not forwarded to the write side if there are some
pending outgoind data. The idea is to foward data first and the close the
write side. However, when fast-forwarding is enabled and last data block is
received with the read0, the close is never forwarded.

We cannot revert the commit above because it really fix an issue. However,
we can schedule the shutdown for write by setting CF_SHUTW_NOW flag on the
write side. Indeed, it is the purpose of this flag.

To not replicate ugly and hardly maintainable code block at different places
in stconn.c, an helper function is used. Thus, sc_cond_forward_shutw() must
be called to know if the close can be fowarded or not. It returns 1 if it is
possible. In this case, the caller is responsible to forward the close to
the write side. Otherwise, if the close cannot be forwarded, 0 is
returned. It happens when it should not be performed at all. Or when it
should only be delayed, waiting for the input channel to be flushed. In this
last case, the CF_SHUTW_NOW flag is set in the output channel.

This patch should fix the issue #2033. It must be backported with the commit
above, thus at least as far as 2.2.

(cherry picked from commit eb3f26d5a023431d28107d29a60f0e923dcfc85e)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit 8c5ac22886b7786234c13c4701c8b0f5503ae43f)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit e537945e72d08425ba42860b356621704d0a77e3)
[cf: Applied on stream_interface.c. The stream-connector is replaced by the
     stream-interface but no other change]
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 7225a7e5b3b6f81424b3953c14b7185fde7310bb)
Signed-off-by: Willy Tarreau <w@1wt.eu>
diff --git a/src/stream_interface.c b/src/stream_interface.c
index 267b33f..5336fee 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -154,6 +154,30 @@
 	channel_shutr_now(oc);
 }
 
+/* Conditionnaly forward the close to the wirte side. It return 1 if it can be
+ * forwarded. It is the caller responsibility to forward the close to the write
+ * side. Otherwise, 0 is returned. In this case, CF_SHUTW_NOW flag may be set on
+ * the channel if we are only waiting for the outgoing data to be flushed.
+ */
+static inline int si_cond_forward_shutw(struct stream_interface *si)
+{
+	/* The close must not be forwarded */
+	if (!(si_ic(si)->flags & CF_SHUTR) || !(si->flags & SI_FL_NOHALF))
+		return 0;
+
+	if (!channel_is_empty(si_ic(si))) {
+		/* the close to the write side cannot be forwarded now because
+		 * we should flush outgoing data first. But instruct the output
+		 * channel it should be done ASAP.
+		 */
+		channel_shutw_now(si_oc(si));
+		return 0;
+	}
+
+	/* the close can be immediately forwarded to the write side */
+	return 1;
+}
+
 /*
  * This function performs a shutdown-read on a detached stream interface in a
  * connected or init state (it does nothing for other states). It either shuts
@@ -178,7 +202,7 @@
 		si->state = SI_ST_DIS;
 		si->exp = TICK_ETERNITY;
 	}
-	else if (si->flags & SI_FL_NOHALF && channel_is_empty(ic)) {
+	else if (si_cond_forward_shutw(si)) {
 		/* we want to immediately forward this close to the write side */
 		return stream_int_shutw(si);
 	}
@@ -1023,7 +1047,7 @@
 		si->state = SI_ST_DIS;
 		si->exp = TICK_ETERNITY;
 	}
-	else if (si->flags & SI_FL_NOHALF && channel_is_empty(ic)) {
+	else if (si_cond_forward_shutw(si)) {
 		/* we want to immediately forward this close to the write side */
 		return stream_int_shutw_conn(si);
 	}
@@ -1572,7 +1596,7 @@
 	if (oc->flags & CF_SHUTW)
 		goto do_close;
 
-	if (si->flags & SI_FL_NOHALF && channel_is_empty(ic)) {
+	if (si_cond_forward_shutw(si)) {
 		/* we want to immediately forward this close to the write side */
 		/* force flag on ssl to keep stream in cache */
 		cs_shutw(cs, CS_SHW_SILENT);
@@ -1660,7 +1684,7 @@
 		si->state = SI_ST_DIS;
 		si->exp = TICK_ETERNITY;
 	}
-	else if (si->flags & SI_FL_NOHALF && channel_is_empty(ic)) {
+	else if (si_cond_forward_shutw(si)) {
 		/* we want to immediately forward this close to the write side */
 		return stream_int_shutw_applet(si);
 	}