[BUG] stream_sock: check for shut{r,w} before refreshing some timeouts
Under some circumstances, it appears possible to refresh a timeout
just after a side has been shut. For instance, if poll() plans to
call both read and write, and the read side calls chk_snd() which
in turn causes a shutw to occur, then stream_sock_write could update
its write timeout. The same problem happens the other way.
The timeout checks will then not catch these cases because they
ignore timeouts in case of shut{r,w}.
This is very likely to be the major cause of the 100% CPU usages
reported by Bart Bobrowski.
The fix consists in always ensuring that a side is not shut before
updating its timeout.
diff --git a/src/stream_sock.c b/src/stream_sock.c
index 9337df8..82bf8ca 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -242,6 +242,10 @@
if ((fdtab[fd].ev & (FD_POLL_IN|FD_POLL_HUP)) == FD_POLL_HUP)
goto out_shutdown_r;
+ /* maybe we were called immediately after an asynchronous shutr */
+ if (b->flags & BF_SHUTR)
+ goto out_wakeup;
+
#if defined(CONFIG_HAP_LINUX_SPLICE)
if (b->to_forward && b->flags & BF_KERN_SPLICING) {
@@ -461,7 +465,7 @@
EV_FD_CLR(fd, DIR_RD);
b->rex = TICK_ETERNITY;
}
- else if ((b->flags & (BF_READ_PARTIAL|BF_FULL|BF_READ_NOEXP)) == BF_READ_PARTIAL)
+ else if ((b->flags & (BF_SHUTR|BF_READ_PARTIAL|BF_FULL|BF_READ_NOEXP)) == BF_READ_PARTIAL)
b->rex = tick_add_ifset(now_ms, b->rto);
/* we have to wake up if there is a special event or if we don't have
@@ -647,6 +651,10 @@
if (fdtab[fd].state == FD_STERROR || (fdtab[fd].ev & FD_POLL_ERR))
goto out_error;
+ /* we might have been called just after an asynchronous shutw */
+ if (b->flags & BF_SHUTW)
+ goto out_wakeup;
+
if (likely(!(b->flags & BF_EMPTY))) {
/* OK there are data waiting to be sent */
retval = stream_sock_write_loop(si, b);
@@ -704,7 +712,7 @@
goto out_wakeup;
}
- if (b->flags & BF_EMPTY)
+ if ((b->flags & (BF_EMPTY|BF_SHUTW)) == BF_EMPTY)
si->flags |= SI_FL_WAIT_DATA;
EV_FD_CLR(fd, DIR_WR);
@@ -730,7 +738,7 @@
}
/* the producer might be waiting for more room to store data */
- if (likely((b->flags & (BF_WRITE_PARTIAL|BF_FULL)) == BF_WRITE_PARTIAL &&
+ if (likely((b->flags & (BF_SHUTW|BF_WRITE_PARTIAL|BF_FULL)) == BF_WRITE_PARTIAL &&
(b->prod->flags & SI_FL_WAIT_ROOM)))
b->prod->chk_rcv(b->prod);
@@ -786,7 +794,6 @@
}
/* fall through */
case SI_ST_CON:
- si->flags &= ~SI_FL_WAIT_ROOM;
/* we may have to close a pending connection, and mark the
* response buffer as shutr
*/
@@ -795,6 +802,7 @@
case SI_ST_CER:
si->state = SI_ST_DIS;
default:
+ si->flags &= ~SI_FL_WAIT_ROOM;
si->ib->flags |= BF_SHUTR;
si->ib->rex = TICK_ETERNITY;
si->exp = TICK_ETERNITY;
@@ -893,13 +901,13 @@
EV_FD_COND_S(fd, DIR_WR);
if (!tick_isset(ob->wex) || ob->flags & BF_WRITE_ACTIVITY) {
ob->wex = tick_add_ifset(now_ms, ob->wto);
- if (tick_isset(ob->wex) && tick_isset(ib->rex)) {
+ if (tick_isset(ib->rex)) {
/* Note: depending on the protocol, we don't know if we're waiting
* for incoming data or not. So in order to prevent the socket from
* expiring read timeouts during writes, we refresh the read timeout,
* except if it was already infinite.
*/
- ib->rex = ob->wex;
+ ib->rex = tick_add_ifset(now_ms, ib->rto);
}
}
}
@@ -991,8 +999,8 @@
stream_sock_shutw(si);
goto out_wakeup;
}
-
- if ((ob->flags & (BF_EMPTY|BF_HIJACK|BF_WRITE_ENA)) == (BF_EMPTY|BF_WRITE_ENA))
+
+ if ((ob->flags & (BF_SHUTW|BF_EMPTY|BF_HIJACK|BF_WRITE_ENA)) == (BF_EMPTY|BF_WRITE_ENA))
si->flags |= SI_FL_WAIT_DATA;
ob->wex = TICK_ETERNITY;
}