From 31c816a6a9a2037d8860d56814835d9ac488d52f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 29 Dec 2016 15:01:32 +1030 Subject: [PATCH] io: allow freeing of io_conn at any time. io_close() currently marks the io_conn for freeing, but doesn't actually do it. This is a problem for tal() users, because we can't just call it in the parent's constructor. Make io_close() just tal_free() + return &io_conn_freed (a magic io_plan pointer). Signed-off-by: Rusty Russell --- ccan/io/backend.h | 9 ++---- ccan/io/io.c | 52 ++++++++++++++------------------ ccan/io/poll.c | 60 +++++++++++++------------------------ ccan/io/test/run-18-errno.c | 2 +- 4 files changed, 46 insertions(+), 77 deletions(-) diff --git a/ccan/io/backend.h b/ccan/io/backend.h index aace0f2b..f57d2495 100644 --- a/ccan/io/backend.h +++ b/ccan/io/backend.h @@ -31,9 +31,7 @@ enum io_plan_status { /* Waiting for io_wake */ IO_WAITING, /* Always do this. */ - IO_ALWAYS, - /* Closing (both plans will be the same). */ - IO_CLOSING + IO_ALWAYS }; /** @@ -60,8 +58,8 @@ struct io_plan { struct io_conn { struct fd fd; - /* always and closing lists. */ - struct list_node always, closing; + /* always list. */ + struct list_node always; void (*finish)(struct io_conn *, void *arg); void *finish_arg; @@ -75,7 +73,6 @@ bool add_listener(struct io_listener *l); bool add_conn(struct io_conn *c); bool add_duplex(struct io_conn *c); void del_listener(struct io_listener *l); -void backend_new_closing(struct io_conn *conn); void backend_new_always(struct io_conn *conn); void backend_new_plan(struct io_conn *conn); void remove_from_always(struct io_conn *conn); diff --git a/ccan/io/io.c b/ccan/io/io.c index 83f933ac..4c41c93a 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -14,6 +14,8 @@ void *io_loop_return; +struct io_plan io_conn_freed; + struct io_listener *io_new_listener_(const tal_t *ctx, int fd, struct io_plan *(*init)(struct io_conn *, void *), @@ -35,8 +37,6 @@ struct io_listener *io_new_listener_(const tal_t *ctx, int fd, void io_close_listener(struct io_listener *l) { - close(l->fd.fd); - del_listener(l); tal_free(l); } @@ -45,7 +45,8 @@ static struct io_plan *io_never_called(struct io_conn *conn, void *arg) abort(); } -static void next_plan(struct io_conn *conn, struct io_plan *plan) +/* Returns false if conn was freed. */ +static bool next_plan(struct io_conn *conn, struct io_plan *plan) { struct io_plan *(*next)(struct io_conn *, void *arg); @@ -57,6 +58,9 @@ static void next_plan(struct io_conn *conn, struct io_plan *plan) plan = next(conn, plan->next_arg); + if (plan == &io_conn_freed) + return false; + /* It should have set a plan inside this conn (or duplex) */ assert(plan == &conn->plan[IO_IN] || plan == &conn->plan[IO_OUT] @@ -65,6 +69,7 @@ static void next_plan(struct io_conn *conn, struct io_plan *plan) || conn->plan[IO_OUT].status != IO_UNSET); backend_new_plan(conn); + return true; } static void set_blocking(int fd, bool block) @@ -93,7 +98,6 @@ struct io_conn *io_new_conn_(const tal_t *ctx, int fd, conn->finish = NULL; conn->finish_arg = NULL; list_node_init(&conn->always); - list_node_init(&conn->closing); if (!add_conn(conn)) return tal_free(conn); @@ -106,7 +110,8 @@ struct io_conn *io_new_conn_(const tal_t *ctx, int fd, conn->plan[IO_IN].next = init; conn->plan[IO_IN].next_arg = arg; - next_plan(conn, &conn->plan[IO_IN]); + if (!next_plan(conn, &conn->plan[IO_IN])) + return NULL; return conn; } @@ -355,24 +360,20 @@ void io_wake(const void *wait) backend_wake(wait); } -static int do_plan(struct io_conn *conn, struct io_plan *plan) +/* Returns false if this has been freed. */ +static bool do_plan(struct io_conn *conn, struct io_plan *plan) { - /* Someone else might have called io_close() on us. */ - if (plan->status == IO_CLOSING) - return -1; - /* We shouldn't have polled for this event if this wasn't true! */ assert(plan->status == IO_POLLING); switch (plan->io(conn->fd.fd, &plan->arg)) { case -1: io_close(conn); - return -1; + return false; case 0: - return 0; + return true; case 1: - next_plan(conn, plan); - return 1; + return next_plan(conn, plan); default: /* IO should only return -1, 0 or 1 */ abort(); @@ -382,7 +383,8 @@ static int do_plan(struct io_conn *conn, struct io_plan *plan) void io_ready(struct io_conn *conn, int pollflags) { if (pollflags & POLLIN) - do_plan(conn, &conn->plan[IO_IN]); + if (!do_plan(conn, &conn->plan[IO_IN])) + return; if (pollflags & POLLOUT) do_plan(conn, &conn->plan[IO_OUT]); @@ -391,7 +393,8 @@ void io_ready(struct io_conn *conn, int pollflags) void io_do_always(struct io_conn *conn) { if (conn->plan[IO_IN].status == IO_ALWAYS) - next_plan(conn, &conn->plan[IO_IN]); + if (!next_plan(conn, &conn->plan[IO_IN])) + return; if (conn->plan[IO_OUT].status == IO_ALWAYS) next_plan(conn, &conn->plan[IO_OUT]); @@ -409,15 +412,8 @@ void io_do_wakeup(struct io_conn *conn, enum io_direction dir) /* Close the connection, we're done. */ struct io_plan *io_close(struct io_conn *conn) { - /* Already closing? Don't close twice. */ - if (conn->plan[IO_IN].status == IO_CLOSING) - return &conn->plan[IO_IN]; - - conn->plan[IO_IN].status = conn->plan[IO_OUT].status = IO_CLOSING; - conn->plan[IO_IN].arg.u1.s = errno; - backend_new_closing(conn); - - return io_set_plan(conn, IO_IN, NULL, NULL, NULL); + tal_free(conn); + return &io_conn_freed; } struct io_plan *io_close_cb(struct io_conn *conn, void *next_arg) @@ -453,10 +449,6 @@ struct io_plan *io_duplex(struct io_conn *conn, struct io_plan *io_halfclose(struct io_conn *conn) { - /* Already closing? Don't close twice. */ - if (conn->plan[IO_IN].status == IO_CLOSING) - return &conn->plan[IO_IN]; - /* Both unset? OK. */ if (conn->plan[IO_IN].status == IO_UNSET && conn->plan[IO_OUT].status == IO_UNSET) @@ -479,7 +471,7 @@ struct io_plan *io_set_plan(struct io_conn *conn, enum io_direction dir, plan->io = io; plan->next = next; plan->next_arg = next_arg; - assert(plan->status == IO_CLOSING || next != NULL); + assert(next != NULL); return plan; } diff --git a/ccan/io/poll.c b/ccan/io/poll.c index 36af33ef..6738f720 100644 --- a/ccan/io/poll.c +++ b/ccan/io/poll.c @@ -95,10 +95,17 @@ static void del_fd(struct fd *fd) close(fd->fd); } +static void destroy_listener(struct io_listener *l) +{ + close(l->fd.fd); + del_fd(&l->fd); +} + bool add_listener(struct io_listener *l) { if (!add_fd(&l->fd, POLLIN)) return false; + tal_add_destructor(l, destroy_listener); return true; } @@ -107,13 +114,6 @@ void remove_from_always(struct io_conn *conn) list_del_init(&conn->always); } -void backend_new_closing(struct io_conn *conn) -{ - /* In case it's on always list, remove it. */ - list_del_init(&conn->always); - list_add_tail(&closing, &conn->closing); -} - void backend_new_always(struct io_conn *conn) { /* In case it's already in always list. */ @@ -164,25 +164,28 @@ void backend_wake(const void *wait) } } -bool add_conn(struct io_conn *c) +static void destroy_conn(struct io_conn *conn) { - return add_fd(&c->fd, 0); -} + int saved_errno = errno; -static void del_conn(struct io_conn *conn) -{ + close(conn->fd.fd); del_fd(&conn->fd); + /* In case it's on always list, remove it. */ + list_del_init(&conn->always); + + /* errno saved/restored by tal_free itself. */ if (conn->finish) { - /* Saved by io_close */ - errno = conn->plan[IO_IN].arg.u1.s; + errno = saved_errno; conn->finish(conn, conn->finish_arg); } - tal_free(conn); } -void del_listener(struct io_listener *l) +bool add_conn(struct io_conn *c) { - del_fd(&l->fd); + if (!add_fd(&c->fd, 0)) + return false; + tal_add_destructor(c, destroy_conn); + return true; } static void accept_conn(struct io_listener *l) @@ -196,22 +199,6 @@ static void accept_conn(struct io_listener *l) io_new_conn(l->ctx, fd, l->init, l->arg); } -/* It's OK to miss some, as long as we make progress. */ -static bool close_conns(void) -{ - bool ret = false; - struct io_conn *conn; - - while ((conn = list_pop(&closing, struct io_conn, closing)) != NULL) { - assert(conn->plan[IO_IN].status == IO_CLOSING); - assert(conn->plan[IO_OUT].status == IO_CLOSING); - - del_conn(conn); - ret = true; - } - return ret; -} - static bool handle_always(void) { bool ret = false; @@ -244,11 +231,6 @@ void *io_loop(struct timers *timers, struct timer **expired) while (!io_loop_return) { int i, r, ms_timeout = -1; - if (close_conns()) { - /* Could have started/finished more. */ - continue; - } - if (handle_always()) { /* Could have started/finished more. */ continue; @@ -309,8 +291,6 @@ void *io_loop(struct timers *timers, struct timer **expired) } } - close_conns(); - ret = io_loop_return; io_loop_return = NULL; diff --git a/ccan/io/test/run-18-errno.c b/ccan/io/test/run-18-errno.c index 75d84cca..904a20ca 100644 --- a/ccan/io/test/run-18-errno.c +++ b/ccan/io/test/run-18-errno.c @@ -27,8 +27,8 @@ static struct io_plan *init_conn(struct io_conn *conn, int *state) { if (*state == 0) { (*state)++; - errno = 100; io_set_finish(conn, finish_100, state); + errno = 100; return io_close(conn); } else { ok1(*state == 2); -- 2.39.2