From 4f6d604ce616e70659b8494fd41ecd41e8fca30a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 14 Mar 2017 12:15:19 +1030 Subject: [PATCH 1/1] io: add io_flush_sync(). This is needed for emergency handling in lightningd: we want to output a (fatal) error packet on the socket, but we don't want to do so in the middle of another packet. Signed-off-by: Rusty Russell --- ccan/io/io.c | 34 +++++++++++ ccan/io/io.h | 18 ++++++ ccan/io/test/run-30-io_flush_sync.c | 91 +++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 ccan/io/test/run-30-io_flush_sync.c diff --git a/ccan/io/io.c b/ccan/io/io.c index 68b95e82..f298af70 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -483,3 +483,37 @@ struct io_plan *io_set_plan(struct io_conn *conn, enum io_direction dir, return plan; } + +bool io_flush_sync(struct io_conn *conn) +{ + struct io_plan *plan = &conn->plan[IO_OUT]; + bool ok; + + /* Not writing? Nothing to do. */ + if (plan->status != IO_POLLING) + return true; + + /* Synchronous please. */ + set_blocking(io_conn_fd(conn), true); + +again: + switch (plan->io(conn->fd.fd, &plan->arg)) { + case -1: + ok = false; + break; + /* Incomplete, try again. */ + case 0: + goto again; + case 1: + ok = true; + /* In case they come back. */ + set_always(conn, IO_OUT, plan->next, plan->next_arg); + break; + default: + /* IO should only return -1, 0 or 1 */ + abort(); + } + + set_blocking(io_conn_fd(conn), false); + return ok; +} diff --git a/ccan/io/io.h b/ccan/io/io.h index 1664df65..fe42b537 100644 --- a/ccan/io/io.h +++ b/ccan/io/io.h @@ -673,6 +673,24 @@ void *io_loop(struct timers *timers, struct timer **expired); */ int io_conn_fd(const struct io_conn *conn); +/** + * io_flush_sync - (synchronously) complete any outstanding output. + * @conn: the connection. + * + * This is generally used as an emergency escape, for example when we + * want to write an error message on a socket before terminating, but it may + * be in the middle of existing I/O. We don't want to service any other + * IO, either. + * + * This returns true if all pending output is complete, false on error. + * The next callback is not called on the conn, but will be as soon as + * io_loop() is called. + * + * See Also: + * io_close_taken_fd + */ +bool io_flush_sync(struct io_conn *conn); + /** * io_time_override - override the normal call for time. * @nowfn: the function to call. diff --git a/ccan/io/test/run-30-io_flush_sync.c b/ccan/io/test/run-30-io_flush_sync.c new file mode 100644 index 00000000..3ff8ff7b --- /dev/null +++ b/ccan/io/test/run-30-io_flush_sync.c @@ -0,0 +1,91 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +static size_t bytes_written; + +/* Should be called multiple times, since only writes 1 byte. */ +static int do_controlled_write(int fd, struct io_plan_arg *arg) +{ + ssize_t ret; + + ret = write(fd, arg->u1.cp, 1); + if (ret < 0) + return -1; + bytes_written += ret; + arg->u1.cp += ret; + arg->u2.s -= ret; + return arg->u2.s == 0; +} + +static int do_error(int fd, struct io_plan_arg *arg) +{ + errno = 1001; + return -1; +} + +static struct io_plan *conn_wait(struct io_conn *conn, void *unused) +{ + return io_wait(conn, conn, io_never, NULL); +} + +static struct io_plan *init_conn_writer(struct io_conn *conn, const char *str) +{ + struct io_plan_arg *arg = io_plan_arg(conn, IO_OUT); + + arg->u1.const_vp = str; + arg->u2.s = strlen(str); + + return io_set_plan(conn, IO_OUT, do_controlled_write, conn_wait, NULL); +} + +static struct io_plan *init_conn_reader(struct io_conn *conn, void *dst) +{ + /* Never actually succeeds. */ + return io_read(conn, dst, 1000, io_never, NULL); +} + +static struct io_plan *init_conn_error(struct io_conn *conn, void *unused) +{ + io_plan_arg(conn, IO_OUT); + return io_set_plan(conn, IO_OUT, do_error, io_never, NULL); +} + +int main(void) +{ + int fd = open("/dev/null", O_RDWR); + const tal_t *ctx = tal(NULL, char); + struct io_conn *conn; + + /* This is how many tests you plan to run */ + plan_tests(9); + + conn = io_new_conn(ctx, fd, init_conn_writer, "hello"); + ok1(bytes_written == 0); + + ok1(io_flush_sync(conn)); + ok1(bytes_written == strlen("hello")); + + /* This won't do anything */ + ok1(io_flush_sync(conn)); + ok1(bytes_written == strlen("hello")); + + /* It's reading, this won't do anything. */ + conn = io_new_conn(ctx, fd, init_conn_reader, ctx); + ok1(io_flush_sync(conn)); + ok1(bytes_written == strlen("hello")); + + /* Now test error state. */ + conn = io_new_conn(ctx, fd, init_conn_error, ctx); + ok1(!io_flush_sync(conn)); + ok1(errno == 1001); + + tal_free(ctx); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} -- 2.39.2