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 <rusty@rustcorp.com.au>
/* Waiting for io_wake */
IO_WAITING,
/* Always do this. */
/* Waiting for io_wake */
IO_WAITING,
/* Always do this. */
- IO_ALWAYS,
- /* Closing (both plans will be the same). */
- IO_CLOSING
struct io_conn {
struct fd fd;
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;
void (*finish)(struct io_conn *, void *arg);
void *finish_arg;
bool add_conn(struct io_conn *c);
bool add_duplex(struct io_conn *c);
void del_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);
void backend_new_always(struct io_conn *conn);
void backend_new_plan(struct io_conn *conn);
void remove_from_always(struct io_conn *conn);
+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 *),
struct io_listener *io_new_listener_(const tal_t *ctx, int fd,
struct io_plan *(*init)(struct io_conn *,
void *),
void io_close_listener(struct io_listener *l)
{
void io_close_listener(struct io_listener *l)
{
- close(l->fd.fd);
- del_listener(l);
-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);
{
struct io_plan *(*next)(struct io_conn *, void *arg);
plan = next(conn, plan->next_arg);
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]
/* It should have set a plan inside this conn (or duplex) */
assert(plan == &conn->plan[IO_IN]
|| plan == &conn->plan[IO_OUT]
|| conn->plan[IO_OUT].status != IO_UNSET);
backend_new_plan(conn);
|| conn->plan[IO_OUT].status != IO_UNSET);
backend_new_plan(conn);
}
static void set_blocking(int fd, bool block)
}
static void set_blocking(int fd, bool block)
conn->finish = NULL;
conn->finish_arg = NULL;
list_node_init(&conn->always);
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);
if (!add_conn(conn))
return tal_free(conn);
conn->plan[IO_IN].next = init;
conn->plan[IO_IN].next_arg = arg;
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;
-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);
/* 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);
- next_plan(conn, plan);
- return 1;
+ return next_plan(conn, plan);
default:
/* IO should only return -1, 0 or 1 */
abort();
default:
/* IO should only return -1, 0 or 1 */
abort();
void io_ready(struct io_conn *conn, int pollflags)
{
if (pollflags & POLLIN)
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]);
if (pollflags & POLLOUT)
do_plan(conn, &conn->plan[IO_OUT]);
void io_do_always(struct io_conn *conn)
{
if (conn->plan[IO_IN].status == IO_ALWAYS)
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]);
if (conn->plan[IO_OUT].status == IO_ALWAYS)
next_plan(conn, &conn->plan[IO_OUT]);
/* Close the connection, we're done. */
struct io_plan *io_close(struct io_conn *conn)
{
/* 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)
}
struct io_plan *io_close_cb(struct io_conn *conn, void *next_arg)
struct io_plan *io_halfclose(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)
/* Both unset? OK. */
if (conn->plan[IO_IN].status == IO_UNSET
&& conn->plan[IO_OUT].status == IO_UNSET)
plan->io = io;
plan->next = next;
plan->next_arg = next_arg;
plan->io = io;
plan->next = next;
plan->next_arg = next_arg;
- assert(plan->status == IO_CLOSING || next != NULL);
+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;
bool add_listener(struct io_listener *l)
{
if (!add_fd(&l->fd, POLLIN))
return false;
+ tal_add_destructor(l, destroy_listener);
list_del_init(&conn->always);
}
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. */
void backend_new_always(struct io_conn *conn)
{
/* In case it's already in always list. */
-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)
-{
+ /* In case it's on always list, remove it. */
+ list_del_init(&conn->always);
+
+ /* errno saved/restored by tal_free itself. */
- /* Saved by io_close */
- errno = conn->plan[IO_IN].arg.u1.s;
conn->finish(conn, conn->finish_arg);
}
conn->finish(conn, conn->finish_arg);
}
-void del_listener(struct io_listener *l)
+bool add_conn(struct io_conn *c)
+ if (!add_fd(&c->fd, 0))
+ return false;
+ tal_add_destructor(c, destroy_conn);
+ return true;
}
static void accept_conn(struct io_listener *l)
}
static void accept_conn(struct io_listener *l)
io_new_conn(l->ctx, fd, l->init, l->arg);
}
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;
static bool handle_always(void)
{
bool ret = false;
while (!io_loop_return) {
int i, r, ms_timeout = -1;
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;
if (handle_always()) {
/* Could have started/finished more. */
continue;
ret = io_loop_return;
io_loop_return = NULL;
ret = io_loop_return;
io_loop_return = NULL;
{
if (*state == 0) {
(*state)++;
{
if (*state == 0) {
(*state)++;
io_set_finish(conn, finish_100, state);
io_set_finish(conn, finish_100, state);
return io_close(conn);
} else {
ok1(*state == 2);
return io_close(conn);
} else {
ok1(*state == 2);