- set_current(conn);
- switch (conn->plan.io(conn->fd.fd, &conn->plan)) {
- case -1: /* Failure means a new plan: close up. */
- conn->plan = io_close();
- backend_plan_changed(conn);
- break;
- case 0: /* Keep going with plan. */
- break;
- case 1: /* Done: get next plan. */
- if (timeout_active(conn))
- backend_del_timeout(conn);
- /* In case they call io_duplex, clear our poll flags so
- * both sides don't seem to be both doing read or write
- * (See assert(!mask || pfd->events != mask) in poll.c) */
- conn->plan.pollflag = 0;
- conn->plan = conn->plan.next(conn, conn->plan.next_arg);
- backend_plan_changed(conn);
- }
- set_current(NULL);
-
- /* If it closed, close duplex if not already */
- if (!conn->plan.next && conn->duplex && conn->duplex->plan.next) {
- set_current(conn->duplex);
- conn->duplex->plan = io_close();
- backend_plan_changed(conn->duplex);
- set_current(NULL);
+ if (pollflags & POLLIN)
+ if (!do_plan(conn, &conn->plan[IO_IN], false))
+ return;
+
+ if (pollflags & POLLOUT)
+ /* If we're writing to a closed pipe, we need to wait for
+ * read to fail if we're duplex: we want to drain it! */
+ do_plan(conn, &conn->plan[IO_OUT],
+ conn->plan[IO_IN].status == IO_POLLING_NOTSTARTED
+ || conn->plan[IO_IN].status == IO_POLLING_STARTED);
+}
+
+void io_do_always(struct io_conn *conn)
+{
+ /* There's a corner case where the in next_plan wakes up the
+ * out, placing it in IO_ALWAYS and we end up processing it immediately,
+ * only to leave it in the always list.
+ *
+ * Yet we can't just process one, in case they are both supposed
+ * to be done, so grab state beforehand.
+ */
+ bool always_out = (conn->plan[IO_OUT].status == IO_ALWAYS);
+
+ if (conn->plan[IO_IN].status == IO_ALWAYS)
+ if (!next_plan(conn, &conn->plan[IO_IN]))
+ return;
+
+ if (always_out) {
+ /* You can't *unalways* a conn (except by freeing, in which
+ * case next_plan() returned false */
+ assert(conn->plan[IO_OUT].status == IO_ALWAYS);
+ next_plan(conn, &conn->plan[IO_OUT]);