Browse Source

ccan/io: add io_out_wait() and io_out_always().

This specificity is required, for example, when doing:

	return io_duplex(conn, io_read(...), io_always(...));

The workaround suggested doesn't work, because io_duplex_prepare()
asserts() if the plans aren't UNSET to start.  And pettycoin needs this.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 11 years ago
parent
commit
0fbc79090f
3 changed files with 100 additions and 31 deletions
  1. 41 21
      ccan/io/io.c
  2. 45 6
      ccan/io/io.h
  3. 14 4
      ccan/io/test/run-12-bidir.c

+ 41 - 21
ccan/io/io.c

@@ -125,16 +125,28 @@ static struct io_plan *set_always(struct io_conn *conn,
 	return io_set_plan(conn, dir, NULL, next, arg);
 	return io_set_plan(conn, dir, NULL, next, arg);
 }
 }
 
 
+static struct io_plan *io_always_dir(struct io_conn *conn,
+				     enum io_direction dir,
+				     struct io_plan *(*next)(struct io_conn *,
+							     void *),
+				     void *arg)
+{
+	return set_always(conn, dir, next, arg);
+}
+
 struct io_plan *io_always_(struct io_conn *conn,
 struct io_plan *io_always_(struct io_conn *conn,
 			   struct io_plan *(*next)(struct io_conn *, void *),
 			   struct io_plan *(*next)(struct io_conn *, void *),
 			   void *arg)
 			   void *arg)
 {
 {
-	/* If we're duplex, we want this on the current plan.  Otherwise,
-	 * doesn't matter. */
-	if (conn->plan[IO_IN].status == IO_UNSET)
-		return set_always(conn, IO_IN, next, arg);
-	else
-		return set_always(conn, IO_OUT, next, arg);
+	return io_always_dir(conn, IO_IN, next, arg);
+}
+
+struct io_plan *io_out_always_(struct io_conn *conn,
+			       struct io_plan *(*next)(struct io_conn *,
+						       void *),
+			       void *arg)
+{
+	return io_always_dir(conn, IO_OUT, next, arg);
 }
 }
 
 
 static int do_write(int fd, struct io_plan_arg *arg)
 static int do_write(int fd, struct io_plan_arg *arg)
@@ -294,22 +306,14 @@ struct io_plan *io_connect_(struct io_conn *conn, const struct addrinfo *addr,
 	return io_set_plan(conn, IO_IN, do_connect, next, next_arg);
 	return io_set_plan(conn, IO_IN, do_connect, next, next_arg);
 }
 }
 
 
-struct io_plan *io_wait_(struct io_conn *conn,
-			 const void *wait,
-			 struct io_plan *(*next)(struct io_conn *, void *),
-			 void *next_arg)
+static struct io_plan *io_wait_dir(struct io_conn *conn,
+				   const void *wait,
+				   enum io_direction dir,
+				   struct io_plan *(*next)(struct io_conn *,
+							   void *),
+				   void *next_arg)
 {
 {
-	enum io_direction dir;
-	struct io_plan_arg *arg;
-
-	/* If we're duplex, we want this on the current plan.  Otherwise,
-	 * doesn't matter. */
-	if (conn->plan[IO_IN].status == IO_UNSET)
-		dir = IO_IN;
-	else
-		dir = IO_OUT;
-
-	arg = io_plan_arg(conn, dir);
+	struct io_plan_arg *arg = io_plan_arg(conn, dir);
 	arg->u1.const_vp = wait;
 	arg->u1.const_vp = wait;
 
 
 	conn->plan[dir].status = IO_WAITING;
 	conn->plan[dir].status = IO_WAITING;
@@ -317,6 +321,22 @@ struct io_plan *io_wait_(struct io_conn *conn,
 	return io_set_plan(conn, dir, NULL, next, next_arg);
 	return io_set_plan(conn, dir, NULL, next, next_arg);
 }
 }
 
 
+struct io_plan *io_wait_(struct io_conn *conn,
+			 const void *wait,
+			 struct io_plan *(*next)(struct io_conn *, void *),
+			 void *next_arg)
+{
+	return io_wait_dir(conn, wait, IO_IN, next, next_arg);
+}
+
+struct io_plan *io_out_wait_(struct io_conn *conn,
+			     const void *wait,
+			     struct io_plan *(*next)(struct io_conn *, void *),
+			     void *next_arg)
+{
+	return io_wait_dir(conn, wait, IO_OUT, next, next_arg);
+}
+
 void io_wake(const void *wait)
 void io_wake(const void *wait)
 {
 {
 	backend_wake(wait);
 	backend_wake(wait);

+ 45 - 6
ccan/io/io.h

@@ -362,6 +362,27 @@ struct io_plan *io_always_(struct io_conn *conn,
 			   struct io_plan *(*next)(struct io_conn *, void *),
 			   struct io_plan *(*next)(struct io_conn *, void *),
 			   void *arg);
 			   void *arg);
 
 
+/**
+ * io_out_always - output plan to immediately call next callback
+ * @conn: the connection that plan is for.
+ * @next: function to call.
+ * @arg: @next argument
+ *
+ * This is a variant of io_always() which uses the output plan; it only
+ * matters if you are using io_duplex, and thus have two plans running at
+ * once.
+ */
+#define io_out_always(conn, next, arg)					\
+	io_out_always_((conn), typesafe_cb_preargs(struct io_plan *, void *, \
+						   (next), (arg),	\
+						   struct io_conn *),	\
+		       (arg))
+
+struct io_plan *io_out_always_(struct io_conn *conn,
+			       struct io_plan *(*next)(struct io_conn *,
+						       void *),
+			       void *arg);
+
 /**
 /**
  * io_connect - create an asynchronous connection to a listening socket.
  * io_connect - create an asynchronous connection to a listening socket.
  * @conn: the connection that plan is for.
  * @conn: the connection that plan is for.
@@ -418,12 +439,6 @@ struct io_plan *io_connect_(struct io_conn *conn, const struct addrinfo *addr,
  *
  *
  * Note that if either plan closes the connection, it will be closed.
  * Note that if either plan closes the connection, it will be closed.
  *
  *
- * Note that if one plan is io_wait or io_always, that causes a problem:
- * they look at the input and output plan slots to figure out which to
- * use, but if the other plan hasn't been evaluated yet, that will fail.
- * In this case, you'll need to ensure the other plan is evaluated first,
- * eg. "struct io_plan *r = io_read(...); return io_duplex(r, io_always(...))"
- *
  * Example:
  * Example:
  * struct buf {
  * struct buf {
  *	char in[100];
  *	char in[100];
@@ -501,6 +516,30 @@ struct io_plan *io_wait_(struct io_conn *conn,
 			 void *arg);
 			 void *arg);
 
 
 
 
+/**
+ * io_out_wait - leave the output plan idle until something wakes us.
+ * @conn: the connection that plan is for.
+ * @waitaddr: the address to wait on.
+ * @next: function to call after waiting.
+ * @arg: @next argument
+ *
+ * io_wait() makes the input plan idle: if you're not using io_duplex it
+ * doesn't matter which plan is waiting.  Otherwise, you may need to use
+ * io_out_wait() instead, to specify explicitly that the output plan is
+ * waiting.
+ */
+#define io_out_wait(conn, waitaddr, next, arg)				\
+	io_out_wait_((conn), (waitaddr),				\
+		     typesafe_cb_preargs(struct io_plan *, void *,	\
+					 (next), (arg),			\
+					 struct io_conn *),		\
+		     (arg))
+
+struct io_plan *io_out_wait_(struct io_conn *conn,
+			     const void *wait,
+			     struct io_plan *(*next)(struct io_conn *, void *),
+			     void *arg);
+
 /**
 /**
  * io_wake - wake up any connections waiting on @wait
  * io_wake - wake up any connections waiting on @wait
  * @waitaddr: the address to trigger.
  * @waitaddr: the address to trigger.

+ 14 - 4
ccan/io/test/run-12-bidir.c

@@ -24,10 +24,20 @@ static void finish_ok(struct io_conn *conn, struct data *d)
 	d->state++;
 	d->state++;
 }
 }
 
 
-static struct io_plan *rw_done(struct io_conn *conn, struct data *d)
+static struct io_plan *r_done(struct io_conn *conn, struct data *d)
 {
 {
 	d->state++;
 	d->state++;
-	return io_halfclose(conn);
+	if (d->state == 3)
+		return io_close(conn);
+	return io_wait(conn, NULL, io_never, NULL);
+}
+
+static struct io_plan *w_done(struct io_conn *conn, struct data *d)
+{
+	d->state++;
+	if (d->state == 3)
+		return io_close(conn);
+	return io_out_wait(conn, NULL, io_never, NULL);
 }
 }
 
 
 static struct io_plan *init_conn(struct io_conn *conn, struct data *d)
 static struct io_plan *init_conn(struct io_conn *conn, struct data *d)
@@ -44,8 +54,8 @@ static struct io_plan *init_conn(struct io_conn *conn, struct data *d)
 	io_set_finish(conn, finish_ok, d);
 	io_set_finish(conn, finish_ok, d);
 
 
 	return io_duplex(conn,
 	return io_duplex(conn,
-			 io_read(conn, d->buf, sizeof(d->buf), rw_done, d),
-			 io_write(conn, d->wbuf, sizeof(d->wbuf), rw_done, d));
+			 io_read(conn, d->buf, sizeof(d->buf), r_done, d),
+			 io_write(conn, d->wbuf, sizeof(d->wbuf), w_done, d));
 }
 }
 
 
 static int make_listen_fd(const char *port, struct addrinfo **info)
 static int make_listen_fd(const char *port, struct addrinfo **info)