Previously, our 'cmds_to_issue' list was at most 1 element long,
because we reject all commands except from state READY, but don't get
back to state READY until the issue-commands sequence has completed.
However, this is not as friendly on the client - once we are in
transmission phase, a client may want to queue up another command
whether or not the state machine is still tied up in processing a
previous command. We still want to reject commands sent before the
first time we reach READY, as well as keep the cmd_issue event for
kicking the state machine into action when there is no previous
command being worked on, but otherwise, the state machine itself can
recognize when the command queue needs draining.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
generator/states.c | 6 ++++++
lib/internal.h | 3 +++
lib/rw.c | 24 ++++++++++++++++++++----
3 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/generator/states.c b/generator/states.c
index 202c305..a4eecbd 100644
--- a/generator/states.c
+++ b/generator/states.c
@@ -110,6 +110,12 @@ send_from_wbuf (struct nbd_connection *conn)
/*----- End of prologue. -----*/
/* STATE MACHINE */ {
+ READY:
+ conn->reached_ready = true;
+ if (conn->cmds_to_issue)
+ SET_NEXT_STATE (%ISSUE_COMMAND.START);
+ return 0;
+
DEAD:
if (conn->sock) {
conn->sock->ops->close (conn->sock);
diff --git a/lib/internal.h b/lib/internal.h
index 67bd52a..2d6ad9d 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -175,6 +175,9 @@ struct nbd_connection {
/* Global flags from the server. */
uint16_t gflags;
+ /* True if we've ever reached the READY state. */
+ bool reached_ready;
+
/* When issuing a command, the first list contains commands waiting
* to be issued. The second list contains commands which have been
* issued and waiting for replies. The third list contains commands
diff --git a/lib/rw.c b/lib/rw.c
index a7587e9..ebd4ff9 100644
--- a/lib/rw.c
+++ b/lib/rw.c
@@ -246,7 +246,8 @@ command_common (struct nbd_connection *conn,
uint16_t flags, uint16_t type,
uint64_t offset, uint64_t count, void *data)
{
- struct command_in_flight *cmd;
+ struct command_in_flight *cmd, *prev_cmd;
+ bool event = !conn->reached_ready;
switch (type) {
/* Commands which send or receive data are limited to MAX_REQUEST_SIZE. */
@@ -296,9 +297,24 @@ command_common (struct nbd_connection *conn,
if (conn->structured_replies && cmd->data && type ==
NBD_CMD_READ)
memset (cmd->data, 0, cmd->count);
- cmd->next = conn->cmds_to_issue;
- conn->cmds_to_issue = cmd;
- if (nbd_internal_run (conn->h, conn, cmd_issue) == -1)
+ /* If we have not reached READY yet, sending the event lets the
+ * state machine fail for requesting a command too early. If there
+ * are no queued commands and we are already in state READY, send an
+ * event to kick the state machine. In all other cases, append our
+ * command to the end of the queue, and the state machine will
+ * eventually get to it when it cycles back to READY.
+ */
+ if (conn->cmds_to_issue != NULL) {
+ prev_cmd = conn->cmds_to_issue;
+ while (prev_cmd->next)
+ prev_cmd = prev_cmd->next;
+ prev_cmd->next = cmd;
+ }
+ else {
+ conn->cmds_to_issue = cmd;
+ event |= conn->state == STATE_READY;
+ }
+ if (event && nbd_internal_run (conn->h, conn, cmd_issue) == -1)
return NULL;
return cmd;
--
2.20.1