Browse Source

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 <rusty@rustcorp.com.au>
Rusty Russell 9 years ago
parent
commit
31c816a6a9
4 changed files with 46 additions and 77 deletions
  1. 3 6
      ccan/io/backend.h
  2. 22 30
      ccan/io/io.c
  3. 20 40
      ccan/io/poll.c
  4. 1 1
      ccan/io/test/run-18-errno.c

+ 3 - 6
ccan/io/backend.h

@@ -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,
-	/* Closing (both plans will be the same). */
-	IO_CLOSING
+	IO_ALWAYS
 };
 };
 
 
 /**
 /**
@@ -60,8 +58,8 @@ struct io_plan {
 struct io_conn {
 struct io_conn {
 	struct fd fd;
 	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)(struct io_conn *, void *arg);
 	void *finish_arg;
 	void *finish_arg;
@@ -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);

+ 22 - 30
ccan/io/io.c

@@ -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,
 
 
 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)
 	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)
 
 
 	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)
 	       || 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,
 	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,
 
 
 	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)
 	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 1;
+		return next_plan(conn, plan);
 	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)
 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)
 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)
 /* 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. */
-	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)
@@ -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,
 	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;
 }
 }

+ 20 - 40
ccan/io/poll.c

@@ -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)
 	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)
 	}
 	}
 }
 }
 
 
-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 = conn->plan[IO_IN].arg.u1.s;
+		errno = saved_errno;
 		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)
 	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)
 	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)
 		}
 		}
 	}
 	}
 
 
-	close_conns();
-
 	ret = io_loop_return;
 	ret = io_loop_return;
 	io_loop_return = NULL;
 	io_loop_return = NULL;
 
 

+ 1 - 1
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) {
 	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);