From 609670cc6a5fc9ba819cacca251cde1709c053d6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 4 Jan 2022 13:06:34 +1030 Subject: [PATCH] io: add new io_sock_shutdown helper. Turns out that there's no good way to flush a socket: https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable Signed-off-by: Rusty Russell --- ccan/io/io.c | 12 ++++++++++++ ccan/io/io.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/ccan/io/io.c b/ccan/io/io.c index 36dcb81e..12df06d8 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -529,6 +529,18 @@ bool io_plan_out_started(const struct io_conn *conn) return conn->plan[IO_OUT].status == IO_POLLING_STARTED; } +/* Despite being a TCP expert, I missed the full extent of this + * problem. The legendary ZmnSCPxj implemented it (with the URL + * pointing to the explanation), and I imitate that here. */ +struct io_plan *io_sock_shutdown(struct io_conn *conn) +{ + if (shutdown(io_conn_fd(conn), SHUT_WR) != 0) + return io_close(conn); + + /* And leave unset .*/ + return &conn->plan[IO_IN]; +} + bool io_flush_sync(struct io_conn *conn) { struct io_plan *plan = &conn->plan[IO_OUT]; diff --git a/ccan/io/io.h b/ccan/io/io.h index e6905fb9..1197626f 100644 --- a/ccan/io/io.h +++ b/ccan/io/io.h @@ -389,6 +389,40 @@ struct io_plan *io_out_always_(struct io_conn *conn, void *), void *arg); +/** + * io_sock_shutdown - start socket close process (flushes TCP sockets). + * @conn: the connection the plan is for + * + * Simply closing a TCP socket can lose data; unfortunately you should + * shutdown(SHUT_WR) and wait for the other side to see this and close. + * Of course, you also need to set a timer, in case it doesn't (you may + * already have some responsiveness timer, of course). + * + * On error, is equivalent to io_close(). + * + * Example: + * #include + * + * // Timer infra needs wrapper to contain extra data. + * struct timeout_timer { + * struct timer t; + * struct io_conn *conn; + * }; + * static struct timers timers; + * + * static struct io_plan *flush_and_close(struct io_conn *conn) + * { + * struct timeout_timer *timeout; + * // Freed if conn closes normally. + * timeout = tal(conn, struct timeout_timer); + * timeout->conn = conn; + * timeout->t = conn; + * timer_addrel(&timers, &timeout->t, time_from_sec(5)); + * return io_sock_shutdown(conn); + * } + */ +struct io_plan *io_sock_shutdown(struct io_conn *conn); + /** * io_connect - create an asynchronous connection to a listening socket. * @conn: the connection that plan is for. -- 2.39.2