On 6/29/19 8:28 AM, Eric Blake wrote:
Having the client polling thread perform an O(n) loop over all known
in-flight commands after each time the poll woke up is somewhat
inefficient, and in a multi-threaded setup requires additional locking
beyond libnbd to track the set of known command handles. Better is a
way for aio commands to call a notify callback the moment a specific
command is ready to complete, and then a separate thread can gather
the final completion status using just libnbd's locking, making the
polling loop more efficient. This also provides an opportunity to
clean up any opaque data and/or change the final command status (for
example, writing a strict validator for nbd_aio_pread_structured can
change the command from success to failure if the server violated
protocol by not returning chunks to cover the entire read).
We also want the client to be aware of any issued/in-flight commands
that failed because they were stranded when the state machine moved to
CLOSED or DEAD. Previously, nbd_aio_command_completed() would never
locate such stranded commands, but adding a common point to fire the
notifier for such commands makes it also possible to move those
commands to the completion queue.
This patch sets up the framework, with observable effects for stranded
commands per the testsuite changes, but nothing yet actually sets the
notify callback; that will come in the next patch.
---
+/* Forcefully fail any remaining in-flight commands in list */
+void abort_commands (struct nbd_handle *h,
+ struct command_in_flight **list)
+{
+ struct command_in_flight *prev_cmd, *cmd;
+
+ for (cmd = *list, prev_cmd = NULL;
+ cmd != NULL;
+ prev_cmd = cmd, cmd = cmd->next) {
+ if (cmd->cb.notify && cmd->type != NBD_CMD_DISC) {
+ int error = cmd->error ? cmd->error : ENOTCONN;
+
+ if (cmd->cb.notify (cmd->cb.opaque, cmd->handle, &error) == -1
&& error)
+ cmd->error = error;
+ }
Note that this special-cases NBD_CMD_DISC - since we did not return a
handle to the user, nor add an nbd_aio_disconnect_notify() variant, and
since the server (if compliant) does not reply to NBD_CMD_DISC, I
decided it did not make sense to call a notify callback for that command
(instead, you know the disconnect command completed when the state
machine moves to CLOSED or DEAD).
But my special-casing was incomplete; I'm squashing this in before pushing:
diff --git a/lib/aio.c b/lib/aio.c
index b29378b..748665e 100644
--- a/lib/aio.c
+++ b/lib/aio.c
@@ -69,7 +69,7 @@ nbd_unlocked_aio_command_completed (struct nbd_handle *h,
if (cmd->handle == handle)
break;
}
- if (!cmd)
+ if (!cmd || cmd->type == NBD_CMD_DISC)
return 0;
type = cmd->type;
@@ -103,6 +103,14 @@ nbd_unlocked_aio_command_completed (struct
nbd_handle *h,
int64_t
nbd_unlocked_aio_peek_command_completed (struct nbd_handle *h)
{
+ /* Special case NBD_CMD_DISC, as it does not have a user-visible
handle */
+ if (h->cmds_done && h->cmds_done->type == NBD_CMD_DISC) {
+ struct command_in_flight *cmd = h->cmds_done;
+
+ h->cmds_done = cmd->next;
+ free (cmd);
+ }
+
if (h->cmds_done != NULL)
return h->cmds_done->handle;
diff --git a/lib/disconnect.c b/lib/disconnect.c
index 53de386..5bbc64b 100644
--- a/lib/disconnect.c
+++ b/lib/disconnect.c
@@ -60,10 +60,11 @@ nbd_unlocked_aio_disconnect (struct nbd_handle *h,
uint32_t flags)
return -1;
h->disconnect_request = true;
- /* This will leave the command on the in-flight list. Is this a
- * problem? Probably it isn't. If it is, we could add a flag to
- * the command struct to tell SEND_REQUEST not to add it to the
- * in-flight list.
+ /* As the server does not reply to this command, it is left
+ * in-flight until the cleanup performed when moving to CLOSED or
+ * DEAD. We don't return a handle to the user, and thus also
+ * special case things so that the user cannot request the status of
+ * this command during aio_[peek_]command_completed.
*/
return 0;
}
diff --git a/tests/server-death.c b/tests/server-death.c
index f8747e4..18ca5f8 100644
--- a/tests/server-death.c
+++ b/tests/server-death.c
@@ -145,6 +145,27 @@ main (int argc, char *argv[])
goto fail;
}
+ /* With all commands retired, no further command should be pending */
+ if (nbd_aio_in_flight (nbd) != 0) {
+ fprintf (stderr, "%s: test failed: nbd_aio_in_flight\n",
+ argv[0]);
+ goto fail;
+ }
+ if (nbd_aio_peek_command_completed (nbd) != -1) {
+ fprintf (stderr, "%s: test failed: nbd_aio_peek_command_completed\n",
+ argv[0]);
+ goto fail;
+ }
+ msg = nbd_get_error ();
+ err = nbd_get_errno ();
+ printf ("error: \"%s\"\n", msg);
+ printf ("errno: %d (%s)\n", err, strerror (err));
+ if (err != EINVAL) {
+ fprintf (stderr, "%s: test failed: unexpected errno %d (%s)\n",
argv[0],
+ err, strerror (err));
+ goto fail;
+ }
+
close (fd);
unlink (pidfile);
nbd_close (nbd);
--
2.20.1
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3226
Virtualization:
qemu.org |
libvirt.org