From d493282092b509fac8e03208744d9d196992e29a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 24 Jun 2024 12:15:03 +0930 Subject: [PATCH] ccan: call io routines repeatedly until EAGAIN. Benchmark time halved. ``` Finished: 68697129usec ``` perf shows 75% of time in libc_write. Signed-off-by: Rusty Russell --- ccan/io/io.c | 77 +++++++++++++++++++----- ccan/io/test/run-43-io_plan_in_started.c | 10 ++- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/ccan/io/io.c b/ccan/io/io.c index 3d00de04..58ae19bd 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -384,9 +384,19 @@ void io_wake(const void *wait) backend_wake(wait); } -/* Returns false if this should not be touched (eg. freed). */ -static bool do_plan(struct io_conn *conn, struct io_plan *plan, - bool idle_on_epipe) +enum plan_result { + /* Destroyed, do not touch */ + FREED, + /* Worked, call again. */ + KEEP_GOING, + /* Failed with EAGAIN or did partial. */ + EXHAUSTED, + /* No longer interested in read (or write) */ + UNINTERESTED +}; + +static enum plan_result do_plan(struct io_conn *conn, struct io_plan *plan, + bool idle_on_epipe) { /* We shouldn't have polled for this event if this wasn't true! */ assert(plan->status == IO_POLLING_NOTSTARTED @@ -394,18 +404,26 @@ static bool do_plan(struct io_conn *conn, struct io_plan *plan, switch (plan->io(conn->fd.fd, &plan->arg)) { case -1: + /* This is expected, as we call optimistically! */ + if (errno == EAGAIN) + return EXHAUSTED; if (errno == EPIPE && idle_on_epipe) { plan->status = IO_UNSET; backend_new_plan(conn); - return false; + return UNINTERESTED; } io_close(conn); - return false; + return FREED; case 0: plan->status = IO_POLLING_STARTED; - return true; + /* If it started but didn't finish, don't call again. */ + return EXHAUSTED; case 1: - return next_plan(conn, plan); + if (!next_plan(conn, plan)) + return FREED; + if (plan->status == IO_POLLING_NOTSTARTED) + return KEEP_GOING; + return UNINTERESTED; default: /* IO should only return -1, 0 or 1 */ abort(); @@ -414,16 +432,43 @@ static bool do_plan(struct io_conn *conn, struct io_plan *plan, void io_ready(struct io_conn *conn, int pollflags) { - if (pollflags & POLLIN) - if (!do_plan(conn, &conn->plan[IO_IN], false)) - return; + enum plan_result res; + + if (pollflags & POLLIN) { + for (;;) { + res = do_plan(conn, &conn->plan[IO_IN], false); + switch (res) { + case FREED: + return; + case EXHAUSTED: + case UNINTERESTED: + goto try_write; + case KEEP_GOING: + continue; + } + abort(); + } + } - 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); +try_write: + if (pollflags & POLLOUT) { + for (;;) { + /* 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! */ + res = do_plan(conn, &conn->plan[IO_OUT], + conn->plan[IO_IN].status == IO_POLLING_NOTSTARTED + || conn->plan[IO_IN].status == IO_POLLING_STARTED); + switch (res) { + case FREED: + case EXHAUSTED: + case UNINTERESTED: + return; + case KEEP_GOING: + continue; + } + abort(); + } + } } void io_do_always(struct io_plan *plan) diff --git a/ccan/io/test/run-43-io_plan_in_started.c b/ccan/io/test/run-43-io_plan_in_started.c index f63f8779..74fcf55c 100644 --- a/ccan/io/test/run-43-io_plan_in_started.c +++ b/ccan/io/test/run-43-io_plan_in_started.c @@ -18,9 +18,17 @@ static struct io_plan *init_in_conn(struct io_conn *conn, char *buf) return io_read(conn, buf, 2, in_conn_done, NULL); } +/* Every second time we say we're exhausted */ static int do_nothing(int fd, struct io_plan_arg *arg) { - return 1; + static bool read_once; + + read_once = !read_once; + if (read_once) + return 1; + + errno = EAGAIN; + return -1; } static struct io_plan *dummy_write(struct io_conn *conn, -- 2.39.5