Commit 31c816a6 authored by Rusty Russell's avatar Rusty Russell

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: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 96dcdfbf
...@@ -31,9 +31,7 @@ enum io_plan_status { ...@@ -31,9 +31,7 @@ enum io_plan_status {
/* Waiting for io_wake */ /* Waiting for io_wake */
IO_WAITING, IO_WAITING,
/* Always do this. */ /* Always do this. */
IO_ALWAYS, IO_ALWAYS
/* Closing (both plans will be the same). */
IO_CLOSING
}; };
/** /**
...@@ -60,8 +58,8 @@ struct io_plan { ...@@ -60,8 +58,8 @@ struct io_plan {
struct io_conn { struct io_conn {
struct fd fd; struct fd fd;
/* always and closing lists. */ /* always list. */
struct list_node always, closing; struct list_node always;
void (*finish)(struct io_conn *, void *arg); void (*finish)(struct io_conn *, void *arg);
void *finish_arg; void *finish_arg;
...@@ -75,7 +73,6 @@ bool add_listener(struct io_listener *l); ...@@ -75,7 +73,6 @@ bool add_listener(struct io_listener *l);
bool add_conn(struct io_conn *c); bool add_conn(struct io_conn *c);
bool add_duplex(struct io_conn *c); bool add_duplex(struct io_conn *c);
void del_listener(struct io_listener *l); 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_always(struct io_conn *conn);
void backend_new_plan(struct io_conn *conn); void backend_new_plan(struct io_conn *conn);
void remove_from_always(struct io_conn *conn); void remove_from_always(struct io_conn *conn);
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
void *io_loop_return; void *io_loop_return;
struct io_plan io_conn_freed;
struct io_listener *io_new_listener_(const tal_t *ctx, int fd, struct io_listener *io_new_listener_(const tal_t *ctx, int fd,
struct io_plan *(*init)(struct io_conn *, struct io_plan *(*init)(struct io_conn *,
void *), void *),
...@@ -35,8 +37,6 @@ struct io_listener *io_new_listener_(const tal_t *ctx, int fd, ...@@ -35,8 +37,6 @@ struct io_listener *io_new_listener_(const tal_t *ctx, int fd,
void io_close_listener(struct io_listener *l) void io_close_listener(struct io_listener *l)
{ {
close(l->fd.fd);
del_listener(l);
tal_free(l); tal_free(l);
} }
...@@ -45,7 +45,8 @@ static struct io_plan *io_never_called(struct io_conn *conn, void *arg) ...@@ -45,7 +45,8 @@ static struct io_plan *io_never_called(struct io_conn *conn, void *arg)
abort(); 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); 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) ...@@ -57,6 +58,9 @@ static void next_plan(struct io_conn *conn, struct io_plan *plan)
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) */ /* It should have set a plan inside this conn (or duplex) */
assert(plan == &conn->plan[IO_IN] assert(plan == &conn->plan[IO_IN]
|| plan == &conn->plan[IO_OUT] || plan == &conn->plan[IO_OUT]
...@@ -65,6 +69,7 @@ static void next_plan(struct io_conn *conn, struct io_plan *plan) ...@@ -65,6 +69,7 @@ static void next_plan(struct io_conn *conn, struct io_plan *plan)
|| conn->plan[IO_OUT].status != IO_UNSET); || conn->plan[IO_OUT].status != IO_UNSET);
backend_new_plan(conn); backend_new_plan(conn);
return true;
} }
static void set_blocking(int fd, bool block) static void set_blocking(int fd, bool block)
...@@ -93,7 +98,6 @@ struct io_conn *io_new_conn_(const tal_t *ctx, int fd, ...@@ -93,7 +98,6 @@ struct io_conn *io_new_conn_(const tal_t *ctx, int fd,
conn->finish = NULL; conn->finish = NULL;
conn->finish_arg = NULL; conn->finish_arg = NULL;
list_node_init(&conn->always); list_node_init(&conn->always);
list_node_init(&conn->closing);
if (!add_conn(conn)) if (!add_conn(conn))
return tal_free(conn); return tal_free(conn);
...@@ -106,7 +110,8 @@ struct io_conn *io_new_conn_(const tal_t *ctx, int fd, ...@@ -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 = init;
conn->plan[IO_IN].next_arg = arg; 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; return conn;
} }
...@@ -355,24 +360,20 @@ void io_wake(const void *wait) ...@@ -355,24 +360,20 @@ void io_wake(const void *wait)
backend_wake(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! */ /* We shouldn't have polled for this event if this wasn't true! */
assert(plan->status == IO_POLLING); assert(plan->status == IO_POLLING);
switch (plan->io(conn->fd.fd, &plan->arg)) { switch (plan->io(conn->fd.fd, &plan->arg)) {
case -1: case -1:
io_close(conn); io_close(conn);
return -1; return false;
case 0: case 0:
return 0; return true;
case 1: case 1:
next_plan(conn, plan); return next_plan(conn, plan);
return 1;
default: default:
/* IO should only return -1, 0 or 1 */ /* IO should only return -1, 0 or 1 */
abort(); abort();
...@@ -382,7 +383,8 @@ static int do_plan(struct io_conn *conn, struct io_plan *plan) ...@@ -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) void io_ready(struct io_conn *conn, int pollflags)
{ {
if (pollflags & POLLIN) if (pollflags & POLLIN)
do_plan(conn, &conn->plan[IO_IN]); if (!do_plan(conn, &conn->plan[IO_IN]))
return;
if (pollflags & POLLOUT) if (pollflags & POLLOUT)
do_plan(conn, &conn->plan[IO_OUT]); do_plan(conn, &conn->plan[IO_OUT]);
...@@ -391,7 +393,8 @@ void io_ready(struct io_conn *conn, int pollflags) ...@@ -391,7 +393,8 @@ void io_ready(struct io_conn *conn, int pollflags)
void io_do_always(struct io_conn *conn) void io_do_always(struct io_conn *conn)
{ {
if (conn->plan[IO_IN].status == IO_ALWAYS) 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) if (conn->plan[IO_OUT].status == IO_ALWAYS)
next_plan(conn, &conn->plan[IO_OUT]); next_plan(conn, &conn->plan[IO_OUT]);
...@@ -409,15 +412,8 @@ void io_do_wakeup(struct io_conn *conn, enum io_direction dir) ...@@ -409,15 +412,8 @@ void io_do_wakeup(struct io_conn *conn, enum io_direction dir)
/* Close the connection, we're done. */ /* Close the connection, we're done. */
struct io_plan *io_close(struct io_conn *conn) struct io_plan *io_close(struct io_conn *conn)
{ {
/* Already closing? Don't close twice. */ tal_free(conn);
if (conn->plan[IO_IN].status == IO_CLOSING) return &io_conn_freed;
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);
} }
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)
...@@ -453,10 +449,6 @@ struct io_plan *io_duplex(struct io_conn *conn, ...@@ -453,10 +449,6 @@ struct io_plan *io_duplex(struct io_conn *conn,
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. */ /* Both unset? OK. */
if (conn->plan[IO_IN].status == IO_UNSET if (conn->plan[IO_IN].status == IO_UNSET
&& conn->plan[IO_OUT].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, ...@@ -479,7 +471,7 @@ struct io_plan *io_set_plan(struct io_conn *conn, enum io_direction dir,
plan->io = io; plan->io = io;
plan->next = next; plan->next = next;
plan->next_arg = next_arg; plan->next_arg = next_arg;
assert(plan->status == IO_CLOSING || next != NULL); assert(next != NULL);
return plan; return plan;
} }
...@@ -95,10 +95,17 @@ static void del_fd(struct fd *fd) ...@@ -95,10 +95,17 @@ static void del_fd(struct fd *fd)
close(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) bool add_listener(struct io_listener *l)
{ {
if (!add_fd(&l->fd, POLLIN)) if (!add_fd(&l->fd, POLLIN))
return false; return false;
tal_add_destructor(l, destroy_listener);
return true; return true;
} }
...@@ -107,13 +114,6 @@ void remove_from_always(struct io_conn *conn) ...@@ -107,13 +114,6 @@ void remove_from_always(struct io_conn *conn)
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) void backend_new_always(struct io_conn *conn)
{ {
/* In case it's already in always list. */ /* In case it's already in always list. */
...@@ -164,25 +164,28 @@ void backend_wake(const void *wait) ...@@ -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); 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) { if (conn->finish) {
/* Saved by io_close */ errno = saved_errno;
errno = conn->plan[IO_IN].arg.u1.s;
conn->finish(conn, conn->finish_arg); 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) static void accept_conn(struct io_listener *l)
...@@ -196,22 +199,6 @@ 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); 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) static bool handle_always(void)
{ {
bool ret = false; bool ret = false;
...@@ -244,11 +231,6 @@ void *io_loop(struct timers *timers, struct timer **expired) ...@@ -244,11 +231,6 @@ void *io_loop(struct timers *timers, struct timer **expired)
while (!io_loop_return) { while (!io_loop_return) {
int i, r, ms_timeout = -1; int i, r, ms_timeout = -1;
if (close_conns()) {
/* Could have started/finished more. */
continue;
}
if (handle_always()) { if (handle_always()) {
/* Could have started/finished more. */ /* Could have started/finished more. */
continue; continue;
...@@ -309,8 +291,6 @@ void *io_loop(struct timers *timers, struct timer **expired) ...@@ -309,8 +291,6 @@ void *io_loop(struct timers *timers, struct timer **expired)
} }
} }
close_conns();
ret = io_loop_return; ret = io_loop_return;
io_loop_return = NULL; io_loop_return = NULL;
......
...@@ -27,8 +27,8 @@ static struct io_plan *init_conn(struct io_conn *conn, int *state) ...@@ -27,8 +27,8 @@ static struct io_plan *init_conn(struct io_conn *conn, int *state)
{ {
if (*state == 0) { if (*state == 0) {
(*state)++; (*state)++;
errno = 100;
io_set_finish(conn, finish_100, state); io_set_finish(conn, finish_100, state);
errno = 100;
return io_close(conn); return io_close(conn);
} else { } else {
ok1(*state == 2); ok1(*state == 2);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment