Added merely as an example to be run by hand, rather than automated
into 'make check', since nbdkit --filter=noparallel is quite new.
---
.gitignore | 1 +
examples/Makefile.am | 10 ++
examples/batched-read-write.c | 214 ++++++++++++++++++++++++++++++++++
3 files changed, 225 insertions(+)
create mode 100644 examples/batched-read-write.c
diff --git a/.gitignore b/.gitignore
index 66ff811..2ea7da0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ Makefile.in
/docs/libnbd.3
/docs/libnbd-api.3
/docs/libnbd-api.pod
+/examples/batched-read-write
/examples/threaded-reads-and-writes
/examples/simple-fetch-first-sector
/examples/simple-reads-and-writes
diff --git a/examples/Makefile.am b/examples/Makefile.am
index be3f21d..6825384 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -18,6 +18,7 @@
include $(top_srcdir)/subdir-rules.mk
noinst_PROGRAMS = \
+ batched-read-write \
simple-fetch-first-sector \
simple-reads-and-writes \
threaded-reads-and-writes
@@ -50,3 +51,12 @@ threaded_reads_and_writes_CFLAGS = \
threaded_reads_and_writes_LDADD = \
$(top_builddir)/lib/libnbd.la \
$(PTHREAD_LIBS)
+
+batched_read_write_SOURCES = \
+ batched-read-write.c
+batched_read_write_CPPFLAGS = \
+ -I$(top_srcdir)/include
+batched_read_write_CFLAGS = \
+ $(WARNINGS_CFLAGS)
+batched_read_write_LDADD = \
+ $(top_builddir)/lib/libnbd.la
diff --git a/examples/batched-read-write.c b/examples/batched-read-write.c
new file mode 100644
index 0000000..5dda2e2
--- /dev/null
+++ b/examples/batched-read-write.c
@@ -0,0 +1,214 @@
+/* This example can be copied, used and modified for any purpose
+ * without restrictions.
+ *
+ * Example usage with nbdkit:
+ *
+ * nbdkit -U - --filter=noparallel memory 2M \
+ * --run './batched-read-write $unixsocket'
+ *
+ * This will attempt to batch a large aio read request immediately
+ * followed by a large aio write request, prior to waiting for any
+ * command replies from the server. A naive client that does not check
+ * for available read data related to the first command while trying
+ * to write data for the second command, coupled with a server that
+ * only processes commands serially, would cause deadlock (both
+ * processes fill up their write buffers waiting for a reader); thus,
+ * this tests that libnbd is smart enough to always respond to replies
+ * for in-flight requests even when it has batched up other commands
+ * to write.
+ *
+ * To run it against a remote server over TCP:
+ *
+ * ./batched-read-write hostname port
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+#include <time.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <libnbd.h>
+
+/* The single NBD handle. */
+static struct nbd_handle *nbd;
+
+/* Buffers used for the test. */
+static char *in, *out;
+static int64_t packetsize;
+
+static int
+try_deadlock (void *arg)
+{
+ struct pollfd fds[1];
+ struct nbd_connection *conn;
+ size_t i;
+ int64_t handles[2], done;
+ size_t in_flight; /* counts number of requests in flight */
+ int dir, r;
+
+ /* The single thread "owns" the connection. */
+ conn = nbd_get_connection (nbd, 0);
+
+ /* Issue commands. */
+ in_flight = 0;
+ handles[0] = nbd_aio_pread (conn, in, packetsize, 0);
+ if (handles[0] == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ goto error;
+ }
+ in_flight++;
+ handles[1] = nbd_aio_pwrite (conn, out, packetsize, packetsize, 0);
+ if (handles[1] == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ goto error;
+ }
+ in_flight++;
+
+ /* Now wait for commands to retire, or for deadlock to occur */
+ while (in_flight > 0) {
+ if (nbd_aio_is_dead (conn) || nbd_aio_is_closed (conn)) {
+ fprintf (stderr, "connection is dead or closed\n");
+ goto error;
+ }
+
+ fds[0].fd = nbd_aio_get_fd (conn);
+ fds[0].events = 0;
+ fds[0].revents = 0;
+ dir = nbd_aio_get_direction (conn);
+ if ((dir & LIBNBD_AIO_DIRECTION_READ) != 0)
+ fds[0].events |= POLLIN;
+ if ((dir & LIBNBD_AIO_DIRECTION_WRITE) != 0)
+ fds[0].events |= POLLOUT;
+
+ if (poll (fds, 1, -1) == -1) {
+ perror ("poll");
+ goto error;
+ }
+
+ if ((dir & LIBNBD_AIO_DIRECTION_READ) != 0 &&
+ (fds[0].revents & POLLIN) != 0)
+ nbd_aio_notify_read (conn);
+ else if ((dir & LIBNBD_AIO_DIRECTION_WRITE) != 0 &&
+ (fds[0].revents & POLLOUT) != 0)
+ nbd_aio_notify_write (conn);
+
+ /* If a command is ready to retire, retire it. */
+ while ((done = nbd_aio_peek_command_completed (conn)) >= 0) {
+ for (i = 0; i < in_flight; ++i) {
+ if (handles[i] == done) {
+ r = nbd_aio_command_completed (conn, handles[i]);
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ goto error;
+ }
+ assert (r);
+ memmove (&handles[i], &handles[i+1],
+ sizeof (handles[0]) * (in_flight - i - 1));
+ break;
+ }
+ }
+ assert (i < in_flight);
+ in_flight--;
+ }
+ }
+
+ printf ("finished OK\n");
+
+ return 0;
+
+ error:
+ fprintf (stderr, "failed\n");
+ return -1;
+}
+
+static void
+alarm_handler (int sig)
+{
+ fprintf (stderr, "alarm fired; deadlock probably occurred\n");
+ exit (EXIT_FAILURE);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int64_t exportsize;
+
+ if (argc < 2 || argc > 3) {
+ fprintf (stderr, "%s socket | hostname port\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* Connect synchronously as this is simpler. */
+ if (argc == 2) {
+ if (nbd_connect_unix (nbd, argv[1]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ }
+ else {
+ if (nbd_connect_tcp (nbd, argv[1], argv[2]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (nbd_read_only (nbd) == 1) {
+ fprintf (stderr, "%s: error: this NBD export is read-only\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ exportsize = nbd_get_size (nbd);
+ if (exportsize == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ packetsize = exportsize / 2;
+ if (packetsize > 2 * 1024 * 1024)
+ packetsize = 2 * 1024 * 1024;
+
+ in = malloc (packetsize);
+ out = malloc (packetsize);
+ if (!in || !out) {
+ fprintf (stderr, "insufficient memory\n");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Attempt to be non-destructive, by writing what file already contains */
+ if (nbd_pread (nbd, out, packetsize, packetsize) == -1) {
+ fprintf (stderr, "sync read failed: %s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* When not debugging, set an alarm, in case this test deadlocks
+ * instead of succeeding
+ */
+ if (nbd_get_debug (nbd) < 1) {
+ signal (SIGALRM, alarm_handler);
+ alarm (10);
+ }
+
+ if (try_deadlock (NULL) == -1)
+ exit (EXIT_FAILURE);
+
+ if (nbd_shutdown (nbd) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_close (nbd);
+
+ return EXIT_SUCCESS;
+}
--
2.20.1